在前几篇的基础上,大家如果认真的阅读,并跟着思路实践的话,应该可以收获很多的,前面基本已经覆盖了Kotlin语言中常见的使用方法,下面让我们来进一步,在前面的基础上深深的扩展一下
尽管到目前为止,我们已经讲了很多关于Kotlin的新技术,但远远是不够的,让我们进一步了解更多的Kotlin的新知识
所谓的数据结构,就是将对象中的数据解析成相应的独立变量,也就是脱离原来的对象存在
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 //其中这些对象都是通过数据类实现的,当然我们自己也可以实现的,这里就不做展示了,自己可以下去试试 复制代码
尽管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()//只读的也可以转为读写的 复制代码
值范围表达式用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)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原则
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) } 复制代码
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()) } 复制代码
如果类型强制转换,而且类型不兼容,类型转换操作符通常会抛出一个异常,称之为不安全的,而不安全的类型转换使用中缀操作符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 复制代码
为了访问外层范围内的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表达式没有接收者 } } } } 复制代码
本章将会继续探索null值安全性、异常类、注解以及反射 #####2.1 null值安全性 在Java中,经常遇到空指针的困扰,表脑瓜子疼,对于这个Kotlin使用一些新的语法糖,会尽可能避免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 复制代码
假设我们有一个可为null的引用r,我们可以认为:如果不为空,就是用,否则使用其他的值
//如果"?:"左侧的表达式不是null,Elvis操作符就会返回它的值,否则,返回右侧表达式的值,注意,只有在左侧表达式为null,才会计算右侧表达式的值 var len1 = b?.length ?:-1 复制代码
在Kotlin中,由于throw和return都是表达式,因此可以用在右侧
var len1 = b?.length ?:throw NullPointerException() var len2 = b?.length ?:return 复制代码
对于NPE的忠实粉丝,还可以写!!b,对于b不为null的情况,这个表达式会返回一个非null的值,如果是null,就会抛出NPE
var len2 = b!!.length 复制代码
#####2.2 异常类 Kotlin中所有的异常类都是Throwable的子类,要抛出异常,可以使用throw表达式
//和Java的使用区别不是太大,这里就不说了 try { } catch (e: NullPointerException) { null } finally { } 复制代码
注解是用来为代码添加元数据(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{ } 复制代码
注解可以在类、函数、函数参数和函数返回值中使用
@ MyAnnotationClass class Foo { @ MyAnnotationClass fun bazz(@MyAnnotationClass foo : Int):Int{ return (@MyAnnotationClass l) } } //如果需要对一个类的主构造器加注解,那么必须在主构造器声明中添加constructor关键字,然后在这个关键字之前添加注解 class Foo @MyAnnotationClass constructor(n:Int){ // ... } 复制代码
注解类可以拥有带参数的构造器
annotation class Special(val why :String)
使用Special("example") class Foo{}
并不是所有类型的参数都允许在注解类的构造器中使用,注解构造器只允许使用下面类型的参数 (1)与Java基本类型对应的数据类型(Int、Long) (2)String (3)枚举类 (4)KClass (5)其他注解类
尽管Kotlin是基于JVM的编程语言,但在Kotlin中使用反射,需要引用额外的库(kotlin-reflect.jar)
val c = MyClass::class 类引用是一个KClass类型的值
注意,Kotlin的类引用不是一个Java的类引用,要得到Java的类引用,可使用KClass对象实例的java属性
val c =MyClass::class.java
反射最常用的功能之一就是枚举的成员,如类的属性、方法等。
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 复制代码
反射的另外一个重要应用就是可以动态调用对象的成员,如成员函数、成员函数、成员属性,所谓的动态调用,就是根据成员名字进行调用,可以动态指定成员的名字,通过::操作符,可以直接返回类的成员
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 复制代码
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区别还是比较大的,这篇讲的,实际开发中很是有用的,可能将的还是有限的,毕竟一些知识,还得在实践的更深入的掌握