美文网首页Kotlin
Kotlin语法糖--其他小知识

Kotlin语法糖--其他小知识

作者: 皮球二二 | 来源:发表于2017-09-11 22:14 被阅读161次

    今儿起我们开始学习一下之前零零碎碎涉及到的一些知识

    • 解构声明

    有时候我们把一个对象解构成很多变量,这样看起来就比较方便,就像这样

    val (name, age) = aValue
    

    这种就叫解构声明。一个解构声明可以同时创建多个变量,例如这里的name与age,然后我们可以独立使用它们

    println("$name + $age")
    

    一个解构声明会被编译成以下代码

    println("${aValue.component1()} + ${aValue.component2()}")
    

    其中的component1与component2函数是Kotlin中的约定原则之一,当然也可以用component3、component4等等
    我们在实现解构声明的时候,componentN函数需要使用operator关键字进行标记,你可以写在扩展函数中或者类的内部函数中,就像这样

    private operator fun A.component2(): Int {
        return age
    }
    
    class A {
        val name = "Hello"
        val age = 10
        operator fun component1(): String {
            return name;
        }
    }
    

    在数据类中使用也是相当广泛

    data class DataB(val name1: String, val age1: Int)
    
    class B {
        fun getDataB() : DataB {
            return DataB("World", 20)
        }
    }
    
    val (name1: String, age1: Int) = B().getDataB()
    println("$name1 + $age1")
    

    系统标准库也提供很多这样的扩展,比如对Map的迭代

    @kotlin.internal.InlineOnly
    public inline operator fun <K, V> Map.Entry<K, V>.component1(): K = key
    @kotlin.internal.InlineOnly
    public inline operator fun <K, V> Map.Entry<K, V>.component2(): V = value
    

    使用的时候这样

    val map: Map<String, String> = mapOf("key1" to "value1", "key2" to "value2", "key3" to "value3", "key4" to "value4")
    for ((key, value) in map) {
        println("$key + $value")
    }
    
    • 下划线用于不需要使用的变量
    val (_, age) = aValue
    
    • 在Lambda表达式中解构

    如果Lambda表达式具有Pair类型(或者Map.Entry以及任何其他具有相应componentN函数的类型)的参数,那么可以将他们放在括号中来引入多个新参数来取代单个新参数

    a { 
        it -> it.name+" "+it.age
    }
    a {
        (name, age) -> name+" "+age
    }
    a {
        name+" "+age
    }
    
    • 集合

    与Java不同,Kotlin区分可变集合与不可变集合。这样有助于消除bug和设计良好的API
    Kotlin的List<out T>类型是提供一个只读操作(如get、size)等的接口,可以修改List的是MutableList<T>。这一模式同样适用于Set<out T>/MutableSet<T>以及Map<K, out V>/MutableMap<K, V>
    需要使用Kotlin标准库方法,如listOf、mutableListOf、setOf、mutableSetOf、mapOf、mutableMapOf
    需要注意的是这些类型都是协变的,所以不可变类型子类集合可以直接赋值到父类集合中去。关于协变,在之前的泛型中已经专门说明了,此处不再赘述


    可变集合不是协变的
    只读集合是协变的

    集合中有很多可用的扩展方法,你可以在使用过程中细细体会

    • 区间

    区间表达式由具有操作符..形式的rangeTo函数辅以in和!in组成。区间是为任何可比较类型定义的,但对于整数原生类型,Kotlin对它有一个优化的实现

    val i=2
    if (i in 1..10) {
        println("查询到")
    }
    

    整形区间(IntRange、LongRange、CharRange)有一个额外的特性:他们可以用于迭代。编译器负责将其转换为类似Java的基于索引的for循环

    for (j in 1..10) {
        println(j)
    }
    

    如果要倒序输出,则使用downTo函数

    for (j in 10 downTo 1) {
        println(j)
    }
    

    如果想步进的话,直接用step函数

    for (j in 1..10 step 2) {
        println(j)
    }
    

    如果要创建一个不包含结束元素的区间,使用until函数

    for (j in 1 until 10) {
        println(j)
    }
    
    • 类型的检查与转换

    • is和!is操作符

    我们可以使用is操作符或者其否定形式!is来检查对象是否符合给定的类型

    val obj: Any? = null
    when (obj) {
        is String -> {
    
        }
        is Int -> {
    
        }
        else -> {
    
        }
    }
    
    • 智能转换

    在大多数情况下,不需要在Kotlin中使用显式转换操作符,因为编译器跟踪不可变值的is并检查,并在需要时自动自行安全的转换。我觉得Java在这点上,做的实在是太差了
    来看看Java的类型转换

    ArrayList<Object> objects=new ArrayList<>();
    objects.add("123");
    for (Object object : objects) {
        if (object instanceof String) {
            ((String) object).length();
        }
    }
    

    请注意,在已经判断过并且确定是String之后,Java依然要求将object强转为String,才能使用String的方法
    再来看看Kotlin

    var objs: MutableList<Any?> = mutableListOf()
    objs.add("123")
    for (obj in objs) {
        if (obj is String) {
            obj.length
        }
    }
    

    怎么样,感觉牛逼吧。编译期足够聪明,知道转换后类型是安全的
    智能转换同样适用于when以及while表达式

    • 转换操作符

    Kotlin中不安全的转换由中缀操作符as完成。

    val value = obj as String
    

    但是在之前的那个集合中,如果add进去的是一个null,那么这样强转就会失败,转换操作符会抛出异常,所以我们就得这样

    val value: String? = obj as String?
    

    为了安全的转换,我们就是用中缀操作符as?,它在失败的时候返回null

    val value: String? = obj as? String
    

    需要注意的是,尽管as?的右边是一个非空类型的String,但是其转换结果是可空的

    • This表达式

    我们使用this表达式来表示当前的接收者:

    1. 在类的成员中,this指的是该类当前的对象
    2. 在扩展函数或者带接收者的函数字面值中,this表示在点左侧传递的接收者参数
    3. 如果this没有限定符,那么它指的是最内层包含它的作用域,如果要引用其他作用域中的this,那么请使用标签限定符this@lable,其中@lable是一个代指this来源的标签

    请注意以下图片中高亮部分

    C类对象
    CIn类对象
    a函数的接收者,一个Int
    a函数的接收者,一个Int
    sum函数的接收者
    a函数的接收者,一个Int
    • 相等性

    Kotlin中有两种相等性的类型

    1. 引用相等(2个引用指向同一个对象,由===(以及其否定形式!==)操作判断)
    2. 结构相等(用equals()或者==(以及其否定形式!=)检查)
    • 空安全性

    • 可空类型与非空类型

    Kotlin旨在帮助我们从代码层面上消除NPE,类型系统区分一个引用是否可以容纳null还是不能容纳。例如String类型的常规变量是不能容纳null的,如果想要允许容纳,那么我们就得声明一个变量为可空字符串,写作String?


    可空字符串

    如果你直接访问可空类型字符串,会提示错误


    不能直接访问
    我们来看看如何访问可空类型
    • 在条件中检查null

    很传统

    val length = if (b != null) b.length else 0
    
    • 安全的调用
    b?.length
    

    如果b为非空,则返回b.length,否则返回null。这个表达式类型为Int?
    如果只要对非空值执行某个操作,那么你可以尝试let

    b?.let { 
        val length = b.length
    }
    

    这样在b为null的情况下,lambda表达式中的内容将不会执行

    • Elvis操作符
    val length = b?.length ?: 0
    

    如果?:左边的表达式不为null,那么Elvis操作符就返回左侧表达式的结果,反之返回右侧表达式的结果。这里如果b不为null,length值为b.length,否则为0
    throw跟return在Kotlin中都是表达式,所以他们也可以出现在Elvis操作符右侧

    fun check() : Int? {
        val b: String? = null
        val length = b?.length ?: return null
        val c: String? = null
        val length2 = c?.length ?: throw Exception()
        return length2
    }
    
    • !!操作符

    如果你确定你的值不会出现null,你可以写成这样

    b!!.length
    

    此时如果b出现null,那么就会出现NPE啦

    • 注解

    注解的相关概念这里就不多说了,有疑问请查阅资料或者看看我之前的博客,我们直接谈怎么用吧
    先来一段Java的注解声明

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
    public @interface MyAnnotation {
        String name() default "Jack";
        int age();
    }
    

    用Kotlin翻译一下

    @Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD, AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
    @Retention(AnnotationRetention.RUNTIME)
    annotation class MyKAnnotation(val name: String = "Jack", val age: Int)
    

    运行结果都是一样的


    注解

    使用的时候几乎没啥区别

    @MyKAnnotation(name = "Hello", age = 20)
    public class AnnotationClass {
    
        @MyKAnnotation(name = "WORLD", age = 30)
        String value="value";
    
        @MyKAnnotation(age = 10)
        public void function() {}
    }
    

    看看Kotlin的

    @MyKAnnotation(name = "Hello", age = 20)
    class KAnnotationClass @MyKAnnotation(name = "Hello KAnnotationClass", age = 20) constructor(val value_: String) {
        @MyKAnnotation(name = "WORLD", age = 30)
        val value = 10
        @MyKAnnotation(age = 10)
        fun function() {}
    }
    

    请注意,如果需要对类的主构造函数进行标注,则需要在构造函数声明中添加constructor关键字,并将注解添加在其前面
    注解参数不可以有可空类型,因为JVM不支持将null作为注解属性的值进行存储
    如果注解作为另一个注解的参数,则其名称前不需要加“@”字符作为前缀

    @Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD, AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
    @Retention(AnnotationRetention.RUNTIME)
    annotation class MyKAnnotation2(val kan: MyKAnnotation)
    
    @MyKAnnotation2(kan = MyKAnnotation("old", 100))
    class K2AnnotationClass @MyKAnnotation(name = "Hello KAnnotationClass", age = 20) constructor(@field:MyKAnnotation(name = "Hello KAnnotationClass", age = 20) val value_: String)
    
    • 反射

    我们依然通过对比Java与Kotlin来学习,俗话说没对比就没伤害嘛
    先看一个普通的类,一个私有方法加一个私有对象

    class ReflectStudyA {
        private String aValue="123";
        private void toIntValue(String value) {
            System.out.println(Integer.parseInt(value));
        }
    }
    

    我想在代码中的任何地方调用toIntValue方法,并且将aValue的值作为入参。如果是Java,得这样写

    try {
        Class reflectStudyAClass = Class.forName("com.renyu.kotlin.chapter9.ReflectStudyA");
        ReflectStudyA reflectStudyA = (ReflectStudyA) reflectStudyAClass.getDeclaredConstructor().newInstance();
        Field aValueField = reflectStudyAClass.getDeclaredField("aValue");
        aValueField.setAccessible(true);
        String aValue = (String) aValueField.get(reflectStudyA);
        Method method = reflectStudyAClass.getDeclaredMethod("toIntValue", new Class[] {String.class});
        method.setAccessible(true);
        method.invoke(reflectStudyA, aValue);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    }
    

    直接改成Kotlin,其实也没多大变化

    val reflectStudyAClass = ReflectStudyA::class.java
    var reflectStudyA: ReflectStudyA = reflectStudyAClass.getDeclaredConstructor().newInstance()
    val aValueField = reflectStudyAClass.getDeclaredField("aValue")
    aValueField.isAccessible = true
    val aValue = aValueField.get(reflectStudyA)
    val reflectStudyAMethod = reflectStudyAClass.getDeclaredMethod("toIntValue", String::class.java)
    reflectStudyAMethod.isAccessible=true
    reflectStudyAMethod.invoke(reflectStudyA, aValue)
    

    要知道KClass与Java的Class是不一样的。最基本的反射功能是获取Kotlin类运行时的引用,要获取对静态已知的Kotlin类的引用,可以使用类字面值语法,比如MyClass::class。该引用是KClass类型的值,如果要在Kotlin编码中获得Java类的引用,就得在KClass实例上使用.java属性
    对比一下KClass的写法

    val reflectStudyAClass = ReflectStudyA::class
    for (constructor in reflectStudyAClass.constructors) {
        var reflectStudyA: ReflectStudyA = constructor.javaConstructor!!.newInstance()
        for (declaredMemberProperty in reflectStudyAClass.declaredMemberProperties) {
            if (declaredMemberProperty.name == "aValue") {
                declaredMemberProperty.isAccessible = true
                val aValue = declaredMemberProperty.get(reflectStudyA)
                for (function in reflectStudyAClass.functions) {
                    if (function.name == "toIntValue") {
                        function.isAccessible = true
                        function.javaMethod!!.invoke(reflectStudyA, aValue)
                    }
                }
            }
        }
    }
    

    总得来说并没有变的多好,这个是因为我们使用的是Java中的对象。这次我们全部使用Kotlin来重写一遍

    class ReflectStudyB {
        var aValue = "234"
        fun toIntValue(value: String) {
            println(value)
        }
    }
    

    我把private去掉了,然后你会发现眼前一亮

    val reflectStudyBClass = ReflectStudyB::class
    val reflectStudyB = reflectStudyBClass.createInstance()
    val aValue_: KMutableProperty1<ReflectStudyB, String> = ReflectStudyB::aValue
    val aValue = aValue_.get(reflectStudyB)
    val toIntValueFun = ReflectStudyB::toIntValue
    toIntValueFun.call(reflectStudyB, aValue)
    

    这里有很多我们不明白的地方,没关系,我们一个个的来研究

    • 函数引用

    一般情况下我们这样命名一个函数

    fun abc(value: String) : Int {
        return value.toInt()
    }
    

    调用它也很自然的这样

    abc("2")
    

    但是我们也可以把它当做一个值传递,例如传给一个另函数,为此我们使用::操作符

    fun getValue(funValue: (String) -> Int, value: String) {
        println(funValue(value))
    }
    
    arrays.forEach {
        getValue(::abc, it)
    }
    

    这里的::abc是函数类型(String) -> Int的一个值,所以直接传递到getValue方法中
    如果我们需要使用类的成员函数或扩展函数,那么需要对这种写法限定,例如

    fun String.bcd() : Int {
        return this.toInt()
    }
    

    这种情况下,我们需要使用String进行限定

    arrays.forEach {
        getValue(String::bcd, it)
    }
    
    • 属性引用

    函数可以引用,同样属性也可以引用

    var xValue = 3
    
    ::xValue.set(24)
    println(::xValue.get())
    

    表达式::xValue类型为KProperty<Int>,它允许我们使用get()读取它的值,或者使用name属性来获取属性名称。对于可变属性,类型则为KMutableProperty<Int>,并且有一个set()方法
    如果要访问类的成员的属性,我们要这样限定

    class ABC {
        var yValue = 3
    }
    
    val funValue = ABC::yValue
    var abc = ABC()
    funValue.set(abc, 12)
    funValue.get(abc)
    println(abc.yValue)
    

    扩展属性其实也是差不多的

    val String.x1Value: String
        get() = "Hello "+this
    
    println(String::x1Value.get("author"))
    
    • 构造函数引用

    构造函数可以像其他函数和属性那样引用,通过::操作符并添加类名来引用构造函数

    class CDE {
        var value: String? = null
    
        constructor(value: String) {
            this.value = value
        }
    
        fun printValue() {
            println(value)
        }
    }
    

    通过这个方法引用构造函数

    fun getCDE(cde: (String) -> CDE, value: String) : CDE {
        return cde(value)
    }
    

    看看引用部分

    var cde: CDE = getCDE(::CDE, "aaa")
    cde.printValue()
    

    相关文章

      网友评论

        本文标题:Kotlin语法糖--其他小知识

        本文链接:https://www.haomeiwen.com/subject/rnhujxtx.html