前言
在前几篇的基础上, 大家如果认真的阅读, 并跟着思路实践的话, 应该可以收获很多的, 前面基本已经覆盖了 Kotlin 语言中常见的使用方法, 下面让我们来进一步, 在前面的基础上深深的扩展一下
1.Kotlin 的技术拓展其一
尽管到目前为止, 我们已经讲了很多关于 Kotlin 的新技术, 但远远是不够的, 让我们进一步了解更多的 Kotlin 的新知识
1.1 数据结构与集合
1.1.1 数据结构
所谓的数据结构, 就是将对象中的数据解析成相应的独立变量, 也就是脱离原来的对象存在
- data class Person(var name:String, var age :Int,var salary:Float)
- var person = Person("Bill",30,120f)
- var (name,age,salary)=person // 数据解构
- Log.i("tag",name+age+salary)
输出
Bill20120
有很多的对象, 可以保存一组值, 并可以通过 for...in 的语句, 解构出值
- var map = mutableMapOf<Int,String>()
- map.put(10,"Devin")
- map.put(20,"Max")
- for ((key,values) in map){
- Log.d("tag",key.toString() +";;;;"+values)
- }
输出
- 10;;;;Devin
- 20;;;;Max
- // 其中这些对象都是通过数据类实现的, 当然我们自己也可以实现的, 这里就不做展示了, 自己可以下去试试
1.1.2 集合
尽管 Kotlin 可以使用 JDK 中提供的集合, 但 Kotlin 标准库也提供了自己的集合, 与之不同的是, Kotlin 提供的集合分为可修改和不可修改的, 这一点和 Apple 的 CocoaTouch 类似. 在 Kotlin 只读包括 LIst,Set,Map; 可写的包括 MutableList,MutableSet,MutableMap 等
- public interface List<out E> : Collection<E> {
- ...
- }
- public interface Set<out E> : Collection<E> {
- ...
- }
- public interface Map<K, out V> {
- ...
- }
很显然上面的都是 out 修饰的, 前面学的 out 声明, 泛型如果使用了, 那么该泛型就能只用于读操作
- val nums = mutableListOf<Int>(1,2,3)
- var reNums :List<Int> = nums;
- nums.add(4)// 可以增加; reNums 只能读取
从这个代码可以看出, 集合并没有提供构造器创建集合对象, 提供了一些函数来创建
- listOf; setOf; mapOf; mutableListOf; mutableSetOf; mutableMapOf
- val nums = mutableListOf<Int>(1,2,3)
- var toList = nums.toList()// 通过此方法可以把读写的专为只读的
- var toMutableList = toList.toMutableList()// 只读的也可以转为读写的
1.2 范围值
1.2.1 值范围的应用
值范围表达式用 rangTo 函数实现, 该函数的操作形式是(..), 相关的操作符 in 和! in
- var n =20
- if(n in 1..10){
- Log.d("tag","满足条件")
- }
- if (n !in 30..80){
- Log.d("tag","满足条件")
- }
整数的值范围 (IntRange,LongRange,CharRange) 还有一种额外的功能, 就是可以对这些值范围进行遍历. 编译器会负责将这些代码转换为 Java 中基于下标的 for 循环, 不会产生不必要的性能损耗
- for(i in 1..10){
- Log.i("tag",i.tostring())
- }
- // 相当于 Java 中的
- //for(int i=1; I<=10;i++)
- for(i in 10..1){
- // 如果按照倒序的话, 什么都不会输出的
- Log.i("tag",i.tostring())
- }
- // 但是非要按照倒序输出, 只要使用标准库中的 downTo 函数就可以了
- for(i in 10 downTo 1){
- Log.i("tag",i*i) // 输出 100 到 1 共 10 个数
- }
- // 在前面的代码中, i 的顺序加 1 或减 1, 也就步长为 1; 如果要是改变步长的话, 可以使用 step 函数
- for(i in 1..10 step 2){
- Log.i("tag",i.toString())
- }
输出: 1,3,5,7,9
- // 在前面的代码, 使用的范围都是闭区间, 要是这种的形式[1,10)
- for(i in 1 until 10){
- // 不包含 10 的
- Log.i("tag",i.toString())
- }
1.2.2 常用工具函数
(1)rangTo, 整数类型上定义的 rangTo 操作符, 只是简单地调用 * Rang 类的构造函数
- class Int
- {
- public operator fun rangeTo(other: Int): IntRange
- public operator fun rangeTo(other: Long): LongRange
- }
(2)downTo, 扩展函数可以用于一对整数类型, 下面就是通过扩展函数添加的 downTo 函数
- public infix fun Long.downTo(to: Long): LongProgression {
- return LongProgression.fromClosedRange(this, to, -1L)
- }
- public infix fun Byte.downTo(to: Long): LongProgression {
- return LongProgression.fromClosedRange(this.toLong(), to, -1L)
- }
(3)reversed, 对于每个 * Progression 类都定义了 reversed 扩展函数, 所有的这些函数都会返回相反的数列
- public fun IntProgression.reversed(): IntProgression {
- return IntProgression.fromClosedRange(last, first, -step)
- }
(4)对于每个 * Progression 类都定义了 step 扩展函数, 所有这些函数都会返回使用新的 step 值, 步长值参数要求永远是整数, 因此这个函数不会改变数列遍历的方向
- public infix fun IntProgression.step(step: Int): IntProgression {
- if (!isPositive) throw IllegalArgumentException("Step must be positive, was: $step.")
- return IntProgression.fromClosedRange(first, last, if (this.step> 0) step else -step)
- }
注意: 函数返回的数列 last 值可能与原始数列的 last 的值不同, 这是为了保证(last-first)%increment==0 原则
1.3 类型检查与类型转换
1.3.1 is 与 !is 操作符
- var obj: Any = 234
- if (obj is String) {
- }
- if (obj is Int){
- }
- if (obj !is Int){
- }
如果 is 表达式满足条件, Kotlin 编译器会自动转换 is 前面的对象到后面的数据类型
- var obj: Any = 234
- if (obj is Int){
- obj.rangeTo(4)//Int 类型才有的, 自动转换了
- }
- // 注意的是, 对象 is 后面类型要兼容, 如果不兼容的话, 无法编译通过
- var obj = 234
- if (obj is String) {// 编译不过
- obj.rangeTo(4)
- }
1.3.2 智能类型转换
- var a :Any = "max"
- //&& 的右侧已经转换成了 string
- if (a is String && a.length>0){
- }
- // || 的右侧也已经转换为 string
- if (a !is String ||a.length<0){
- }
- // 这种类型的转换对于 when 和 while 同样有效果的
- var x :Any ="sfs"
- when(x){
- is Int -> Log.i("tag", (x+1).toString())
- is String -> Log.i("tag", x.length.toString())
- }
1.3.3 强行类型转换
如果类型强制转换, 而且类型不兼容, 类型转换操作符通常会抛出一个异常, 称之为不安全的, 而不安全的类型转换使用中缀操作符 as
- var a :Any ="max"
- val x :Int = a as Int //java.lang.ClassCastException
- // 为了避免抛出异常, 我们可以使用安全的类型转换操作符 as?, 当类型转换失败时, 它会返回 null
- var a :Any? =null
- val x : Int? = a as Int?
- Log.i("tag", x.toString())// null
- var a :Any? ="max"
- val x : Int? = a as? Int? //as 后面也要加? 不然还是会抛异常
- Log.i("tag", x.toString())// null
1.3.4 this 表达式
为了访问外层范围内的 this, 我们使用 this@lable, 其中 @lable 是一个标签, 代表 this 所属范围
- class A{
- var A =13
- inner class B{
- fun Int.foo(){
- val a =this@A // 指向 A 的 this
- val b = this@B // 指向 B 的 this
- val c =this// 指向 foo()函数接收者, 一个 Int 值
- val d =this@foo // 指向 foo()函数接收者, 一个 Int 值
- val funLit = {
- s:String ->
- val e = this// 指向 foo()函数接收者, 因为包含当前代码的 Lambda 表达式没有接收者
- }
- }
- }
- }
2.Kotlin 的技术拓展其二
本章将会继续探索 null 值安全性, 异常类, 注解以及反射 #####2.1 null 值安全性 在 Java 中, 经常遇到空指针的困扰, 表脑瓜子疼, 对于这个 Kotlin 使用一些新的语法糖, 会尽可能避免 null 异常带来的麻烦
2.1.1 可为 null 与不可为 null 类型
- var a:String =null // 编译错误, 不能为 null
- var b:String = "abc"
- b=null // 编译错误, 不能为 null
要允许 null 值, 我们可以将变量声明为 null 的字符串类型: String ?
- var a :String ="abcd"
- var b:String? = "abc"
- b =null
- var len = a.length // 由于 a 不允许为 null, 因此不会产生 NPE
- val len1 = b.length // 编译出错, 因为 b 可能为 null
- // 要是必须访问的话, 使用 if 语句进行判断
- var len = if (b==null) -1 else b.length;
- // 第二种就是使用安全调用操作符:?
- print(b?.length) // 输出为 null
- // 当然可以使用在类中的调用
- bob?.depart?.head?.name
- // 这样的链式调用, 只有属性链中任何一个属性为 null, 整个表达式就会返回 null
2.1.3 Elvis 操作符
假设我们有一个可为 null 的引用 r, 我们可以认为: 如果不为空, 就是用, 否则使用其他的值
- // 如果 "?:" 左侧的表达式不是 null,Elvis 操作符就会返回它的值, 否则, 返回右侧表达式的值, 注意, 只有在左侧表达式为 null, 才会计算右侧表达式的值
- var len1 = b?.length ?:-1
在 Kotlin 中, 由于 throw 和 return 都是表达式, 因此可以用在右侧
- var len1 = b?.length ?:throw NullPointerException()
- var len2 = b?.length ?:return
2.1.4 !! 操作符
对于 NPE 的忠实粉丝, 还可以写!!b, 对于 b 不为 null 的情况, 这个表达式会返回一个非 null 的值, 如果是 null, 就会抛出 NPE
var len2 = b!!.length
- #####2.2 异常类 Kotlin 中所有的异常类都是 Throwable 的子类, 要抛出异常, 可以使用 throw 表达式
- // 和 Java 的使用区别不是太大, 这里就不说了
- try { }
- catch (e: NullPointerException) {
- null
- }
- finally {
- }
2.3 注解(Annotations)
注解是用来为代码添加元数据 (metadata) 的一种手段, 要声明一个注解, 需要在类之前添加 annotation 修饰符
annotation class Fancy
注解的其他属性, 可以通过向注解类添加元注解 (meta-annotation) 的方式指定 (1)@Target 指定这个注解可被用于哪些元素(类, 函数, 属性和表达式) (2)@Retention 指定这个注解的信息是否被保存到编译后 class 文件中, 以及在运行时是否可以通过反射访问到它(默认情况下, 这两个设定都是 true) (3)@Repetable 允许在单个元素上多次使用同一注解 (4)@MustBeDoucumented 表示这个注解是公开 API 的一部分, 在自动产生的 API 文档的类或者函数签名中, 应该包含这个注解的信息
- @Target(AnnotationTarget.CLASS ,AnnotationTarget.FUNCTION)
- @Retention(AnnotationRetention.SOURCE)
- @MustBeDocumented
- @Repeatable
- annotation class MyAnnotationClass{
- }
2.3.1 使用注解
注解可以在类, 函数, 函数参数和函数返回值中使用
- @ MyAnnotationClass
- class Foo {
- @ MyAnnotationClass fun bazz(@MyAnnotationClass foo : Int):Int{
- return (@MyAnnotationClass l)
- }
- }
- // 如果需要对一个类的主构造器加注解, 那么必须在主构造器声明中添加 constructor 关键字, 然后在这个关键字之前添加注解
- class Foo @MyAnnotationClass constructor(n:Int){
- // ...
- }
2.3.2 注解类的构造器
注解类可以拥有带参数的构造器
annotation class Special(val why :String)
使用 Special("example") class Foo{}
并不是所有类型的参数都允许在注解类的构造器中使用, 注解构造器只允许使用下面类型的参数 (1)与 Java 基本类型对应的数据类型 (Int,Long) (2)String (3) 枚举类 (4)KClass (5)其他注解类
2.4 反射(Reflection)
尽管 Kotlin 是基于 JVM 的编程语言, 但在 Kotlin 中使用反射, 需要引用额外的库(kotlin-reflect.jar)
2.4.1 类引用
val c = MyClass::class 类引用是一个 KClass 类型的值
注意, Kotlin 的类引用不是一个 Java 的类引用, 要得到 Java 的类引用, 可使用 KClass 对象实例的 java 属性
val c =MyClass::class.java
2.4.2 枚举类成员
反射最常用的功能之一就是枚举的成员, 如类的属性, 方法等.
- class Person(val name :String,val num :Int){
- fun process(){
- }
- }
- var c = Person :: class
- // 获取 Person 类中所有的成员列表(属性和函数)
- println("成员数:" +c.members.size)
- for(member in c.members){
- // 输出每个成员的名字和返回类型
- print(member.name +"" +member.returnType)
- println()
- }
- // 获取 Person 类中所有属性的个数
- println("属性个数:" +c.memberProperties.size)
- // 枚举 Person 类中所有的属性
- for(property in c.memberProperties){
- // 输出当前属性的名字和返回类型
- print(property.name+""+property.returnType)
- println()
- }
- // 获取 Person 类中所有函数的个数
- println("函数个数:"+c.memberFunctions.size)
- for(function in c.memberFunctions){
- // 输出当前函数的名字和返回类型
- println(function.name+" " +function.returnType)
- }
执行这段代码, 会输出如下内容
成员数: 6
- num kotlin.Int
- value kotlin.String
- process kotlin.Unit
- equals kotlin.Boolean
- hashCode kotlin.Int
- toString kotlin.String
属性个数: 2
- num kotlin.Int
- value kotlin.String
函数个数: 4
- process kotlin.Unit
- equals kotlin.Boolean
- hashCode kotlin.Int
- toString kotlin.String
2.4.3 动态调用成员函数
反射的另外一个重要应用就是可以动态调用对象的成员, 如成员函数, 成员函数, 成员属性, 所谓的动态调用, 就是根据成员名字进行调用, 可以动态指定成员的名字, 通过:: 操作符, 可以直接返回类的成员
- class Person(val name:String ,val num:Int){
- fun process(){
- println("name:${value} num:${num}")
- }
- }
- // 获取 process 函数对象
- var p = Person::process
- // 调用 invoke 函数执行 process 函数
- p.invoke(person("abc",20))
- // 利用 Java 的反射机制指定 process 方法名字
- var method = Person::class.java.getMethod("process")
- // 动态调用 process 函数
- method.invoke(Person("Bill",30))
输出:
- name : abc num: 20
- value : Bill num: 30
2.4.4 动态调用成员属性
Kotlin 类的属性与函数一样, 也可以使用反射动态调用, 不过 Kotlin 编译器在处理 Kotlin 类属性时, 会将器转换为 getter 和 setter 方法, 而不是与属性同名的 Java 字段.
- class Person
- {
- var name :String = "Devin"
- get() = field
- set(v){
- field = v
- }
- }
很明显, name 属性变成了 getName 和 setName 方法, 因此, 在使用反射技术访问 Kotlin 属性时, 仍然需按成员函数处理, 如果使用 Java 的反射技术, 仍然要使用 getMethod 方法获取 getter 和 setter 方法对象, 而不能使用 getField 方法获取字段
- class Person
- {
- var name :String = "Devin"
- get() = field
- set(v){
- field = v
- }
- }
- var person = Person()
- // 获得属性对象
- var name = Person::name
- // 读取属性值
- println(name.get(person))
- // 设置属性值
- name.set(person,"Mike")
- println(name.get(person))
- // 无法使用 getField 方法获得 name 字段值, 因为根本就没生成 name 字段, 只有 getName 和 setName 方法
- var field = Person::class.java.getField("name")
- field.set(person,"Json")
- println(field.get(person))
- // 利用 Java 反射获取 getName 方法
- var getName = Person::class.java.getMethod("getName")
- // 利用 Java 反射获取 SetName 方法, 注意, getMethod 方法的第 2 个参数可变的
- // 需要传递 setName 参数类型的 class
- // 这里不能指定 Kotlin 中的 String, 而要指定 java.lang.String
- var setName = Person::class.java.getMethod("setName",java.lang.String().javaClass)
- // 动态设置 name 属性的值
- setName.invoke(person,"John")
- // 动态获取 name 属性的值
- println(getName.invoke(person))
总结
通过这一篇, 更深入的了解 kotlina 的更多新的知识, 以及语法糖, 和 Java 区别还是比较大的, 这篇讲的, 实际开发中很是有用的, 可能将的还是有限的, 毕竟一些知识, 还得在实践的更深入的掌握
来源: https://juejin.im/post/5c05426e5188255362444d16