前言
在前几篇的基础上,大家如果认真的阅读,并跟着思路实践的话,应该可以收获很多的,前面基本已经覆盖了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区别还是比较大的,这篇讲的,实际开发中很是有用的,可能将的还是有限的,毕竟一些知识,还得在实践的更深入的掌握