前言
本篇文章我们来学一下泛型和委托,Kotlin中的泛型跟java中的泛型有同有异,而委托则是一个新的概念,
发车了兄弟们GO GO GO ~
1:泛型
1.1:泛型的普通用法
我们先来学习一下kotlin中和java中相同的用法,也就基本用法,定义泛型的方式如下
class MyClass<T> {
fun method(param: T): T {
return param
}
}
//调用方式为
val myClass = MyClass<Int>()
val myClassdata= myClass.method(123)
还可以对参数进行约束
fun <T : Number> method(param: T): T {
return param
}
在默认情况下,所有的泛型都是可以指定成可空类型,这是因为在不手动指定上界的时候,泛型的上界默认是Any? 而如果想要让泛型的类型不可为空,而如果想让泛型的类型不可为空,只需要将泛型的上界手动指定为Any就可以了
下边我们对泛型的知识进行应用
还记得我们之前写的build函数
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this
}
这个函数的作用和apply的作用一直,但是我们这个函数只能作用在StringBuilder类上边,接下来我们改造一下让他能在所有类上使用
非常简单,只需要在之前所有StringBuilder的地方全部替换成<T>就可以了
fun <T> T.build(block: T.() -> Unit): T {
block()
return this
}
接下来我们使用build函数简化Cursor的遍历
contentResolver.query(uri, null, null, null, null)?.build {
while (moveToNext()) {
...
}
close()
}
1.2:泛型的高级特性——泛型实化
首先了解一下java的泛型擦除机制
java的泛型对于类型的约束只在编译时期存在,运行的时候仍然会按照JDK1.5之前的机制来实现,Jvm是识别不出我们在代码中指定的泛型,比如List<String> JVM并不知道List内部包含String类型,只知道是一个List,所有基于JVM的语言他的泛型擦除都是通过类型擦除机制来实现的,这种机制导致我们不可能使用 a is T 或者T::class.java这样的语法,因为T的实际类型在运行的时候已经被擦除
然而不同的是Kotlin提供了内联函数,内联函数的代码会在编译的时候自动被替换到它调用的地方,这样就不存在泛型擦除问题,因为代码在编译之后会直接使用实际的类型来替代内联函数中的泛型声明
上方代码的foo函数调用了 内联泛型函数bar,最后do something with string type 实际上直接在foo函数中运行
这就意味着Kotlin中是可以将内联函数中的泛型进行实化的。
1.2.1:那么怎么才能将泛型实化?
1:该函数必须是内联函数才行,也就是要使用inline关键字进行修饰的函数
2:在声明泛型的地方必须加上reified关键字表示该泛型要进行实化,
inline fun <reified T> getGenericType() {
}
inline fun <reified T> getGenericType() =T::class.java
println("result:${getGenericType<String>()}")
打印后结果:
result:class java.lang.String
1.2.2:泛型实化的应用
泛型实化允许我们在泛型函数获得泛型的实际类型,这也就使得类似于a is T、T::class.java这样的语法成为了可能性;
平时我们在启动一个activity可以这样写
val intent = Intent(context, TestActivity::class.java)
context.startActivity(intent)
有没有感觉到TestActivity::class.java这样的写法很难受。Kotlin泛型实化使得我们拥有了更好的选择
新建一个reified文件,内容如下
inline fun <reified T> startActivity(context: Context) {
val intent = Intent(context, T::class.java)
context.startActivity(intent)
}
调用如下
startActivity<TestActivity>(context)
如果需要intent传值,我们添加一个高阶函数即可
inline fun <reified T> startActivityForIntent(context: Context, block: Intent.() -> Unit) {
val intent = Intent(context, T::class.java)
intent.block()
context.startActivity(intent)
}
调用如下
startActivityForIntent<MainActivity2>(context) {
putExtra("1", data1)
putExtra("2", data2)
}
1.3:泛型的协变:
泛型的协变平时用的不是很多,但是我们需要尽可能的了解一下
在开始学习之前我们需要先了解一个约定:一个泛型或者一个泛型接口中,它的参数是接收数据的地方,因此可以称它为in位置,它的返回值是输出数据的地方,因此可以称为out位置
泛型未完待续...
2:委托
首先委托是一种设计模式,它的基本概念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另一个辅助对象去处理,kotlin将委托功能分为两种:类委托和委托属性
2.1:类委托
它的核心思想是将一个类的具体实现交给另一个类去实现
有一种数据结构是set, set是一个接口,使用他的话需要使用hashSet这种数据具体的实现方式,代码如下
class Commission<T>(private val helperSet: HashSet<T>) : Set<T> {
override val size: Int
get() = helperSet.size
override fun contains(element: T): Boolean {
return helperSet.contains(element)
}
override fun containsAll(elements: Collection<T>): Boolean {
return helperSet.containsAll(elements);
}
override fun isEmpty(): Boolean {
return helperSet.isEmpty()
}
override fun iterator(): Iterator<T> {
return helperSet.iterator()
}
}
可以看到,commission的构造函数中接受了一个HashSet,这是一个辅助参数,然后在set接口的所有方法的接口中实现,而是调用了辅助对象中相应的方法实现。这是一种委托模式
这种写法的好处是什么呢,既然都是调用辅助对象的方法实现,那么我们直接使用辅助对象不就行了,但是如果我们想让大部分的方法实现调用辅助对象中相应的方法,少部分方法实现由自己重写,甚至加入一些自己独有的方法,那么myset会成为一个新的数据结构,这也就是委托模式的意义。
但是这种写法也有一定弊端,如果接口中待实现的方法过多,难不成我们要一个一个的实现?这个问题我们可以使用kotlin中通过类的委托来实现。
Kotlin中委托的关键字是by,我们只需要在接口声明的后面使用by关键字,再加上委托的辅助对象,就可以免去之前写的一堆模板式的代码了。
class Commission<T>(private val helperSet: HashSet<T>) : Set<T> by helperSet {
fun helloworld() = println("helloWorld")
override fun isEmpty(): Boolean {
return false
}
}
我们新增了helloworld方法,并且实现了isEmpty方法,现在我们的Commission类就成为了一个新的数据结构。
2.2:委托属性
我们来看一下委托属性了
class MyClass{
var p by Delegate()
}
class Delegate{
var propValue :Any?=null
operator fun getValue(myClass:myClass,prop:KProperty<*>):Any?{
return propValue
}
operator fun setValue(myClass:MyClass,prop:KProperty<*>:Any?,value:Any?){
propValue=value
}
}
可以看到这里使用by关键字连接了左边的p属性和右边的Delegate实列,这是什么意思呢,这种写法就代表着将P属性的具体实现委托给了Delegate类去完成,当调用p属性的时候会自动调用Delegate类的getValue方法
这是一种标准的代码实现模板,在Delegate类中我们必须实现getValue和setValue这两个方法,并且都要使用operator关键字进行声明,
getValue()方法接受两个参数:
一个参数用于声明该Delegate类的委托功能可以在什么类中使用,
第二个参数Kproperty<* *>是Kotlin中的一个属性操作类,可用于获取各种属性相关的值,必须在方法参数上进行声明,另外这种泛型写法表示你不知道或者不关心泛型的具体类型,知识未通过语法编译而已
setValue方法也是相似的只不过他要接受3个参数,前两个参数和getValue方法是相同的,最后一个参数表示要赋值给委托属性的值,这个参数的类型必须要和getValue()方法返回值的类型保持一致
委托属性的工作流程就是这样实现的,现在我们给MyClass的p属性赋值时,就会调用 Delegate
类的setValue,当获取p的值的时候,就会调用Delegate类的getValue方法,是不是很好理解呢
2.3:自己实现一个lazy函数
延时加载:在一开始执行的时候不加载,只在第一次调用的时候才会加载
把想要延时执行的代码放到by lazy{}代码块中就可以了
基本结构如下
val p by lazy{
}
现在我们再来看这段代码,是不是感觉很好理解了,by lazy 并不是连在一起的关键字,只有by才是Kotlin中的关键字,lazy知识一个高阶函数,在lazy函数中会创建并返回一个Delegate对象,当我们调用p属性的时候,其实调用的是Delegate对象的getvalue方法,然后getValue方法中又会调用lazy函数传入的lambda表达式,这样表达式中的代码就可以得到执行了,并且调用p属性后得到的值就是lambda表达式中最后一行代码的返回值
接下来我们自己实现一个lazy函数吧
class Later<T>(val block: () -> T) {
var value: Any? = null
}
首先我们定义一个Later泛型类,later的构造函数中接收一个函数型参数,这个参数类型不接收任何参数、返回值的类型就是Later类指定的泛型
class Later<T>(val block: () -> T) {
var value: Any? = null
operator fun getValue(any: Any, porp: KProperty<*>): T {
if (value == null) {
value = block()
}
return value as T
}
}
这里将getValue方法中的第一个参数指定成了Any?类型,因为我们希望委托功能在所有类中都可以使用,然后使用value变量对值进行缓存,如果value为空就调用构造函数中传入的函数类型参数去取值,否则直接返回
最后我们在Later类中添加一个顶层方法
fun <T> later(block: () -> T) = Later(block)
class Later<T>(val block: () -> T) {
var value: Any? = null
operator fun getValue(any: Any, porp: KProperty<*>): T {
if (value == null) {
value = block()
}
return value as T
}
}
最后我们来验证一下我们的懒加载
class MainActivity : AppCompatActivity() {
private val str by Later {
println("懒加载")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
text1.setOnClickListener {
str
}
}
}
你会发现之后在调用了text的点击事件之后才会打印字符串
本篇我们学习了Kotlin我们学习了泛型和委托这两块内容,内容确实挺多的,建议大家根据文章中的内容全部手敲一遍,做到掌握。
其中泛型实化带文章中讲到的例子用法我们可以再日常开发中直接食用,你会发现功能十分方便。
委托属性也要结合自己实现的懒加载方式进行理解。
下一节我们将来学习Kotlin中著名的协程协程了,看到这里的小伙伴恭喜你,基本上学完协程的知识我们的Kotlin就可以出山了哈~~,然后学完kotlin就是一些工具类的封装和DSL还有javakotlin代码之间的转换了
有什么问题欢迎留言指出😜😜😜😜😜😜😜😜😜😜
加油~