团购网站短信平台,网站建设计划表模板,南宁专业网站建设公司,wordpress 黄聪ajax变量声明
val—声明只读变量var—声明可读写变量
在kotlin中 val 声明的是只读变量#xff0c;但是不是常量#xff0c;这个说法比较有意思#xff0c;和java有区别#xff0c;比如#xff0c;val声明一个变量#xff0c;可以定义它的get方法#xff1a;
class X {v…变量声明
val—声明只读变量var—声明可读写变量
在kotlin中 val 声明的是只读变量但是不是常量这个说法比较有意思和java有区别比如val声明一个变量可以定义它的get方法
class X {val b: Intget() {return (Math.random() * 100).toInt()}
}它并不是一个常量要定义一个真正意义上的常量必须使用const, const只能修饰基本类型且必须初始化
object KotlinVars2 {//编译时常量const val b 3
}
这样定义了一个编译时常量等价于java的 static final int
val跟java的final一样可以不指定初始值但是必须要在后面的某个地方初始化它 val c: Intif (a 3) {c 4} else {c 5}建议尽可能使用 val 来声明不可变引用让程序的含义更加清晰稳定。
kotlin基本数据类型 变量声明 var a: Int 2val b: String Hello Kotlin特点是冒号后面跟上类型
变量声明—类型自动推导 var a 2val b Hello Kotlin这种方式可以省略类型由kotlin自动推导
长整型写法必须以大写 L 结尾与java不同
// val c 12345678910l // compile error.val c 12345678910L // okFloat是后面加小写的f, 不写f的小数就是Double类型的: val d 3.0 // Double, 3.0f Floatval float1: Float 1f与java不同kotlin中所有类型转换必须显示调用方法 val e: Int 10//val f: Long e // implicitness not allowedval f: Long e.toLong()
字符串支持模板变量引用 val j I❤️Chinaprintln(Value of String j is: $j) // no need bracketsprintln(Length of String j is: ${j.length}) // need bracketsSystem.out.printf(Length of String j is: %d\n, j.length)比较引用比较值 val k Today is a sunny day.val m String(Today is a sunny day..toCharArray())println(k m) //比较引用println(k m) //比较值val n !doctype htmlhtmlheadmeta charsetUTF-8/titleHello World/title/headbodydiv idcontainerH1Hello World/H1pThis is a demo page./p/div/body/html.trimIndent()println(n)集合相关
集合类型 集合框架的创建 只有可变的才能添加删除元素 val intList: ListInt listOf(1, 2, 3, 4) //不可变list 不能添加删除val intList2: MutableListInt mutableListOf(1, 2, 3, 4)//可变list 可以添加删除val map: MapString, Any mapOf(name to benny, age to 20)val map2: MapString, Any mutableMapOf(name to benny, age to 20)集合遍历 //集合遍历 for-ifor (i in 0..10) {println(i)}//集合遍历 for-infor (e in list) {println(e)}var i 0while (i 10) {println(i)}do {println(Hello)} while (false) //集合遍历 for-eachlist.forEach {if (it 2) returnforEach //continue操作println(it)}集合遍历可以直接通过 或 - 号来添加/删除元素 //直接仿java的创建方式也可以val stringList ArrayListString()for (a in a..z){stringList.add(char: $a)}for (a in stringList){println(item${a})}//kotlin集合添加元素可以直接等价于add方法for (i in 0 .. 10){stringList num: $i}//kotlin集合删除元素可以直接-等价于remove方法for (i in 0 .. 10){stringList - num: $i}与java不同可以直接通过[ ]下标索引赋值取值 stringList[5] HelloWorldval valueAt5 stringList[5]map也可以直接用[ ]来存取 val hashMap HashMapString, Int()hashMap[Hello] 10println(hashMap[Hello])特殊集合类型Pair表示一对值 Triple表示一个三值集合 val pair Hello to Kotlinval pair Pair(Hello, Kotlin)val first pair.firstval second pair.secondval (x, y) pairval triple Triple(x, 2, 3.0)val first triple.firstval second triple.secondval third triple.thirdval (x, y, z) triple数组类型 kotlin中的数组类型跟java相对应其中基本类型和装箱类型的写法也是不一样的装箱类型的都带有泛型参数基本类型直接是一个类
数组的创建 数组的长度 val a IntArray(5)println(a.size) //same with the Collections(e.g. List)val b ArrayListString()println(b.size)比java好的地方不需要.length和.size区分了集合长度也是.size
引用数组值 val d arrayOf(Hello, World)d[1] Kotlinprintln(${d[0]}, ${d[1]})数组遍历 val e floatArrayOf(1f, 3f, 5f, 7f)//普通遍历for (element in e) {println(element)}println()//遍历支持箭头函数e.forEach { element -println(element)}println()//一个参数可以省略箭头 it是某个元素e.forEach {println(it)}println()
显然forEach的遍历方式最方便
判断某个元素是否在数组中 if(1f in e){println(1f exists in variable e)}判断某个元素不在数组中 if(1.2f !in e){println(1.2f not exists in variable e)}区间类型
kotlin中的区间类型java没有 开区间 倒序区间 区间步长 可数区间 即离散区间才能打印出来连续区间是不能打印的 //闭区间val intRange 1..10 // [1, 10]val charRange a..zval longRange 1L..100Lval floatRange 1f .. 2f // [1, 2]val doubleRange 1.0 .. 2.0//打印可数的区间 离散区间println(intRange.joinToString())//对于不可数的区间这样不能打印出来 连续区间println(floatRange.toString())可数的离散区间才支持步长设置 //可数的离散区间才支持步长设置val intRangeWithStep 1..10 step 2val charRangeWithStep a..z step 2val longRangeWithStep 1L..100L step 5println(intRangeWithStep.joinToString())println()//半闭半开区间val intRangeExclusive 1 until 10 // [1, 10)val charRangeExclusive a until zval longRangeExclusive 1L until 100Lprintln(intRangeExclusive.joinToString())println()//倒序区间val intRangeReverse 10 downTo 1 // [10, 9, ... , 1]val charRangeReverse z downTo aval longRangeReverse 100L downTo 1Lprintln(intRangeReverse.joinToString())println()遍历离散区间跟数组一样 for (element in intRange) {println(element)}intRange.forEach {println(it)}判断一个值是否在连续区间中 if (3.0 in doubleRange) {println(3.0 in range doubleRange)}if (3.0 !in doubleRange) {println(3.0 not in range doubleRange)}判断一个值是否在离散区间中 if (12 !in intRange) {println(12 not in range intRange)}if (12 in intRange) {println(12 in range intRange)}kotlin中的for-i循环便捷写法 val array intArrayOf(1, 3, 5, 7)for(i in array.indices){println(array[i])}//麻烦点的写法for (i in 0 until array.size) { println(array[i])}集合、数组、区间这三种类型他们的遍历和判断是否在集合中都是一样的方法使用for-in语法。
函数定义 其中函数返回值为Unit可以省略 即void类型省略跟java也是一样的。
函数的引用
函数引用的写法感觉比较奇怪它是函数名前面加两个冒号函数的引用类似C语言中的函数指针可用于函数传递。 左边冒号后面的函数类型可以写也可以省略掉简写 其中等号右侧冒号前面有类名的是类对象的方法引用在调用的时候也要传对象实例才行 val foo Foo();h(foo, qq, 1);变长参数kotlin中函数变长参数类型使用vararg 修饰
fun multiParameters(vararg ints: Int){println(ints.contentToString())
}这时ints实际上是一个IntArray可以进行遍历操作等。这个变长参数类型很奇怪看上去是var和arg两个单词的合并。
listOf(1,2,3) //listOf也是通过变长参数实现的函数默认参数
fun defaultParameter(x: Int, y: String, z: Long 0L){TODO()
}
//默认参数省略
defaultParameter(5, Hello)如果默认参数不是最后一个必须使用具名参数
fun defaultParameter(x: Int 5, y: String, z: Long 0L){TODO()
}
defaultParameter(y Hello)函数参数可以是另一个函数
fun test(p: (Foo, String, Long) - Any){//p(Foo(), Hello, 3L)//调用
}val x Foo::bar
test(x)多参数返回值:
fun multiReturnValues(): TripleInt, Long, Double {return Triple(1, 3L, 4.0)
}val (a, b, c) multiReturnValues() val r a bval r1 a c实际就是返回了一个Triple对象它可以持有多个值而已
表达式
在kotlin里面分支判断都是表达式而不是语句在java中是语句这个是kotlin与java的最大不同 var c: Intif (a 3) {c 4} else {c 5}//可以直接表达式赋值c if (a 3) 4 else 5由于是表达式所以分支判断都可以直接赋值为一个变量c if (a 3) 4 else 5因此kotlin里面没有三目运算符是因为本身表达式就支持。类似的when、try-catch全部可以赋值 c when (a) {0 - 51 - 100else - 20}var x: Any Any()c when {x is String - x.lengthx 1 - 100else - 20}c when(val input readLine()){null - 0else - input.length}val b 0try {c a / b}catch (e: Exception){e.printStackTrace()c 0}c try {a / b} catch (e: ArithmeticException){2} catch (e: Exception) {e.printStackTrace()0}c try {a / b} catch (e: ArithmeticException){2} catch (e: Exception) {e.printStackTrace()0}这样可以直接赋值确实比java方便了许多when代替了java的switch-case写法也更简洁了
运算符重载
kotlin支持运算符重载类似Ckotlin中的 、、、[]、包括函数调用符号 () 都是kotlin中内置好的重载运算符参考https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators 在IDE当中点击对应的符号可以直接跳转到对应的实现。
运算符重载例子复数运算操作
import java.lang.IndexOutOfBoundsException//复数
class Complex(var real: Double, var image: Double){override fun toString() $real ${image}i
}operator fun Complex.plus(other: Complex): Complex {return Complex(this.real other.real, this.image other.image)
}operator fun Complex.plus(other: Double): Complex {return Complex(this.real other, this.image)
}operator fun Complex.plus(other: Int): Complex {return Complex(this.real other, this.image)
}operator fun Complex.minus(real: Double): Complex {return Complex(this.real - real, this.image)
}operator fun Complex.get(index: Int): Double when(index){0 - this.real1 - this.imageelse - throw IndexOutOfBoundsException()
}fun main() {val c1 Complex(3.0, 4.0)val c2 Complex(2.0, 2.0)println(c1 2.0)println(c1 c2)println(c1 3)println(c1 - 3.0)println(c1[0])println(c1[1])println(c1[2])
}运算符重载例子2实现字符串的加减乘除
operator fun String.minus(right: Any?) this.replaceFirst(right.toString(), )operator fun String.times(right: Int): String {return (1..right).joinToString(){ this }
}operator fun String.div(right: Any?): Int {val right right.toString()return this.windowed(right.length, 1, transform {it right}).count { it }
}fun main() {val value HelloWorld Worldprintln(value - World)println(value * 2)val star *println(* * 20)println(value / 3)println(value / l)println(value / ld)
}
重载运算符的定义特点就是类定义扩展方法方法名和运算符对应的描述可以到前面的网址上查然后方法前面加 operator 关键字。
定义hascode和euqals方法
class Person(val age: Int, val name: String){override fun equals(other: Any?): Boolean {val other (other as? Person)?: return falsereturn other.age age other.name name}override fun hashCode(): Int {return 1 7 * age 13 * name.hashCode()}
}与java一样如果是添加到哈希一类的数据结构中必须重写equals和hascode方法如果equals方法判断相同则认为是同一个对象。 val persons HashSetPerson()(0..5).forEach {persons Person(20, Benny)}println(persons.size) //打印出1lambda表达式
kotlin里面的lambdas表达式是一个匿名函数的语法糖因此它的类型其实就是对应的函数类型java中的lambdas表达式其实是接口类型的一个语法糖二者不同 //函数类型是 () - Unitval func: () - Unit fun() {println(Hello) //最后一行是函数的返回值}val lambda: () - Unit {println(Hello)}//函数类型是 Int - Stringval f1 { p: Int -println(p)Hello //最后一行是函数的返回值}println(f1(1))kotlin中lambda表达式中最后一行的类型就是函数的返回类型
中缀表达式 kotlin中比较奇怪的一种写法 val map mutableMapOf(Hello to 2,World to 3)2 to 32.to(3)println(HelloWorld rotate 5)infix fun String.rotate(count: Int): String {val index count % lengthreturn this.substring(index) this.substring(0, index)
}本质还是类的扩展方法前面加 infix 关键字可能是为了实现更加语义化的书写方式 class Bookclass Deskinfix fun Book.on(desk: Desk){}val book Book()val desk Desk()book on desk
高阶函数
高阶函数简单来说就是函数的参数可传递另一个函数常见于forEach表达式 val intArray IntArray(5){ it 1 }intArray.forEach {println(it)}intArray.forEach(::println)intArray.forEach {println(Hello $it)}例子定义一个函数打印方法的耗时
fun cost(block: () - Unit) {val start System.currentTimeMillis()block()println(${System.currentTimeMillis() - start}ms)
}//返回值是一个lambda表达式也是高阶函数
fun fibonacci(): () - Long {var first 0Lvar second 1Lreturn {val next first secondval current firstfirst secondsecond nextcurrent}
}调用 cost {val fibonacciNext fibonacci()for (i in 0..10) {println(fibonacciNext())}}显然在java中这样是做不到的必须每个方法里面前后去打印一下才行。。
内联函数
添加inline关键字的函数标记为内联函数内联函数的特点是代码会被直接插入到调用处编译的代码反编译结果就是代码直接插入到调用处的效果。内联函数效率高一些。 val intArray IntArray(5){ it 1 }intArray.forEach {println(it)}forEach在kotlin中的实现就是内联函数
public inline fun IntArray.forEach(action: (Int) - Unit): Unit {for (element in this) action(element)
}高阶函数搭配内联函数使用更加高效 inline fun cost(block: () - Unit) {val start System.currentTimeMillis()block()println(System.currentTimeMillis() - start)}cost {println(Hello)}编译后等价于 val start System.currentTimeMillis()println(Hello)println(System.currentTimeMillis() - start)内联函数的返回 val ints intArrayOf(1, 2, 3, 4)ints.forEach {if(it 3) returnforEachprintln(Hello $it)}其实是跳过3并不是返回等价于 for (element in ints) {if(element 3) continueprintln(Hello $element)}nonLocalReturn 返回调用的方法下面直接返回main方法
inline fun nonLocalReturn(block: () - Unit){block()
}fun main() { nonLocalReturn {return}}禁止non-local-return使用crossinline 关键字
public inline fun IntArray.forEach(crossinline action: (Int) - Unit): Unit {for (element in this) action(element)
}内联属性
var pocket: Double 0.0
var money: Doubleinline get() pocketinline set(value) {pocket value}内联函数的限制
内联函数只能访问对应类的 public 成员内联函数的参数不能被存储赋值给变量内联函数的参数只能传递给其他内联函数参数
扩展函数
标准库中的常用扩展函数let、run、also、apply、use fun main() {val person Person(benny, 20)person.let(::println)person.run(::println)val person2 person.also {it.name hhh //it是当前的对象}val person3 person.apply {name xxx //this是当前对象}File(build.gradle).inputStream().reader().buffered().use {println(it.readLines())}
}在IDE中点击打开 use 方法的实现源码
InlineOnly
RequireKotlin(1.2, versionKind RequireKotlinVersionKind.COMPILER_VERSION, message Requires newer compiler version to be inlined correctly.)
public inline fun T : Closeable?, R T.use(block: (T) - R): R {contract {callsInPlace(block, InvocationKind.EXACTLY_ONCE)}var exception: Throwable? nulltry {return block(this)} catch (e: Throwable) {exception ethrow e} finally {when {apiVersionIsAtLeast(1, 1, 0) - this.closeFinally(exception)this null - {}exception null - close()else -try {close()} catch (closeException: Throwable) {// cause.addSuppressed(closeException) // ignored here}}}
}可以发现它基本上实现了我们java的try-catch-finally结构包括最终流它也默认帮你关闭了你都不需要关心了可以说用起来真的省事多了IO读写再也不用那么麻烦了比java又臭又长的写法真是方便太多
集合变换操作符
集合的变换操作 val list listOf(1, 2, 3, 4)
list.filter { it % 2 0 }
list.flatMap {0 until it}.joinToString().let(::println)list.asSequence().flatMap {(0 until it).asSequence()}.joinToString().let(::println)val newList list.flatMap {ArrayListString(it)} sequence的概念 sequence类似java8里面的stream流或者RxJava里面的流概念 val list listOf(1, 2, 3, 4)list.asSequence().filter {println(filter: $it)it % 2 0}.map {println(map: $it)it * 2 1}.forEach {println(forEach: $it)}上面代码list调用asSequence后每个元素会依次调用filter和map, 不加asSequence每个元素会先调用filter再调用map。 加asSequence最后不加forEach的话不会有输出不加asSequence的话去掉forEach也会有输出。即加上asSequence变成了流一样。
asSequence被称为懒序列使用asSequence性能会优化一些因为每个元素只需要走一遍每个操作而不是每个操作中将每个元素走一遍。
集合的聚合操作 sum val list listOf(1, 2, 3, 4)val s list.sum()println(list.sum() $s)//10val list listOf(1, 2, 3, 4)
//acc是上次的计算结果初始值为StringBuffer()返回值跟初始值类型一样
val foldStrBuf list.fold(StringBuffer()){acc, i - acc.append(i)}println(list.fold() $foldStrBuf)//1234reduce val list listOf(1, 2, 3, 4)val r list.reduce() { acc, i -acc i}println(list.reduce() $r)//10
fold和reduce有点递归的意思在里面每次的结果都是基于上次的结果。
zip val list listOf(1, 2, 3, 4)val array arrayOf(2, 2)val z list.zip(array) { a: Int, b: Int -a * b}z.forEach {println(it)} // 2 4val array2 arrayOf(x, y)val z2 list.zip(array2) { a: Int, b: String -$a$b}z2.forEach {println(it)} // 1x 2y看源码zip其实就是将两个集合遍历执行某个操作只不过最终集合大小是以最小长度的那个集合为准
public inline fun T, R, V IterableT.zip(other: Arrayout R, transform: (a: T, b: R) - V): ListV {val arraySize other.sizeval list ArrayListV(minOf(collectionSizeOrDefault(10), arraySize))var i 0for (element in this) {if (i arraySize) breaklist.add(transform(element, other[i]))}return list
}集合变换应用例子
统计文本文件中非空格字符出现的次数
import java.io.Filefun main() {File(build.gradle).readText() // 1. read file.toCharArray() // 2.//.filter{ !it.isWhitespace() } // 3. filter white space.filterNot(Char::isWhitespace) // 等价上面一行.groupBy { it } //分组.map {it.key to it.value.size}.let {println(it)}
}SAM转换 Java 的 SAM: Kotlin 的 SAM: Kotlin的匿名内部类 val executor: ExecutorService Executors.newSingleThreadExecutor()//匿名内部类的写法executor.submit(object : Runnable {override fun run() {println(run in executor.)}})//匿名内部类简写executor.submit(Runnable {println(run in executor.)})//匿名内部类简写executor.submit { println(run in executor.) }kotlin中SAM目前只支持只有一个方法的java接口
fun submitRunnable(runnable: Runnable){runnable.run()
}submitRunnable {println(Hello)
}kotlin中SAM不支持只有一个方法的kotlin接口, 但是可以直接定义一个函数参数 下面这样写法是不行的
interface Invokable {fun invoke()
}fun submit(invokable: Invokable) {invokable.invoke()
}
//报错
submit {println(Hello)
}下面这样写法是可行的
typealias FunctionX ()-Unit
//函数参数传递一个lambda表达式
fun submit(block: FunctionX){block()
}
//等价这种直接传lambda表达式的写法
//fun submit(()-Unit){
//
//}//这样是可以的submit {println(Hello啊啊啊)}SAM转换支持对比
一个例子添加和移除监听的正确kotlin写法:
public class EventManager {interface OnEventListener {void onEvent(int event);}private HashSetOnEventListener onEventListeners new HashSet();public void addOnEventListener(OnEventListener onEventListener){this.onEventListeners.add(onEventListener);}public void removeOnEventListener(OnEventListener onEventListener){this.onEventListeners.remove(onEventListener);}
}使用上面的java类
fun main() {val eventManager EventManager()//匿名内部类的写法val onEvent EventManager.OnEventListener { event - println(onEvent $event) }//等价上面的写法val onEvent2 object : EventManager.OnEventListener{override fun onEvent(event: Int) {println(onEvent $event)}}// DO NOT use this. //错误的写法这样还是一个函数类型传到removeOnEventListener方法里不能移除// 还是会调用方法创建一个对象
// val onEvent3 { event: Int -
// println(onEvent $event)
// }eventManager.addOnEventListener(onEvent)eventManager.removeOnEventListener(onEvent)
}DSL: 领域特定语言
如sql语言、gradle中的groovy语言等kotlin可以方便的实现这些语言的写法
例子: 通过拼接操作生成一个html文件
import java.io.Fileinterface Node {fun render(): String
}class StringNode(val content: String): Node {override fun render(): String {return content}
}class BlockNode(val name: String): Node {val children ArrayListNode()val properties HashMapString, Any()override fun render(): String {return $name ${properties.map { ${it.key}${it.value} }.joinToString( )}${children.joinToString(){ it.render() }}/$name}operator fun String.invoke(block: BlockNode.()- Unit): BlockNode {val node BlockNode(this)node.block()thisBlockNode.children nodereturn node}operator fun String.invoke(value: Any) {thisBlockNode.properties[this] value}operator fun String.unaryPlus(){thisBlockNode.children StringNode(this)}}fun html(block: BlockNode.() - Unit): BlockNode {val html BlockNode(html)html.block()return html
}fun BlockNode.head(block: BlockNode.()- Unit): BlockNode {val head BlockNode(head)head.block()this.children headreturn head
}fun BlockNode.body(block: BlockNode.()- Unit): BlockNode {val head BlockNode(body)head.block()this.children headreturn head
}fun main() {//变量后面跟东西相当于传递一个lambda表达式val htmlContent html {head {meta { charset(UTF-8) } //字符串后面跟东西相当于运算符重载 invoke}body {div {style(width: 200px; height: 200px; line-height: 200px; background-color: #C9394A;text-align: center.trimIndent())span {style(color: white;font-family: Microsoft YaHei.trimIndent())Hello HTML DSL!!}}}}.render()File(Kotlin.html).writeText(htmlContent)
}这个例子主要有两点
一个是如果是变量后面跟东西相当于传递一个lambda表达式那定义的时候其实就是定义一个高阶函数来实现二是如果字符串后面跟东西相当于运算符重载 invoke跟{}相当于参数是一个ambda表达式跟()就是普通参数定义String类的扩展函数即可实现。 operator fun String.invoke(block: BlockNode.()- Unit): BlockNode {val node BlockNode(this)node.block()thisBlockNode.children nodereturn node}operator fun String.invoke(value: Any) {thisBlockNode.properties[this] value}Hello HTML DSL!! 这种也是字符串的运算符重载 operator fun String.unaryPlus(){thisBlockNode.children StringNode(this)}字符串前面后面跟操作符好像基本都是运算符重载
另外扩展方法中如果想访问除了自身以外的其他Receiver的话只需将扩展方法定义到对应的类内部即可如上面的String相关扩展方法直接定义到了BlockNode类的内部就可以引用BlockNode类的成员属性来使用了。
Kotlin中的几个特殊符号 ( ‘?.‘ ‘?:‘ ‘!!‘ ‘as?‘ ‘?‘ ) 含义
?.安全调用符
if (foo ! null){return foo.bar()
}else{return null
}?: as? !! 类构造器 init 块 构造器内省写var的属性可以防止类内初始化类似java中的大括号初始化该初始化会跟构造函数一起执行。init块中可以直接访问到构造方法中的参数。init块可以有多个可以分开写init块最终会合并执行。 副构造函数后面可以指定调用主构造函数或其他副构造函数类似java的构造函数重载但是kotlin不推荐定义很多副构造函数会增加复杂度还是推荐定义一个主构造器。
主构造器默认参数 构造同名的工厂函数函数名也可以与类的名字不同工厂方法比构造函数更具有表现力因为它可以通过名字知道类是如何构造出来的
class Person(var age: Int, var name: String unknown) {override fun equals(other: Any?) (other as? Person)?.name?.equals(name) ?: falseoverride fun hashCode() name.hashCode()
}val persons HashMapString, Person()
//函数名也可以与类的名字不同
fun Person(name: String): Person {return persons[name] ?: Person(1, name).also { persons[name] it }
}类似String有常见的工厂方法调用
fun main() {val str String()//调用的是构造函数val str1 String(charArrayOf(1, 2))//调用的是工厂函数 实际是一个函数指向了String的构造函数
}可以自己给系统String类添加工厂方法一般写成函数名跟类名一样
fun String(ints: IntArray): String {return ints.contentToString()
}其实就是一个函数返回类型为对应的类
类的可见性 与java不同kotlin中啥也不写默认就是public的而java中不写默认是default包内可见kotlin中多一个限制可见性的internal关键字去掉了default关键字对于protected, java是包内可见而kotlin是类内可见这点不同当然子类肯定都是可见的kotlin中protected 只能用来修饰类的成员不能用来修饰类和顶级声明 internal VS default internal这个关键字比较有意思可以在kotlin中用作模块化隔离可见性
比如在一个模块中声明
//internal只在模块内可见模块外的kotlin访问不到
internal class CoreApiKotlinA {//指定JvmName使java也不能访问该方法JvmName(%abcd)internal fun a(){println(Hello A)}
}而在依赖它的模块中使用它会报错 //kotlin中访问其他模块的internal的类会报错 不可见val coreApiKotlinA CoreApiKotlinA()coreApiKotlinA.a()但是通过java代码却可以使用此时可以通过internal 方法上添加JvmName(xxx)注解指定一个非法的java变量可达到java不能调用的目的但实际上java是能看到的只不过不能打出来合法的方法名而已。 //java是可以访问internal的kotlin类的但是可以指定JvmName使java也不能访问对应方法CoreApiKotlinA coreApiKotlinA new CoreApiKotlinA(); coreApiKotlinA.%abcd();构造器的可见性 属性的可见性 get可见性必须和属性可见性一致, 不能定义public属性的get为privateset可见性不能大于属性的可见性, 不能定义private属性的set为public
顶级声明的可见性
顶级声明指文件内直接定义的属性、函数、类等顶级声明不支持 protected顶级声明被 private 修饰表示文件内可见
延迟初始化方案 lateinit 的注意事项
lateinit 会让编译器忽略变量的初始化不支持 Int 等基本数据类型开发者必须能够在完全确定变量值的生命周期下使用 lateinit不要在复杂的逻辑中使用lateinit它只会让你的代码更加脆弱
使用 lazy: lazy是比较推荐的延迟初始化方式实际上它是一个属性代理
接口代理 接口代理其实就是可以把一些没必要实现的接口方法隐藏起来不去实现方便一些而不用每一个接口都要写一下。其中by关键字右边的就是实际代理类对象它是构造函数中的一个属性by关键字左边的是代理类对象实现的接口。
例子
//超级数组 同时支持list和map接口通过接口代理的方式不必实现list和map接口的所有方法
class SuperArrayE(private val list: MutableListE? ArrayList(),private val map: MutableMapAny, E HashMap()
) : MutableListE? by list, MutableMapAny, E by map {override fun isEmpty() list.isEmpty() map.isEmpty()override val size: Intget() list.size map.sizeoverride fun clear() {list.clear()map.clear()}override operator fun set(index: Int, element: E?): E? {if (list.size index) {repeat(index - list.size 1) {list.add(null)}}return list.set(index, element)}override fun toString(): String {return List: [$list]; Map: [$map]}
}fun main() {val superArray SuperArrayString()val superArray2 SuperArrayString()superArray HellosuperArray[Hello] WorldsuperArray2[superArray] WorldsuperArray[1] worldsuperArray[4] !!!println(superArray)println(superArray2)
}
属性代理 - lazy lazy属性代理 代理了Person实例的firstName的getter方法实际是一个函数 传递一个lambda表达式
class Person(val name: String){//lazy属性代理 代理了Person实例的firstName的getter方法// 实际是一个函数 传递一个lambda表达式val firstName by lazy {name.split( )[0]}val lastName by lazy {name.split( )[1]}
}observable代理属性监听set值变化
class StateManager {//observable代理属性监听set值变化var state: Int by Delegates.observable(0) {property, oldValue, newValue -println(State changed from $oldValue - $newValue)}
}
自定义代理属性
class Foo {val x: Int by X()var y: Int by X()
}class X {operator fun getValue(thisRef: Any?, property: KProperty*): Int {return 2}operator fun setValue(thisRef: Any?, property: KProperty*, i: Int) {}
}其中getValue和setValue方法的参数写法是固定的
调用
fun main() {val stateManager StateManager()stateManager.state 3stateManager.state 4println(Foo().x)
}自定义实例读取Config.properties中的配置项
Config.properties文件中一般是key-value的配置
authorxxxx
version1.0
descThis is a demo.class PropertiesDelegate(private val path: String, private val defaultValue: String ){private lateinit var url: URLprivate val properties: Properties by lazy {val prop Properties()url try {javaClass.getResourceAsStream(path).use {prop.load(it)}javaClass.getResource(path)} catch (e: Exception) {try {ClassLoader.getSystemClassLoader().getResourceAsStream(path).use {prop.load(it)}ClassLoader.getSystemClassLoader().getResource(path)!!} catch (e: Exception) {FileInputStream(path).use {prop.load(it)}URL(file://${File(path).canonicalPath})}}prop}operator fun getValue(thisRef: Any?, property: KProperty*): String {return properties.getProperty(property.name, defaultValue)}operator fun setValue(thisRef: Any?, property: KProperty*, value: String) {properties.setProperty(property.name, value)File(url.toURI()).outputStream().use {properties.store(it, Hello!!)}}
}abstract class AbsProperties(path: String){protected val prop PropertiesDelegate(path)
}class Config: AbsProperties(Config.properties){var author by propvar version by propvar desc by prop
}fun main() {val config Config()println(config.author)config.author Bennyhuoprintln(config.author)
}其实主要还是实现getValue和setValue方法就可以需要注意的是签名写法setValue最后一个参数是设置的值的类型而getValue的返回值就是对应获取的值的类型。 operator fun getValue(thisRef: Any?, property: KProperty*): String {}operator fun setValue(thisRef: Any?, property: KProperty*, value: String) {}只要定义好了getValue和setValue方法然后就可以通过by操作符去代理了
使用形式var 变量 by 【实现getValue和setValue的类】的实例()
Kotlin单例
只需要类前面添加object关键字即可object定义类等价于java的恶汉式的单例模式
object Singleton {var x: Int 2 fun y(){ }init {//object不能定义构造函数但可以定义init块}
}object不能定义构造函数但可以定义init块
使用
fun main() {Singleton.xSingleton.y()
}JvmStatic 和 JvmField
object Singleton {JvmField var x: Int 2 //生成java的静态成员 不会生成getter和setter方法JvmStatic fun y(){ } //生成java的静态方法
}静态成员 JvmStatic object修饰的类内部方法相当于静态方法但是伪静态也就是内部会生成一个静态的成员对象对类的方法调用实际上是调用的内部静态成员对象的方法只有在方法上添加JvmStatic才会真正的生成静态方法。
不生成 gettter 和 setter JvmField 普通kotlin类(非单例)中使用使用JvmField和JvmStatic
class Foo {//普通类中使用JvmField和JvmStaticcompanion object {JvmField var x: Int 2JvmStatic fun y(){ }}
}注意加的注解JvmField和JvmStatic只针对java平台的可用其他平台并不可行。
单例的object类仍可以继承类
object Singleton: Runnable{override fun run() {}
}内部类 在kotlin中类内部的class前面不写修饰符默认就是 静态内部类class前面写 inner修饰符才是java中的 普通内部类与java一样普通内部类会持有外部类的对象引用。
class Outer {//普通内部类 与java一样会持有外部类的引用inner class Inner//默认静态内部类class StaticInner
}fun main() {val inner Outer().Inner()val staticInner Outer.StaticInner()
}object类内部的object类默认是静态的 不存在非静态的情况 不能定义成inner
object OuterObject {//内部object默认是静态的 不存在非静态的情况 不能定义成innerobject StaticInnerObject
}匿名内部类
fun main() {//匿名内部类object : Cloneable {}
}其实就是object 省略类名直接实现接口。
匿名内部类 可用继承多个接口java不行
fun main() {// 类型是混合类型Cloneable Runnable val runnableCloneable object : Cloneable, Runnable {override fun run() {}}}实现多个接口时它的类型是多个接口类型的混合类型。
Local class :
fun main() {//本地函数fun localFunc(){println(Hello)}//对应Java的local classclass LocalClass: Cloneable, Runnable{override fun run() {}}
}可以对比java的local class实现其实就是在静态函数的内部定义一个类:
public class JavaInnerClasses {public static void main(String... args) {class LocalClass implements Cloneable, Runnable {Overridepublic void run() { }}}
}说实话写了这么多年代码未曾这么干过。。
数据类
kotlin中提供一个data关键字data修饰的类就是一个数据类对标java的bean类
data class Book(val id: Long, val name: String, val author: Person)
data class Person(val id: Long, val name: String, val age: Int)与java的bean类相比kotlin的data类不能被继承并且属性要全部写到构造函数当中没有无参的构造函数。确实简便了许多 并且编译器会为data类生成了一些好用的方法 val book Book(0, Kotlin in Action, Person(1, Dmitry, 40))//编译器生成的方法 copy component1book.copy()val id book.component1()val name book.component2()val author book.component3()其中copy() 和 component1()等都是编译器自动生成的component方法的意义是方便解构赋值的使用 // 解构赋值对应的字段是通过component方法获取的val (id, name, author) book关于解构 //解构val pair Hello to Worldval (hello, world) pair自己实现解构方法
class Student(var id: Int, var name: String, var sex: Char) {// component 不能写错operator fun component1(): Int idoperator fun component2(): String nameoperator fun component3(): Char sexoperator fun component4(): String KT Study OK
}fun main() {val stu Student(4545, Derry, M)val (n1, n2, n3, n4) stu
}Kotlin的数据类内部其实会生成上面的三个component方法
data class User(val id: Int, val name: String, val sex: Char)fun main() {val u User(1, Derry, M)val (v1, v2, v3) u.copy()println(v1: $v1, v2: $v2, v3: $v3)
}例如上面代码翻译后就会看到 data 不能被继承那么为啥不能有子类呢
可以先看一下kotlin为data类生成的对应的java类是什么样的查看方式doule shift键然后Actions中输入kotlin Bytecode显示 点击Decompile即可查看对应生成的java代码 以下是Book的数据类对应生成的java代码
public final class Book {private final long id;NotNullprivate final String name;NotNullprivate final Person author;public final long getId() {return this.id;}NotNullpublic final String getName() {return this.name;}NotNullpublic final Person getAuthor() {return this.author;}public Book(long id, NotNull String name, NotNull Person author) {Intrinsics.checkNotNullParameter(name, name);Intrinsics.checkNotNullParameter(author, author);super();this.id id;this.name name;this.author author;}public final long component1() {return this.id;}NotNullpublic final String component2() {return this.name;}NotNullpublic final Person component3() {return this.author;}NotNullpublic final Book copy(long id, NotNull String name, NotNull Person author) {Intrinsics.checkNotNullParameter(name, name);Intrinsics.checkNotNullParameter(author, author);return new Book(id, name, author);}// $FF: synthetic methodpublic static Book copy$default(Book var0, long var1, String var3, Person var4, int var5, Object var6) {if ((var5 1) ! 0) {var1 var0.id;}if ((var5 2) ! 0) {var3 var0.name;}if ((var5 4) ! 0) {var4 var0.author;}return var0.copy(var1, var3, var4);}NotNullpublic String toString() {return Book(id this.id , name this.name , author this.author );}public int hashCode() {long var10000 this.id;int var1 (int)(var10000 ^ var10000 32) * 31;String var10001 this.name;var1 (var1 (var10001 ! null ? var10001.hashCode() : 0)) * 31;Person var2 this.author;return var1 (var2 ! null ? var2.hashCode() : 0);}public boolean equals(Nullable Object var1) {if (this ! var1) {if (var1 instanceof Book) {Book var2 (Book)var1;if (this.id var2.id Intrinsics.areEqual(this.name, var2.name) Intrinsics.areEqual(this.author, var2.author)) {return true;}}return false;} else {return true;}}
}除了类名方法名前面添加了final关键字不允许继承以外还生成了许多方法其中有重写hashCode()和equals()方法所以如果有一个类继承了data类可能导致属性变化从而导致hashCode()和equals()方法的结果不一致。
如果有子类的话会发生什么
如何合理的使用 data class : data类的属性最好全部为基本类型或者其他data类型保持它的纯净性。
另外有方法可以破除data类的不可继承性也有网友在吐槽kotlin的这个设计觉得它不好其实如果你想用一个可以继承到类只需要把data关键字去掉建一个普通类就好了。kotlin这样设计肯定是想保持它的纯洁性如果可继承只会变的更复杂。
枚举类
kotlin里面的枚举类跟java差不多
enum class State {Idle, Busy
}//枚举定义构造函数 同java
enum class State1(val id: Int) {Idle(0), Busy(1)
}enum class Color {White, Red, Green, Blue, Yellow, Black
}fun main() {State.Idle.name // IdleState.Idle.ordinal // 0val state State.Idle//枚举全部值val value when (state) {State.Idle - { 0 }State.Busy - { 1 }}//枚举创建区间val colorRange Color.White .. Color.Greenval color Color.Blue //Blue不在区间内println(color in colorRange)
}枚举类不可继承其他类因为枚举类有父类是enum但是可以实现接口
enum class State: Runnable{Idle, Busy{override fun run() {println(For Busy State.)}};override fun run() {println(For Every State.)}
}fun main() {State.Idle.run()State.Busy.run()
}枚举类可以定义扩展函数
fun State.successor(): State? {return State.values().let {if (ordinal 1 it.size) nullelse it[ordinal 1]}
}fun State.predecessor(): State? {return State.values().let {if (ordinal - 1 0) nullelse it[ordinal - 1]}
}fun main() {println(State.Idle.successor())println(State.Busy.successor())
}密封类
密封类是一种特殊的抽象类密封类的子类必须定义在与自身相同的文件中密封类的子类的个数是有限的
其实就是一个只能在同一个文件中定义子类的抽象类。定义方式是在类的前面加sealed关键字
sealed class PlayerStateobject Idle : PlayerState()class Playing(val song: Song) : PlayerState() {fun start() {}fun stop() {}
}class Error(val errorInfo: ErrorInfo) : PlayerState() {fun recover() {}
}完整的示例控制播放器播放状态的例子
data class Song(val name: String, val url: String, var position: Int)
data class ErrorInfo(val code: Int, val message: String)object Songs {val StarSky Song(Star Sky, https://fakeurl.com/321144.mp3, 0)
}sealed class PlayerStateobject Idle : PlayerState()class Playing(val song: Song) : PlayerState() {fun start() {}fun stop() {}
}class Error(val errorInfo: ErrorInfo) : PlayerState() {fun recover() {}
}class Player {var state: PlayerState Idlefun play(song: Song) {this.state when (val state this.state) {Idle - {Playing(song).also(Playing::start)}is Playing - {state.stop()Playing(song).also(Playing::start)}is Error - {state.recover()Playing(song).also(Playing::start)}}}
}fun main() {val player Player()player.play(Songs.StarSky)
}注意其中的when表达式的使用可见它可以用来替代枚举类做类似的功能子类的个数也是全部可枚举的。跟枚举类有相似之处。
密封类 VS 枚举类
内联类 内联类的概念
内联类是对某一个类型的包装内联类是类似于 Java 装箱类型的一种类型内联类编译器会尽可能使用被包装的类型进行优化
内联类的限制
主构造器必须有且仅有一个只读属性不能定义有 backing-field 的其他属性被包装类型不能是泛型类型不能继承父类也不能被继承
简而言之内联类是一种类型的包装类类前面加inline关键字构造器只能有一个参数不能继承类不能被继承不能作为内部类。类似于java的Integer、Double这种但有不同。
内联类虽然不能继承类或被继承但是可以实现接口。
//只能有方法不能有属性
inline class BoxInt(val value: Int): ComparableInt {override fun compareTo(other: Int) value.compareTo(other)operator fun inc(): BoxInt {return BoxInt(value 1)}
}BoxInt会做编译优化只有在需要的时候才使用包装类 多数情况下直接使用Int可以反编译字节码生成的java代码查看。另外还有个特点就是它只能有方法不能有属性。
内联类也可以用来模拟枚举类与枚举相比内存占用小但使用方式类似
inline class State(val ordinal: Int) {companion object {val Idle State(0)val Busy State(1)}fun values() arrayOf(Idle, Busy)val name: Stringget() when (this) {State.Idle - IdleState.Busy - Busyelse - throw IllegalArgumentException()}
}inline class Color(val value: UInt) {companion object {val Red Color(0xFFFF0000u)val Green Color(0xFF00FF00u)val Blue Color(0xFF0000FFu)}fun values() arrayOf(Red, Green, Blue)val name: Stringget() when (this) {Red - RedGreen - GreenBlue - Blueelse - throw IllegalArgumentException()}
}
fun main() {State.BusyColor.Bluevar boxInt BoxInt(5)if(boxInt 10){println(value is less than 10)}val newValue boxInt.value * 200println(newValue)boxIntprintln(boxInt)
}typealias VS inline class:
Json解析
Kotlin中解析Json有那么几种方式
GsonMoshikotlinx.serialization
其中Gson是原来经常使用的Google的解析库用这个在kotlin中使用已经不适合会有很多情况不支持如空类型 默认值等。然后就是moshi这个是square出品的还有就是Kotlin自带的kotlinx.serialization不过kotlin自带的居然不支持java类。显然对于android开发者来说如果你使用kotlin一般都是java和kotlin混合使用的所以首选的还是moshi这个库。如果是纯kotlin的话还是选官方自带的比较好。
框架对比
这里补充一个比较不错的库klaxon 这个也是用来解析json的kotlin库貌似支持的功能也比较丰富后面有空再详细了解一下吧。这里先看一下moshi。
moshi的话使用可以看官网https://github.com/square/moshi 这里有一篇更详细的中文使用介绍新一代Json解析库Moshi使用及原理解析
这里主要简单记录一下moshi的使用方式首先gradle需要添加依赖
dependencies {...//moshiimplementation(com.squareup.moshi:moshi:1.11.0)kapt(com.squareup.moshi:moshi-kotlin-codegen:1.11.0)
}还需要应用kotlin的apt插件否则kapt()方法找不到
apply plugin: kotlin-kaptmoshi官方提供了两种方式一是反射方式二是通过注解处理器可以选其一也可以都用。我上面的依赖是采用的注解处理器的方式因为采用反射方式的话需要引入一个额外的依赖库
implementation(com.squareup.moshi:moshi-kotlin:1.11.0)但是使用反射库会依赖导入一个2.5M大小的jar包这么大。。这还得了所以我们肯定不会选这种方式了。。。
然后就是代码只需要在data class上面添加注解
JsonClass(generateAdapter true)
data class Person(val name: String, val age: Int 18)序列化和反序列化 val moshi Moshi.Builder().build()val jsonAdapter moshi.adapter(Person::class.java)val json jsonAdapter.toJson(Person(张三, 25))//val json jsonAdapter.toJson(Person(张三))Log.e(TAG, json : ${json}) //{name:张三, age: 25}val person jsonAdapter.fromJson({name:张三})Log.e(TAG, name: ${person?.name}) //张三Log.e(TAG, age: ${person?.age}) //18还有很多高级的用法具体参考官网介绍。
上面的data类给定了默认值moshi在序列化和反序列化时是识别这个值的如果没有给定值就采用默认值。
moshi更好的支持空类型安全如果把上面的数据类的默认参数去掉
JsonClass(generateAdapter true)
data class Person(val name: String, val age: Int)//这行会直接报空类型异常val person jsonAdapter.fromJson({name:张三})Log.e(TAG, name: ${person?.name})Log.e(TAG, age: ${person?.age})moshi进行json反序列化为KClass时如果filed是Nullable类型则可以填入Null如果是NonNull类型则在填入Null时会立即抛出异常将NPE风险暴露在早期如果是java的话则会到调用 person.name 的时候才会暴露。
也就是说如果后台接口少返回了我们定义的data类中的非空类型的属性字段在生成对象的时候就会报异常而不是具体使用的时候。
moshi还支持属性延时初始化
JsonClass(generateAdapter true)
data class PersonWithInits(val name: String, val age: Int){val firstName by lazy {name.split( )[0]}//Transientval lastName name.split( )[1]
}fun main(){val moshi Moshi.Builder().build()val jsonAdapter moshi.adapter(PersonWithInits::class.java)println(jsonAdapter.toJson(PersonWithInits(Hello world, 18))) // {name:Hello world,age:18}val personWithInits jsonAdapter.fromJson({name:Hello world,age:20, lastName:tom})println(personWithInits?.firstName) // Hello println(personWithInits?.lastName) // world firstName和lastName是以name为准
}最后记录一个AS插件 JsonToKotlinClass 类似于原来的GsonFormat插件可以类似的根据Json字符串生成Kotlin的 data class。
泛型 泛型约束
fun T : ComparableT maxOf(a: T, b: T): T {return if (a b) a else b
}fun main() {val max maxOf(Hello, World)println(max) //输出World
}多个约束
多个泛型参数 多个泛型参数可以每个泛型都有约束
fun T, R callMax(a: T, b: T): Rwhere T : ComparableT, T : () - R, //T有两个约束R : Number { //R有一个约束return if (a b) a() else b()
}类似的还有Map类它的K 和V泛型都进行了约束
class MapK, V where K : Serializable, V : ComparableV协变 协变点 协变点举例 协变小结
子类 Derived 兼容父类 Base生产者 ProducerDerived 兼容 ProducerBase存在协变点的类的泛型参数必须声明为协变或不变当泛型类作为泛型参数类实例的生产者时用协变
简而言之就是用out关键字修饰的泛型就是协变 返回值为协变泛型类型的称为协变点。
例子
interface Bookinterface EduBook : Bookclass BookStoreout T : Book {fun getBook(): T {TODO()}
}fun covariant(){val eduBookStore: BookStoreEduBook BookStoreEduBook()val bookStore: BookStoreBook eduBookStoreval book: Book bookStore.getBook() //书店只能获取普通的书val eduBook : EduBook eduBookStore.getBook() //教辅书店只能获取教辅书籍
}逆变 逆变点 逆变小结
子类 Derived 兼容父类 Base生产者 ProducerBase 兼容 ProducerDerived存在逆变点的类的泛型参数必须声明为逆变或不变当泛型类作为泛型参数类实例的消费者时用逆变
简而言之就是用in关键字修饰的泛型就是逆变作为函数输入参数的泛型称为逆变点。逆变点主要是指输入的参数类型是消费者。并且消费者的继承关系跟协变是相反的。
例子
open class Wasteclass DryWaste : Waste()class Dustbinin T : Waste {fun put(t: T) {TODO()}
}fun contravariant(){val dustbin: DustbinWaste DustbinWaste()val dryWasteDustbin: DustbinDryWaste dustbinval waste Waste()val dryWaste DryWaste()//普通垃圾桶可以放入任何垃圾dustbin.put(waste)dustbin.put(dryWaste)//干垃圾桶只能放入干垃圾不能放普通垃圾
// dryWasteDustbin.put(waste)dryWasteDustbin.put(dryWaste)
}星投影
* 可用在变量类型声明的位置* 可用以描述一个未知的类型* 所替换的类型在协变点返回泛型参数上限类型在逆变点接收泛型参数下限类型
协变点 星投影在所有逆变点的下限类型是Nothing, 因此不能用在属性或函数上。
星投影的适用范围 说白了只是一个描述符可以简写泛型参数而已。
fun main() {val queryMap: QueryMap*, * QueryMapString, Int()queryMap.getKey()queryMap.getValue()val f: Function*, * FunctionNumber, Any()//f.invoke()if (f is Function) {(f as FunctionNumber, Any).invoke(1, Any())}maxOf(1, 3)HashMapString, List*()//endregionval hashMap: HashMap*, * HashMapString, Int()//hashMap.get()}class QueryMapout K : CharSequence, out V : Any {fun getKey(): K TODO()fun getValue(): V TODO()
}fun T : ComparableT maxOf(a: T, b: T): T {return if (a b) a else b
}class Functionin P1, in P2 {fun invoke(p1: P1, p2: P2) Unit
}泛型擦除伪泛型
泛型实现原理 泛型实现对比 Java与Kotlin实现机制一样在运行时擦除真正的类型C#则会真的生成一个类型去执行。
内联特化 内联特化在调用的地方会替换到调用处因此这时类型是确定的了即已经特化成某个具体类型。通过fun前面的关键字 inline 和泛型参数T前面的 reified 参数两个来指定泛型参数在调用处实例化。
内联特化实际应用 inline fun reified T genericMethod(t: T){//val t T()val ts ArrayT(3) { TODO() }val jclass T::class.javaval list ArrayListT()if(list is List*){println(list.joinToString())}
}class Person(val age: Int, val name: String)inline fun reified T Gson.fromJson(json: String): T fromJson(json, T::class.java)fun main() {val gson Gson()val person2: Person gson.fromJson({age:18,name:Bennyhuo})val person3 gson.fromJsonPerson({age:18,name:Bennyhuo})
}实例模仿的Self Type
typealias OnConfirm () - Unit
typealias OnCancel () - Unitprivate val EmptyFunction {}open class Notification(val title: String,val content: String
)class ConfirmNotification(title: String,content: String,val onConfirm: OnConfirm,val onCancel: OnCancel
) : Notification(title, content)interface SelfTypeSelf {val self: Selfget() this as Self //当前类型强转成Self类型
}//泛型添加约束只能传子类
open class NotificationBuilderSelf: NotificationBuilderSelf: SelfTypeSelf {protected var title: String protected var content: String fun title(title: String): Self {this.title titlereturn self //返回接口的常量属性即可运行时就是当前子类实际类型}fun content(content: String): Self {this.content contentreturn self}open fun build() Notification(this.title, this.content)
}class ConfirmNotificationBuilder : NotificationBuilderConfirmNotificationBuilder() {private var onConfirm: OnConfirm EmptyFunctionprivate var onCancel: OnCancel EmptyFunctionfun onConfirm(onConfirm: OnConfirm): ConfirmNotificationBuilder {this.onConfirm onConfirmreturn this}fun onCancel(onCancel: OnCancel): ConfirmNotificationBuilder {this.onCancel onCancelreturn this}override fun build() ConfirmNotification(title, content, onConfirm, onCancel)
}fun main() {ConfirmNotificationBuilder().title(Hello).onCancel {println(onCancel)}.content(World).onConfirm {println(onConfirmed)}.build().onConfirm()
}如果不定义SelfType类型则子类在调用ConfirmNotificationBuilder().title(“Hello”)之后不能再继续调用子类的onCancel 方法因为返回的是父类型但是实际运行时这个类型是子类型。
实例 基于泛型实现 Model 实例的注入
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KPropertyabstract class AbsModel {init {Models.run { thisAbsModel.register() }}
}class DatabaseModel : AbsModel() {fun query(sql: String): Int 0
}class NetworkModel : AbsModel() {fun get(url: String): String {code: 0}
}class SpModel : AbsModel() {init {Models.run { register(SpModel2) }}fun hello() println(HelloWorld)
}object Models {private val modelMap ConcurrentHashMapString, AbsModel()fun AbsModel.register(name: String this.javaClass.simpleName) {modelMap[name] this}//String扩展函数fun T: AbsModel String.get(): T {return modelMap[this] as T}
}fun initModels() {DatabaseModel()NetworkModel()SpModel()
}//object单例
object ModelDelegate {operator fun T: AbsModel getValue(thisRef: Any, property: KProperty*): T {return Models.run {property.name.capitalize().get() //利用了String的扩展函数}}
}class MainViewModel {//利用属性代理by by左边指定类型推导泛型val databaseModel: DatabaseModel by ModelDelegateval networkModel: NetworkModel by ModelDelegateval spModel: SpModel by ModelDelegateval spModel2: SpModel by ModelDelegate// val compileKotlin: KotlinCompile by tasks //gradle这种写法类似tasks也是属性代理// val compileTestKotlin: KotlinCompile by tasks
}fun main() {initModels()val mainViewModel MainViewModel()mainViewModel.databaseModel.query(select * from mysql.user).let(::println)mainViewModel.networkModel.get(https://www.imooc.com).let(::println)mainViewModel.spModel.hello()mainViewModel.spModel2.hello()
}反射
反射的依赖
反射常用数据结构 反射常用数据结构Kotlin vs Java 基本上和java是一一对应的其中KType表示的是未擦除的泛型KClass获取的是实际运行时的类型不带泛型参数的。 Kotlin使用反射的话唯一一点不好的就是需要引入一个体积非常大的库
dependencies {// kotlin反射库大小2.5M左右implementation org.jetbrains.kotlin:kotlin-reflect:1.4.20
}没错有2.5M不过编译后的大小还能接受
kotlin获取KClass通过两个冒号
var cls: KClassString String::class
cls.java // 转成java的ClassString
cls.java.kotlin // 再转回到kotlin的KClass
// 获取定义在类中的属性直接写在类当中的
val property cls.declaredMemberProperties.firstOrNull()KClass是不带泛型的类typeOf能拿到具体的泛型实际类型 val mapCls Map::classprintln(mapCls) // 输出class kotlin.collections.Mapval mapType typeOfMapString, Int() // 拿到 KTypemapType.arguments.forEach { // 拿到 KType 中的每个参数泛型的类型println(it) // 输出 kotlin.String 和 kotlin.Int}拿到KClass之后可以通过KClass的方法获取各种其他属性了
一个简单的示例 open class S(val sex : Boolean) {fun superFun() {}
}class A(val name : String, sex: Boolean) : S(sex) {fun String.hello(){}fun foo() {}var age : Int 0;
}fun A.test() {}上面的class A继承了一个类并且类的内部有其他类定义的扩展方法本身也定义了一个扩展方法。
KClass提供了很多方法获取类的属性和方法但是有一些区别方法比较多可以看一下区别
fun main() {// 能得到age, name, foo(), (String.)hello(), equals(), hashCode(), toString(), sex, superFun()println(A::class.members) 获取所有的成员属性和方法包括其他类的扩展方法包括父类的方法但不包括构造方法// 能得到foo(), (String.)hello(), equals(), hashCode(), toString(), superFun()println(A::class.functions) 获取所有的方法, 包括扩展方法包括父类的// 能得到age, name, sexprintln(A::class.memberProperties) 获取所有的成员属性 非扩展 包括父类// 能得到foo(), equals(), hashCode(), toString(), superFun()println(A::class.memberFunctions) 获取所有的成员方法 非扩展 包括父类// 能得到(String.)hello()println(A::class.memberExtensionFunctions) 获取所有的扩展方法 包括父类// 能得到[]println(A::class.memberExtensionProperties) 获取所有的扩展属性 包括父类// 能得到age, nameprintln(A::class.declaredMemberProperties) 获取到所有定义的属性 当前类// 能得到foo()println(A::class.declaredMemberFunctions) 获取到所有定义的方法普通方法非扩展方法非静态方法当前类// 能得到age, name, foo(), (String.)hello()println(A::class.declaredMembers) 获取到所有定义的成员包括属性和方法普通方法和扩展方法当前类// 能得到foo(), (String.)hello()println(A::class.declaredFunctions) 获取到所有定义的方法普通方法和扩展方法 如果是java类可以获取父类的方法// 能得到(String.)hello()println(A::class.declaredMemberExtensionFunctions) 获取定义在当前类的扩展方法// 能得到[]println(A::class.declaredMemberExtensionProperties) 获取定义在当前类的扩展属性
}可以看出以declared开头的方法基本上只能获取当前类的属性和方法不带declared开头的方法则同时可以获取到父类的相关属性和方法。
还有一点需要注意的是这里kotlin里面所指的扩展属性和扩展方法一般是指直接写在当前类中的其他类的扩展方法如上面的A里面的String.hello()方法。如果是A类在某个地方定义的扩展方法是获取不到的如上面的A.test()方法。这点跟java有点不一样。
nestedClasses获取内部类
B::class.nestedClasses//获取内部类objectInstance获取object单例的实例如果不是单例类则返回可能为null B::class.objectInstance?.hello() //获取object实例A::class.objectInstance?.foo() //如果类不是一个object, 则返回null类内部的其他类如何获取外部类的实例对象
class A {fun String.hello(){this 表示当前String对象thisA 表示外部当前的class A对象}
}java也是一样内部类获取外部类的实例时需要通过A.this 获取
获取泛型实参
1.获取接口某个方法的返回值类型的泛型参数
interface Api {fun getUsers(): ListUserDTO
}获取上面 Api 接口的 getUsers() 返回类型的泛型参数类 UserDTO
有几种方式第一种是根据name来比较判断找到对应的方法 //获取到 Api的getUsers() 方法 通过 filterval functions Api::class.declaredFunctions.filter { it.name getUsers }val getUsers : KFunction* functions.get(0)getUsers.returnType.arguments.forEach {println(getUser的返回值泛型${it})}//获取函数的返回值参数的泛型类型UserDTO 通过 firstApi::class.declaredFunctions.first { it.name getUsers }.returnType.arguments.forEach {println(getUser的返回值泛型${it})}还可以直接通过函数引用获取 Api::getUsers 得到的就是一个KFunction Api::getUsers.returnType.arguments.forEach {println(getUser的返回值泛型2${it})}显然这种方式最简单了。
还可以通过java的反射方式来获取 //Api::class.java是获取到对应java的class ClassApi 然后可以调用java的反射方法获取泛型类型Api::class.java.getDeclaredMethod(getUsers).genericReturnType.safeAsParameterizedType()?.actualTypeArguments?.forEach {println(it)}//safeAs是定义的一个Any的扩展方法fun T Any.safeAs(): T? {return this as? T}//safeAs扩展方法可以简写下面的代码等价于上面的代码(Api::class.java.getDeclaredMethod(getUsers).genericReturnType as ParameterizedType).actualTypeArguments?.forEach {println(it)}只能说java的方式也可以但是这种也太麻烦了。。还是全部用kotlin的方法吧不然得各种强转各种判空.
2.获取接口类的泛型
abstract class SuperTypeT {//kotlin反射方法获取val typeParameter by lazy {//this是实际运行子类型, supertypes拿到父类型first是第一个父类型即SuperTypearguments获取到泛型参数列表只有一个可以first(),// first()方法返回的是KTypeProjectionKTypeProjection.type才返回KTypethis::class.supertypes.first().arguments.first().type!!}//java反射方法获取val typeParameterJava by lazy {this.javaClass.genericSuperclass.safeAsParameterizedType()!!.actualTypeArguments.first()}
}open class SubType : SuperTypeString()
获取上面 SubType类实现的SuperType接口类的泛型
val subType SubType()subType.typeParameter.let(::println) // kotlin.StringsubType.typeParameterJava.let(::println) // class java.lang.String java获取的永远是java类型的描述
关键代码就是这句this::class.supertypes.first().arguments.first().type 这里的话主要注意这个this运行时是实际的子类型OO多态所以最后是可以直接强转的。
上面代码是只有一个父类如果有多个父类会有问题需要修改一下
abstract class SuperTypeT {val typeParameter2 by lazy {//实际中如果子类是open可继承的可能还会有子类具体要看使用的类//此时需要找到合适的父类再操作可以根据名字去比较这里示例直接判断不是空的this::class.allSupertypes.first { it.arguments.isNotEmpty() }.arguments.first().type!!//等价上面//this::class.allSupertypes.filter{ it.arguments.isNotEmpty()}.first().arguments.first().type!!}
}open class SubType : SuperTypeString()class SubType2: SubType()
获取上面 SubType2类的父类实现的SuperType接口类的泛型 val subType2 SubType2()
subType2.typeParameter2.let(::println) // kotlin.String
实例为数据类实现 DeepCopy
fun T : Any T.deepCopy(): T {//是数据类data class 才拷贝if(!this::class.isData){return this}//primaryConstructor获取主构造器因为执行到这里的是数据类肯定有主构造器所以!!强转不用判空return this::class.primaryConstructor!!.let {primaryConstructor -primaryConstructor.parameters.map { parameter -//(this::class as KClassT)逆变转协变val value (this::class as KClassT).memberProperties.first { it.name parameter.name } //成员属性名和构造函数的参数名相等.get(this)//classifier先转成KClass然后判断是否是数据类if((parameter.type.classifier as? KClass*)?.isData true){parameter to value?.deepCopy() //如果value是数据类继续调用value的deepCopy()方法深拷贝 递归} else {parameter to value // 如果value不是数据类直接返回(K to V)是返回一个Pair对象}}.toMap() //Pair集合转Map集合.let(primaryConstructor::callBy) //callBy调用构造函数构造对象 callBy需要一个MapKParameter, Any?参数就是当前的map对象}
}调用测试代码
data class Person(val id: Int, val name: String, val group: Group)
data class Group(val id: Int, val name: String, val location: String)fun main() {val person Person(0,hello,Group(0,Kotliner.cn,China))val copiedPerson person.copy()val deepCopiedPerson person.deepCopy()println(person copiedPerson) //falseprintln(person deepCopiedPerson) //falseprintln(person.group copiedPerson.group) //true for shallow copy.println(person.group deepCopiedPerson.group) //falseprintln(deepCopiedPerson)
}上面的例子中主要有几点需要注意的
this::class.isData 判断是否是数据类 data classthis::class.primaryConstructor 获取主构造器因为是数据类一定有主构造器所以可以强转 !!primaryConstructor.parameters let里面调用当前primaryConstructor对象的parameters获取所有的构造器参数this::class as KClassT 逆变转协变否则this.class返回一个协变点out T, 而get()方法接受一个逆变点会报错memberProperties.first { it.name parameter.name } 数据类的特点是构造器的参数名和成员的属性名相等parameter.type.classifier as? KClass* type参数需要调用classifier先转成KClass然后再判断是否是数据类parameter to value?.deepCopy() 如果value是数据类继续调用value的deepCopy()方法深拷贝这里是一个递归调用K to V 是返回的一个 Pair对象.toMap() 将Pair集合转Map集合.let(primaryConstructor::callBy) 调用主构造器callsBy是KCallable接口的方法KFunction是KCallable的子类因此所有的KFunction都可以调用callBycallBy接受一个map参数正好就是let前面返回的结果。
这个例子有一个完整的开源库代码 KotlinDeepCopy 是由大神Bennyhuo所写的但是貌似目前没什么issue, 慎用可以当案例学习一下。
实例实现 Model 映射
这个例子是实现一个拷贝工作将一个对象里的字段赋值给另一个对象里面的同名字段跟深拷贝的例子有点相似
//任意对象转其他对象成员属性名相同
inline fun reified From : Any, reified To : Any From.mapAs(): To {//所有成员转成Map集合再调用下面的Map转对象方法即可return From::class.memberProperties.map { it.name to it.get(this) }.toMap().mapAs()
}//Map转对象成员属性名相同 默认只处理数据类
inline fun reified To : Any MapString, Any?.mapAs(): To {//primaryConstructor反射调用主构造器return To::class.primaryConstructor!!.let {it.parameters.map {parameter -// this[parameter.name] 有可能为null, 如果目标对象To的构造器参数类型可以接受null类型就直接返回一个null 否则抛异常parameter to (this[parameter.name] ?: if(parameter.type.isMarkedNullable) nullelse throw IllegalArgumentException(${parameter.name} is required but missing.))}.toMap().let(it::callBy)//callBy调用主构造器构造出一个To类型的对象}
}第一个方法的实现实际上是调用第二个方法的所以只需实现第二个方法即可这里依然是先获取主构造器To::class.primaryConstructor获取了主构造器之后拿到它的参数列表进行map操作map里面依然是返回当前参数 parameter to valueto 操作符左边的是To对象的也就是目标对象 to 操作符右边的是当前调用.mapAs的map对象因此通过this[parameter.name]访问它里面的同名参数的value值但是这个值可能为null, 不为null就返回 ?: 左边它自身为null还需一个处理就是如果To类型即目标类的构造函数的这个当前参数可接受可空类型 就直接传null, 否则抛异常。 调用测试代码
data class UserVO(val login: String, val avatarUrl: String)data class UserDTO(var id: Int,var login: String,var avatarUrl: String,var url: String,var htmlUrl: String
)fun main() {val userDTO UserDTO(0,world,https://ccccccc,https://ddddddddd,https://eeeeeeeeeee)val userVO: UserVO userDTO.mapAs()println(userVO)val userMap mapOf(id to 0,login to hello,avatarUrl to https://aaaaaaa,url to https://bbbbbbbb)val userVOFromMap: UserVO userMap.mapAs()println(userVOFromMap)
}
实例可释放对象引用的不可空类型
这个例子主要是模仿了一个Android当中释放bitmap对象赋值为null的场景在kotlin当中如果你定义了一个 var bitmap: Bitmap, 然后在onDestroy方法里面将其置为null, 但是这样写bitmapnull是不行的因为定义的时候是一个不可空类型这就矛盾了。
fun T : Any releasableNotNull() ReleasableNotNullT()class ReleasableNotNullT: Any: ReadWritePropertyAny, T {private var value: T? nulloverride fun getValue(thisRef: Any, property: KProperty*): T {return value ?: throw IllegalStateException(Not initialized or released already.)}override fun setValue(thisRef: Any, property: KProperty*, value: T) {this.value value}fun isInitialized() value ! nullfun release() {value null}
}inline val KProperty0*.isInitialized: Booleanget() {//允许反射获取isAccessible true//this.getDelegate()获取属性代理的实例return (this.getDelegate() as? ReleasableNotNull*)?.isInitialized()?: throw IllegalAccessException(Delegate is not an instance of ReleasableNotNull or is null.)}fun KProperty0*.release() {isAccessible true(this.getDelegate() as? ReleasableNotNull*)?.release()?: throw IllegalAccessException(Delegate is not an instance of ReleasableNotNull or is null.)
}class Bitmap(val width: Int, val height: Int)class Activity {private var bitmap by releasableNotNullBitmap()fun onCreate(){//::bitmap省略了this, 默认绑定了当前对象为receiverprintln(this::bitmap.isInitialized)println(this::bitmap.isInitialized)bitmap Bitmap(1920, 1080)println(::bitmap.isInitialized)}fun onDestroy(){println(::bitmap.isInitialized)::bitmap.release()println(::bitmap.isInitialized)}
}fun main() {val activity Activity()activity.onCreate()activity.onDestroy()
}这个例子中主要利用了属性代理然后有两个比较特殊的定义分别实现了属性代理接口KProperty0的扩展属性KProperty0*.isInitialized和扩展方法KProperty0*.release()。
KProperty0表示没有receiver其实是绑定当前调用对象this作为receiver了KProperty1表示有1个receiver, KProperty2表示有2个receiver。
isAccessible true允许反射操作跟java一样也要设置一个accessible为true。 this.getDelegate()获取的是当前属性代理接口的实际代理对象而 this.getDelegate() as? ReleasableNotNull* 这个是转换成实际类型然后调用实际类型的相关属性或方法即可当然这个对象可能为null或者不是一个ReleasableNotNull类型的这时需要抛异常。
注解
注解定义
Retention(AnnotationRetention.RUNTIME)
Target(AnnotationTarget.CLASS)
annotation class Api(val url: String)Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Path(val name: String )Target(AnnotationTarget.FUNCTION)
annotation class Get(val name: String)使用annotation关键字修饰calss前面比java的 interface更人性化其中Retention有三种
public enum class AnnotationRetention {/** Annotation isnt stored in binary output */SOURCE,/** Annotation is stored in binary output, but invisible for reflection */BINARY,/** Annotation is stored in binary output and visible for reflection (default retention) */RUNTIME
}分别表示作用时机是在源码级、编译期、还是运行时跟java基本类似。
Target指定限定标注对象取值如下
public enum class AnnotationTarget {/** Class, interface or object, annotation class is also included */CLASS,/** Annotation class only */ANNOTATION_CLASS,/** Generic type parameter (unsupported yet) */TYPE_PARAMETER,/** Property */PROPERTY,/** Field, including propertys backing field */FIELD,/** Local variable */LOCAL_VARIABLE,/** Value parameter of a function or a constructor */VALUE_PARAMETER,/** Constructor only (primary or secondary) */CONSTRUCTOR,/** Function (constructors are not included) */FUNCTION,/** Property getter only */PROPERTY_GETTER,/** Property setter only */PROPERTY_SETTER,/** Type usage */TYPE,/** Any expression */EXPRESSION,/** File */FILE,/** Type alias */SinceKotlin(1.1)TYPEALIAS
}添加参数
注解类的参数是有限的必须是能在编译期确定的类型。
简单使用
Api(https://api.github.com)
interface GitHubApi {Get(/users/{name})fun getUser(Path name: String): User
}class User内置注解
第一个标注注解的注解主要是指前面的Retention和Target之类的是写在注解类上的注解。 标准库的通用注解 Java虚拟机相关注解
file:JvmName(KotlinAnnotations)
file:JvmMultifileClass
package com.bennyhuo.kotlin.annotations.builtinsimport java.io.IOExceptionVolatile
var volatileProperty: Int 0Synchronized
fun synchronizedFunction(){}val lock Any()
fun synchronizedBlock(){synchronized(lock) {}
}Throws(IOException::class)
fun throwException(){}像 Synchronized Throws注解都是比较好用的替代java的相应关键字比较人性化了。其中 file:JvmName(KotlinAnnotations) 和 file:JvmMultifileClass比较有意思能让多个文件中的kotlin代码最终生成到一个类里面假如还有一个文件如下
file:JvmName(KotlinAnnotations)
file:JvmMultifileClass
package com.bennyhuo.kotlin.annotations.builtinsfun hello(){}那经过编译之后这个文件会和上面的文件合并到一起生成到一个kotlin类文件当中。
实例仿 Retrofit 反射读取注解请求网络
data class User(var login: String,var location: String,var bio: String)Retention(AnnotationRetention.RUNTIME)
Target(AnnotationTarget.CLASS)
annotation class Api(val url: String)Retention(AnnotationRetention.RUNTIME)
Target(AnnotationTarget.CLASS)
annotation class Path(val url: String )Retention(AnnotationRetention.RUNTIME)
Target(AnnotationTarget.FUNCTION)
annotation class Get(val url: String )Retention(AnnotationRetention.RUNTIME)
Target(AnnotationTarget.VALUE_PARAMETER)
annotation class PathVariable(val name: String )Retention(AnnotationRetention.RUNTIME)
Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Query(val name: String )Api(https://api.github.com)
interface GitHubApi {Api(users)interface Users {Get({name})fun get(name: String): UserGet({name}/followers)fun followers(name: String): ListUser}Api(repos)interface Repos {Get({owner}/{repo}/forks)fun forks(owner: String, repo: String)}
}object RetroApi {const val PATH_PATTERN (\{(\w)\})val okHttp OkHttpClient()val gson Gson()val enclosing {cls: Class* -var currentCls: Class*? clssequence {while(currentCls ! null){// enclosingClass获取下一个class// yield将对象添加到正在构建的sequence序列当中currentCls currentCls?.also { yield(it) }?.enclosingClass}}}//内联特化inline fun reified T create(): T {val functionMap T::class.functions.map{ it.name to it }.toMap() //【函数名函数本身】的Pair转成mapval interfaces enclosing(T::class.java).takeWhile { it.isInterface }.toList() //拿到所有接口列表println(interfaces $interfaces)// 输出 [GitHubApi$Users, GitHubApi]//foldRight从interfaces序列的右边开始拼val apiPath interfaces.foldRight(StringBuilder()) {clazz, acc -// 拿到每个接口类的Api注解的url参数值如果url参数为空则使用类名作为url值acc.append(clazz.getAnnotation(Api::class.java)?.url?.takeIf { it.isNotEmpty() } ?: clazz.name).append(/)}.toString()println(apiPath $apiPath) // https://api.github.com/users///动态代理return Proxy.newProxyInstance(RetroApi.javaClass.classLoader, arrayOf(T::class.java)) {proxy, method, args -//所有函数中的抽象函数 即接口的方法functionMap[method.name]?.takeIf { it.isAbstract }?.let {function -//方法的参数val parameterMap function.valueParameters.map {//参数名和参数的值放在一起it.name to args[it.index - 1] //valueParameters包含receiver 因此需要index-1来对应args}.toMap()println(parameterMap $parameterMap) //{namebennyhuo}//{name} 拿到Get注解的参数 如果注解参数不为空就使用注解参数如果为空使用方法名称val endPoint function.findAnnotationGet()!!.url.takeIf { it.isNotEmpty() } ?: function.nameprintln(endPoint $endPoint) //{name}/followers//正则找到endPoint中的所有符合{owner}/{repo}/forks其中{xxx}的结果val compiledEndPoint Regex(PATH_PATTERN).findAll(endPoint).map {matchResult -println(matchResult.groups ${matchResult.groups}) // [MatchGroup(value{name}, range0..5), MatchGroup(value{name}, range0..5), MatchGroup(valuename, range1..4)]println(matchResult.groups1.range ${matchResult.groups[1]?.range}) // 0..5println(matchResult.groups2.value ${matchResult.groups[2]?.value}) // namematchResult.groups[1]!!.range to parameterMap[matchResult.groups[2]!!.value]}.fold(endPoint) {acc, pair -//acc的初始值就是endPoint即{name}/followersprintln(acc ${acc}) // {name}/followersprintln(pair ${pair}) // (0..5, bennyhuo) pair是一个 range to nameacc.replaceRange(pair.first, pair.second.toString()) // 把{name}/followers中的0到5的位置的字符串{name}替换成bennyhuo}println(compiledEndPoint ${compiledEndPoint}) //bennyhuo/followers//拼接api和参数val url apiPath compiledEndPointprintln(url $url)println(*****************)okHttp.newCall(Request.Builder().url(url).get().build()).execute().body()?.charStream()?.use {gson.fromJson(JsonReader(it), method.genericReturnType)//返回json的解析结果}}} as T}
}fun main() {//interface com.bennyhuo.kotlin.annotations.eg.GitHubApi//println(enclosingClass${GitHubApi.Users::class.java.enclosingClass})val usersApi RetroApi.createGitHubApi.Users()val user usersApi.get(bennyhuo)val followers usersApi.followers(bennyhuo).map { it.login }println(user $user)println(followers $followers)
}这个例子还是有点复杂不太好理解有些方法没接触过不知道啥意思这里加了很多打印方法把结果打印输出一下这样能知道具体是代表的啥就好理解一点了。
实例注解加持反射版 Model 映射
这个例子是在前面反射一节实现的model映射例子的基础上通过添加注解方式处理那些字段名称不是相同风格的情况比如两个对象中的avatar_url 和 avatarUrl的相互映射。
//不写默认是RUNTIME
//Retention(AnnotationRetention.RUNTIME)
Target(AnnotationTarget.VALUE_PARAMETER)
annotation class FieldName(val name: String)Target(AnnotationTarget.CLASS)
annotation class MappingStrategy(val klass: KClassout NameStrategy)interface NameStrategy {fun mapTo(name: String): String
}//下划线转驼峰
object UnderScoreToCamel : NameStrategy {// html_url - htmlUrloverride fun mapTo(name: String): String {//先转成字符数组然后fold操作return name.toCharArray().fold(StringBuilder()) { acc, c -when (acc.lastOrNull()) { //上一次的acc不是空_ - acc[acc.lastIndex] c.toUpperCase() //上一次结果的最后一个字符是下划线就把下划线位置替换成当前字符的大写字母else - acc.append(c) // 否则直接拼接}//返回accacc}.toString()}
}//驼峰转下划线
object CamelToUnderScore : NameStrategy {override fun mapTo(name: String): String {//先转成字符数组然后fold操作return name.toCharArray().fold(StringBuilder()) { acc, c -when {c.isUpperCase() - acc.append(_).append(c.toLowerCase()) //如果是大写字母直接拼一个下划线再拼上小写else - acc.append(c)}//返回accacc}.toString()}
}//使用定义的策略注解驼峰转下划线
MappingStrategy(CamelToUnderScore::class)
data class UserVO(val login: String,//FieldName(avatar_url) //这种是单个字段上面添加注解只能一个一个添加val avatarUrl: String,var htmlUrl: String
)data class UserDTO(var id: Int,var login: String,var avatar_url: String,var url: String,var html_url: String
)fun main() {val userDTO UserDTO(0,Bennyhuo,https://avatars2.githubusercontent.com/u/30511713?v4,https://api.github.com/users/bennyhuo,https://github.com/bennyhuo)val userVO: UserVO userDTO.mapAs()println(userVO)val userMap mapOf(id to 0,login to Bennyhuo,avatar_url to https://api.github.com/users/bennyhuo,html_url to https://github.com/bennyhuo,url to https://api.github.com/users/bennyhuo)val userVOFromMap: UserVO userMap.mapAs()println(userVOFromMap)
}inline fun reified From : Any, reified To : Any From.mapAs(): To {return From::class.memberProperties.map { it.name to it.get(this) }.toMap().mapAs()
}inline fun reified To : Any MapString, Any?.mapAs(): To {return To::class.primaryConstructor!!.let {it.parameters.map { parameter -parameter to (this[parameter.name]// let(this::get)等价于let{this[it]} userDTO[avatar_url]?: (parameter.annotations.filterIsInstanceFieldName().firstOrNull()?.name?.let(this::get))// 拿到UserVO类的注解MappingStrategy的kclass即CamelToUnderScore它是一个object calss, objectInstance获取实例然后调用mapTo把avatarUrl转成avatar_url最后调用userDTO[avatar_url]?: To::class.findAnnotationMappingStrategy()?.klass?.objectInstance?.mapTo(parameter.name!!)?.let(this::get)?: if (parameter.type.isMarkedNullable) nullelse throw IllegalArgumentException(${parameter.name} is required but missing.))}.toMap().let(it::callBy)}
}这里如果注解上不写Retention(AnnotationRetention.RUNTIME)默认就是运行时类型。 下面两种写法是等价的
parameter.annotations.filterIsInstanceFieldName()
parameter.findAnnotationFieldName()下面两种写法是等价的
let(this::get)let{this[it]
}mapAs()方法中做了几件事
尝试直接从当前Map中获取To对象的同名参数值尝试从To对象的字段上面的注解来获取需要转换的参数名再根据名字获取Map中的值尝试获取To对象的类注解得到处理类调用处理类方法驼峰转下划线再根据名字获取Map中的值以上大招都没有获取到如果To对象的字段可接受空值就赋值null, 否则就抛异常
驼峰和下划线转换那里稍微有点绕。。
实例注解处理器版 Model 映射 Java编译过程
这个例子会用到一些著名的代码生成库
生成Java代码JavaPoet生成Kotlin代码KotlinPoet
上面两个都是square公司出品的开源库JakeWharton大神的杰作这个例子中主要用到了KotlinPoet 。
dependencies {implementation org.jetbrains.kotlin:kotlin-stdlib-jdk8implementation com.squareup:kotlinpoet:1.4.3implementation com.bennyhuo.aptutils:aptutils:1.7.1implementation project(:apt:annotations)
}注解声明
Retention(AnnotationRetention.BINARY)
Target(AnnotationTarget.CLASS)
annotation class ModelMap这里不需要在运行时保留注解编译就会生成代码了因此使用的是AnnotationRetention.BINARY
注解生成代码
package com.bennyhuo.kotlin.annotations.apt.compilerimport com.bennyhuo.aptutils.AptContext
import com.bennyhuo.aptutils.logger.Logger
import com.bennyhuo.aptutils.types.ClassType
import com.bennyhuo.aptutils.types.asKotlinTypeName
import com.bennyhuo.aptutils.types.packageName
import com.bennyhuo.aptutils.types.simpleName
import com.bennyhuo.aptutils.utils.writeToFile
import com.bennyhuo.kotlin.annotations.apt.ModelMap
import com.squareup.kotlinpoet.*
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy//必须指定注解的类型
SupportedAnnotationTypes(com.bennyhuo.kotlin.annotations.apt.ModelMap)
SupportedSourceVersion(SourceVersion.RELEASE_8)
class ModelMapProcessor: AbstractProcessor() {override fun init(processingEnv: ProcessingEnvironment) {super.init(processingEnv)AptContext.init(processingEnv)}//fun Sample.toMap() mapOf(a to a, b to b)
//fun V MapString, V.toSample() Sample(this[a] as Int, this[b] as String)override fun process(annotations: MutableSetout TypeElement, roundEnv: RoundEnvironment): Boolean {roundEnv.getElementsAnnotatedWith(ModelMap::class.java).forEach {element -element.enclosedElements.filterIsInstanceExecutableElement().firstOrNull { it.simpleName() init }?.let {val typeElement element as TypeElementFileSpec.builder(typeElement.packageName(), ${typeElement.simpleName()}\$\$ModelMap) //$$转义.addFunction(FunSpec.builder(toMap).receiver(typeElement.asType().asKotlinTypeName()).addStatement(return mapOf(${it.parameters.joinToString {${it.simpleName()} to ${it.simpleName()} }}))//mapOf(a to a, b to b).build()).addFunction(FunSpec.builder(to${typeElement.simpleName()}).addTypeVariable(TypeVariableName(V)).receiver(MAP.parameterizedBy(STRING, TypeVariableName(V))).addStatement(return ${typeElement.simpleName()}(${it.parameters.joinToString{ this[${it.simpleName()}] as %T } }), //Sample(this[a] as Int, this[b] as String) %T是模板字符串 用后面的参数替换*it.parameters.map { it.asType().asKotlinTypeName() }.toTypedArray()).build()).build().writeToFile()}}return true}
}这是注解处理器的模块然后新建一个模块来使用它: gradle中必须通过kapt的方式来依赖注解处理器的库
plugins {id javaid org.jetbrains.kotlin.jvmid org.jetbrains.kotlin.kapt
}group com.bennyhuo.kotlin
version 1.0-SNAPSHOTsourceCompatibility 1.8repositories {jcenter()
}dependencies {implementation org.jetbrains.kotlin:kotlin-stdlib-jdk8kapt project(:apt:compiler)implementation project(:apt:annotations)testCompile group: junit, name: junit, version: 4.12
}compileKotlin {kotlinOptions.jvmTarget 1.8
}
compileTestKotlin {kotlinOptions.jvmTarget 1.8
}注意注解处理器仅在编译器起作用而注解库的依赖方式是在运行时也就是会参与编译到字节码中的。
最后使用代码
import com.bennyhuo.kotlin.annotations.apt.ModelMapfun main() {val sample Sample(0, 1)val map sample.toMap();println(map)val sample2 map.toSample2()println(sample2)
}ModelMap
data class Sample(val a: Int, val b: String)//fun Sample.toMap() mapOf(a to a, b to b)
//fun V MapString, V.toSample() Sample(this[a] as Int, this[b] as String)ModelMap
data class Sample2(val a: Int, val b: String)使用前先build一下这样注解处理器会在下面的目录下生成kotlin的代码文件 可以看到每个添加了ModelMap的data类都会生成一个文件 打开看一下内容
package com.bennyhuo.kotlin.annotations.sampleimport kotlin.Int
import kotlin.String
import kotlin.collections.Mapfun Sample.toMap() mapOf(a to a, b to b)fun V MapString, V.toSample() Sample(this[a] as Int , this[b] as String )
package com.bennyhuo.kotlin.annotations.sampleimport kotlin.Int
import kotlin.String
import kotlin.collections.Mapfun Sample2.toMap() mapOf(a to a, b to b)fun V MapString, V.toSample2() Sample2(this[a] as Int , this[b] as String )
基本就是前面期望的结果了
后面有空再把JavaPoet的使用拿出来学习一下吧
kotlin编译器插件对开发者的要求比较高需要熟悉字节码的api。
AllOpen插件的处理逻辑
常见的kotlin提供的插件 kotlin插件还能编译js文件
kotlin 与 java 代码互调的一些注意事项
新建一个Kotlin文件直接在里面写一个方法编译器会生成一个 类名 Kt 结尾的 java 类文件并将该方法生成为该java类的静态方法
// MyUtils.kt
fun show(info: String) {println(info)
}上面代码会生成类似下面Java代码 直接在Java中调用的话找到以 Kt 结尾的Java类进行静态方法调用即可
MyUtilsKt.show(hello);但是如果Kotlin文件中的方法是写在一个类里面的java调用的时候必须创建一个类实例进行调用
// MyUtils.kt
class MyUtils {fun show(info: String) {println(info)}
}// java 代码中调用方式
new MyUtils().show(hello);另外需要注意 in 在 Kotlin 中是 关键字Java 中如果有名字为 in 的变量名在 Kotlin 中使用时需要写成 in 的方式来调用。
Kotlin 和 Java 互调的另一个坑Kotlin 无法判断来自 Java 平台的可空性所以最靠谱的方法是使用一个可空类型来接收来自 Java 的变量。例如Java 中返回值为 String 类型的方法在 Kotlin 中使用时最好使用 var str : String ? 可空类型来接受避免空指针风险。
// java 代码
public class JavaStudent {public static String in hello;public String getName() {return null; // java 返回的 nullkotlin 中是无法感知的}
}// kotlin 代码中调用
fun main() {println(JavaStudent.in)val student JavaStudent()val name : String? student.name // 要用可空类型接受Java方法返回的值println(name?.length)
}Kotlin 中的只读集合并不是真的不可变当跨平台的时候它就可以被其他平台的语言所修改例如当我们在Kotlin中调用下面这个bar方法的时候
fun bar(list: ListInt) {foo(list)
}而这里调用的这个foo方法是用Java的代码定义的
public static ListInt foo(ListInt list) {for (int i 0; i list.size(); i) {list[i] list[i] * 2;}return list;
}所以传入bar方法中的list就会被foo方法改变
val list listOf(1, 2, 3, 4)
bar(list)
println(list)[2, 4, 6, 8]所以当我们与Java进行互操作的时候就要考虑到这种情况。
关于Class类名的传递
如果方法参数的泛型是Java的类必须传 Java类名::class.java如果方法参数的泛型是Kotlin的类直接传 Kotlin类型::class 即可
例如
fun show(clazz: ClassJavaStudent) {}fun show(clazz: KClassKtStudent) {}fun main() {show(JavaStudent::class.java)show(KtStudent::class)
}Kotlin中使用Java的Callback写法
// java 代码
public interface JavaCallback {public void show(String info);
}public class JavaManager {private JavaCallback javaCallback;public void setCallback(JavaCallback callback) {javaCallback callback;}
}// kotlin 代码中调用
fun main() {val javaManager JavaManager()// 第一种写法javaManager.setCallback {println(it)}// 第二种写法javaManager.setCallback(object : JavaCallback {override fun show(info: String?) {println(info)}})// 第三种写法val javaCallback JavaCallback {println(it)}javaManager.setCallback(javaCallback)
}建议使用第一种lambda的写法方式。
而 Kotlin 中使用 Kotlin 的 Callback 写法只能使用object的方式传不能像调用 java 的callback那样灵活
interface KTCallback {fun show(name: String)
}class KTManager {fun setCallback(callback: KTCallback) {callback.show(aaa)}
}fun main() {KTManager().setCallback(object : KTCallback {override fun show(name: String) {println(name)}})// 或者这样val callback object : KTCallback {override fun show(name: String) {println(name)}}KTManager().setCallback(callback)// 但这样写不行// KTManager().setCallback {// println(it)// }
}示例Handler的Callback和Thread都是Java的类可以多种写法
fun main() {Handler(Looper.getMainLooper(), object : Handler.Callback {override fun handleMessage(msg: Message): Boolean {// ...return true}})Handler(Looper.getMainLooper()) {// ...true}Thread {println(这里是Runnbale对象的run方法被执行)}.start()
}一个方法接受到可变参数传给另一个方法时需要前面加上*号
fun foo(vararg students : Student) {foo2(*students)
}fun foo2(vararg students : Student) {}另外需要注意的一点是Java 并不支持主动指定一个函数是否是内联函数所以在 Kotlin 中声明的普通内联函数可以在Java中调用因为它会被当作一个常规函数而用reified来实例化的参数类型的内联函数则不能在 Java 中调用因为它永远是需要内联的。
如何将Kotlin方法与Java方法隔离
通常Java代码中可以直接调用kotlin的方法但是可以通过以下方式使kotlin方法不被Java方法识别只能被kotlin调用
fun 222show() {}
fun 4323545422666() {}fun main() {222show()4323545422666()
}其中 4323545422666 这种写法可以向第三方调用者隐藏函数名意图然后自己找个小本本记录下来 每个数字的含义只有自己人知道具体含义。