前言 《 Kotlin 实战 》读书笔记。
第二部分 拥抱 Kotlin 7 运算符重载及其他约定 约定 在 Kotlin 中,某些功能和特定的函数命名相关。比如在类中定义了一个名为 plus 的特殊方法,那么按照约定 ,就可以在该类的实例上使用 + 运算符。
重载算数运算符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 data class Point (val x: Int , val y: Int ) { operator fun plus (other: Point ) : Point { return Point(x + other.x, y + other.y) } operator fun Point.times (scale: Float ) : Point { return Point((x * scale).toInt(), (y * scale).toInt()) } } fun main () { val p1 = Point(1 , 2 ) val p2 = Point(3 , 4 ) println(p1 + p2) }
使用 operator 定义特定名称的方法(当然也可以是扩展函数),就可以按照普通的算数运算符使用,比如这里的 plus == + 。具体可以使用的方法名称如下表
表达式
函数名
a*b
times
a/b
div
a%b
mod
a+b
plus
a-b
minus
类似的可以有 minusAssign(重载复合运算符)、一元运算符、比较运算符、排序运算符等。
集合与区间约定
1 2 3 4 5 6 7 operator fun Point.get (index:Int ) : Int { return when (index) { 0 -> x 1 -> y else -> throw IndexOutOfBoundsException() } }
也可以自定 in,rangTo,interator 等。
解构声明和组件函数
1 2 3 4 val p1 = Point(1 , 2 )val (x,y) = p1println(x) println(y)
val (x,y)
称为解构声明 ,在初始化括号中的变量时,实际上是在调用名为 componentN 的函数,N为声明中变量的位置。对于数据类 data class
编译器为每个在柱构造方法中声明的属性生成了一个 componentN 函数。为了实现同样的功能,你也可以自定义这样的函数。 其实这个功能类似于 Pair / Triple 类的定义。
委托属性 1 2 3 4 5 class C { var prop : Type by MyDelegate } val c = C()
MyDelegate 的实例会被保存在一个隐藏的属性中,通过这个隐藏属性的 setValue 和 getValue 实现。
1 2 3 val value: Int by lazy { 8 * 8 }
value 只有在被第一次使用的的时候才会完成初始化
8 lambda 作为形参和返回值 声明高阶函数 高阶函数:用另一个函数作为参数或返回值的函数,在 Kotlin 中可以使用 lambda 或函数引用来表示。
变量的类型
1 2 3 4 5 6 7 8 9 10 11 val any = Any()val x = 4 val X = Integer(4 )val sum = { x: Int , y: Int -> x + y }val enter = { println() }println(any.javaClass.typeName) println(x.javaClass.typeName) println(X.javaClass.typeName) println(sum.javaClass.typeName) println(enter.javaClass.typeName)
output
1 2 3 4 5 java.lang.Object int java.lang.Integer com.fun.HighOrderFunKt$main$sum$1 com.fun.HighOrderFunKt$main$enter$1
可以看到 lambda 表达式引用的真实类型就是内部声明的lambda 表达式生成的类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 fun highOrderNotNull (x: Int , block: () -> Float ) : Double { if (x > 0 ) { return block().toDouble() } else { return 0.0 } } fun highOrder (x: Int , block: () -> Unit ) { println(x) block() } fun highOrderParam (x: Int , y: Int , block: (Int , Int ) -> Unit ) : Int { block(x, y) return x } fun highOrderParams (age: Int , name: String , boy: Boolean , block: (age : Int , name : String , boy : Boolean ) -> String ) : String { return block(age, name, boy) } fun String.exchange (block: (String ) -> String ) : String { return block(this ) }
在 函数类型 block 变量声明时,其内部的方法参数声明类型即可,不需要名称,因为这个参数实际上是调用方的参数而已。 但是声明了也有好处,在 IDE 中可以提升代码的可读性。
在 Kotlin 中调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 val m = highOrderParam(10 , 100 ) { a, b -> run { println("a * b is ${a * b} " ) } } val origin = "asdfjhjkl" val upper = origin.exchange { it.toUpperCase() } val number = origin.exchange { it.reversed() } println(upper) println(number)
在 Java 中调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Test { public static void main (String[] args) { HighOrderFunKt.highOrderParam(1 , 2 , new Function2<Integer, Integer, Unit>() { @Override public Unit invoke (Integer integer, Integer integer2) { System.out.println(integer); System.out.println(integer2); return Unit.INSTANCE; } }); HighOrderFunKt.highOrderParams(18 , "mike" , true , new Function3<Integer, String, Boolean, String>() { @Override public String invoke (Integer integer, String s, Boolean aBoolean) { return integer + s + aBoolean; } }); } }
可以看到实际上函数类型 被声明为普通的接口,一个函数类型的变量是 FunctionN 接口的一个实例。因此,在 Java 中调用带有函数类型的 Kotlin 方法时,创建 FunctionN 接口的实例即可。但是要注意的是,对于返回类型为 Unit 的方法,要显示的返回 Unit.INSTANCE。
当然,在 Java8 中可以直接使用 lambda,因此以上代码可以简化为
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Test { public static void main (String[] args) { HighOrderFunKt.highOrderParam(1 , 2 , (integer, integer2) -> { System.out.println(integer); System.out.println(integer2); return Unit.INSTANCE; }); HighOrderFunKt.highOrderParams(18 , "mike" , true , (integer, s, aBoolean) -> integer + s + aBoolean); } }
当然,函数类型的参数也可以有默认值,这和普通函数的定义是一样的。同时可以就是否为null进行定义,也可以就函数类型本身是否可以为null 进行定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 fun highOrderWithDefault (x: Int = 0 , block: (Int ) -> Unit ) { block(x) } fun highOrderWithNullParam (input: String ? = null , block: (String ?) -> Unit ) { block(input) } fun highOrderWithNull (input: String , block: () -> String ?) : String { return block() ?: input } fun highOrderDefault (input: String , block: ((String ) -> String ) = { "null" }) : String { return block.invoke(input) } fun highOrderNull (input: String , block: ((String ) -> String )? = {"null" }) : String { return block?.invoke(input) ?: "null" } fun useHighOrder () { highOrderWithDefault { println(it) } highOrderWithNullParam("a" ) { it?.apply { } } highOrderWithNull("a" ) { null } highOrderDefault("a" ) { "" } highOrderNull("a" ) }
声明 lambda 表达式(函数)作为返回值的函数
这个功能感觉有点炫技,相同的功能完全可以用正常的代码实现。这里简单举例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 fun highOrderReturnFun (type: String ) : (Int ) -> Float { val base = 32 val factor = 1.8f when (type) { "F" -> return { input -> base + input * factor } "C" -> return { input -> (input - base) / factor } else -> return { input -> base + input * factor } } } var calc = highOrderReturnFun("F" ) println("38 ℃ ≈ ${calc(38 )} " ) calc = highOrderReturnFun("C" ) println("100 ℉ ≈ ${calc(100 )} " )
output
1 2 38 ℃ ≈ 100.4 100 ℉ ≈ 37.77778
这里定义了一个函数,根据highOrderReturnFun函数的参数,分别返回了不同的函数。坦白说,相同的功能完全可以用有阅读性的代码实现。所以,返回函数的函数,个人感觉很鸡肋。如果要用或阅读到了此类函数,两个关键点
- 函数返回值,即冒号后面,是一个 lambda 表达式
- 函数 return 结果必然在一个大括号里,因为要返回一个 lambda 表达式。
内联函数 lambda 表达式会被正常的编译成匿名类。因此,每调用一次lambda表达式,一个额外的类会被创建。并且如果lambda表达式捕获了某个变量,每次调用还会创建一个新的变量。 这会带来额外的性能消耗。
可以使用 inline 修饰函数,实现 lambda 表达式被内联的效果,但是其实是有限制的。比如以函数作为参数的时候,内联效果并不是完整的,同时有变量捕获的情况时,也是无法使用的。
9 泛型 Kotlin 泛型的大部分使用规则和 Java 是类似的。同时提供了更多增强性的功能。
泛型允许我们定义带类型形参 的类型。
1 fun <T> List<T> .slice (indices: IntRange ) : List<T>
T 就是类型形参,T 的默认类型是 Any?。 及可空的任意类型,这是需要注意的一点。
泛型参数在运行是会被擦除,因此你在一般情况下无论是 Java 还是 Kotlin 中,你无法感知你的列表是一个字符串列表还是一个 People 的列表。
在 Kotlin 中可以使用 inline 函数,声明带实化类型 参数的函数。
1 2 3 4 inline fun <reified T> isA (value:Any ) = value is T println(isA<String>("aaa" )) println(isA<String>(111 ))
out
可以看到,在运行是我们是可以感知到泛型实际的类型的。
用实化类型参数还可以替代类引用
1 2 3 4 5 6 7 8 9 inline fun <reified T:Activity> Context.startActivity () { val intent = Intent(this , T::class .java) startActivity(intent) } final_one.setOnClickListener { context?.startActivity<FinalActivity>() }
可以看到,使用实化参数之后,FinalActivity 的类型在运行是得以保留,因此可以通过扩展函数获取到他对应的 Java Class 的类型来构建 Intent 。
10 注解与反射 注解定义与使用 1 annotation class Test (val path: String, val level: Int , val since: SinceKotlin)
在 kotlin 中定义注解。比 Java 中要简介一些。val 比不可少,同时也可以在注解中使用其注解比如这里的 SinceKotlin 作为参数)
1 2 @Test("/user/fly/documents" , level, SinceKotlin("1.3.0" ) )val dir = "home"
这里需要注意的是,注解参数的值是编译期决定的,因此在使用时,要么直接使用字面量,要么比如使用 const val 定义的值,val 也不行,比如这里的 level 必须在类的最外层定义 const val level = 100
,要么直接使用 100。
在 Kotlin 中可以直接在表达式上面使用注解,比如@Suppress 这种,范围就可以进一步的缩小,不必为此单独写一个方法。同时 Kotlin 中注解默认的 @Retention 是 Runtime ,而在 Java 中则是 Class。
更多细节可以参考 JKid 这个 Kotlin 实现的 JSON 序列化和反序列化的库。
反射 在 Kotlin 中使用反射,可以用两种 API
java.lang.reflect 中 java 的 API(因为 kotlin 代码只是编译成了普通的 Java 字节码,这个是完全兼容的)
kotlin.reflect 中 kotlin 的 API 。有意思的是,使用这个库的 API 做反射,并不限定有 kotlin 类,可以访问任何 JVM 语言写成的类。
在 Android 中,kotlin 反射的库并没有被默认依赖,如果需要使用时,要单独添加依赖 org.jetbrains.kotlin:kotlin-reflect
。