转载

Kotlin的解析(拓展)

  在前几篇的基础上,大家如果认真的阅读,并跟着思路实践的话,应该可以收获很多的,前面基本已经覆盖了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
正文到此结束
Loading...