Kotlin基础

一 Kotlin概述
Kotlin是一种在Java虚拟机上执行的静态类型编程语言,俄罗斯圣彼得堡的JetBrains团队开发,名字源自俄罗斯科特林岛

特点1:高级语言,接近自然语言,语法简洁(亦好亦坏,见<kotlin语法简洁的背后>),借鉴了多门语言,所以有网友说他像Java、Scala、 Python、C、C#、Swift、JavaScript、Groovy、shell等语言
特点2:支持多种编程范式,例如函数式编程,面向对象
特点3:定义变量用var,定义方法用fun,数据类型后置
特点4:用?关键字把空指针尽量扼杀在编译期间
特点5:一门在不断完善的语言,例如: 默认可见性从原来的internal变更为了public,再例如: protected的可见性在二级子类中不可见,变更为了所有子类可见
二 包名与顶级声明(Top-level declarations)
1.包名跟文件路径不一致时java会报错,kotlin只是告警,例:Test.java Test.kt两个文件在com/gs/test目录下
  java中必须是package com.gs.test 而kotlin可以是package com.gs.zhangsanlisi
2.顶级声明,指跟包名平级的类型声明,java的顶级声明中只能是类或接口,kotlin的顶级声明中还可以是函数,变量,
  从下例kotlin字节码反编译为java的代码中可以看出,kotlin顶级声明的函数跟变量被整合起来定义到了一个类中,
  类名为'文件名Kt',变量跟函数皆为static类型,变更这个类名的话可以用注解 @file:JvmName("TopLevelClass")
  调用顶级方法可以用'包名.方法名'
  
例: .../com/gs/test/TopLevelTest.kt

    //@file:JvmName("TopLevelClass")
    package com.gs.temptest
    //top-level 接口
    interface InterfaceTopLevel {}
    //top-level 类
    class ClassTopLevel {}
    //top-level 变量
    var varTopLevel = 1
    //top-level 方法
    fun funTopLevel() {
        println("TopLevelTest")
    }
    fun main(args: Array<String>) {
        funTopLevel()
    }
    class TopLevelTest {}
    
字节码反编译为java后如下
    
    public interface InterfaceTopLevel {
    }
    public final class ClassTopLevel {
    }
    public final class TopLevelTestKt {
        private static int varTopLevel = 1;
    
        public static final void funTopLevel() {
            System.out.println("TopLevelTest");
        }
        public static final void main(String[] args) {
            funTopLevel();
        }
        public static final int getVarTopLevel() {
            return varTopLevel;
        }
    
        public static final void setVarTopLevel(int var0) {
            varTopLevel = var0;
        }
    }
    public final class TopLevelTest {
    }
三 可见性修饰符及final关键字
Kotlin 提供四个关键字(public private protected internal),来限制类及成员可见性
用于类的成员时含义:
    public:全局可见。此为默认的类型
    private:在当前类别中可见
    protected:在当前类别的子类中可见 //维基百科描述过期了,--如果子类再被继承,则在下一级子类中不可见
    internal:在当前模块中可见
用于顶层声明时含义:
    public:全局可见。此为默认的类型
    private:在当前文件中可见
    protected:顶级声明不允许用该关键字
    internal:在当前模块中可见
从反编译的java代码理解kotlin可见性关键字
    1. 可见性关键字用于变量时,改变的是对应变量Getter/Setter方法的可见性,kotlin成员变量永远都是java中的private类型,kotlin用private修饰变量,变量将不会自动生成Getter/Setter方法
    2. 可见性关键字用于函数时,跟java一样   
    3. 可见性关键字用于类时,private修饰的类被标记为final类型,但并没有被加上java中的private关键字
    4. protected不允许在顶级声明中使用,原因参见'顶级声明'中描述
    5. internal可以简单理解为跟public一样,只是跨module访问不了
从kotlin自身去理解可见性关键字及final
    1. 从kotlin自身去理解,即:
         a. 定义属性,要加var,数据类型后置,默认是public final类型,公开的不可继承的
         b. 定义函数,要加fun,返回类型后置,默认是public final类型,公开的不可继承的
         c. 定义类, 要加class,继承用冒号,默认是public final类型,公开的不可继承的
    2. kotlin中的final只有不能继承重写这一层意思,没有不可更改的意思,类比java中的final有变量不能修改,不能继承重写两层意思,
       从上面反编译角度理解kotlin的语法特性会陷入逻辑悖论,只能认为是kotlinc在编译阶段做了不同的检查或处理
       逻辑悖论例1:上面第3点,"可见性关键字用于类时"中的描述
       逻辑悖论例2:a.var变量默认用final修饰,反编译的java代码中没有加上final关键字
                 b.val变量用open修饰,反编译的java代码中没有移除final关键字
    3. kotlin中的变量,函数,类,默认都是public final类型,要想继承需要用open、abstract关键字修饰去替换默认final,open跟final作用相反,不能共存
    4. 局部变量不能用可见性描述符及final修饰
    
总结:可见性关键字跟java类似,final有明显差异,默认是public final类型(公开的不可继承的),从反编译角度难闭环理解    
四 变量及成员变量(属性)
1. 定义变量,要加var或val,数据类型后置,默认是public final类型
   var name: String = "zhangsan"
       
2. 定义属性的语法
    var <propertyName>[: <PropertyType>] [= <property_initializer>]
        [<getter>]
        [<setter>]
        
        定义变量必须的关键字    
        属性名字 <propertyName>   
        属性类型 <PropertyType>   
        属性初始化器  <property_initializer>  
        属性访问器 <getter> <setter> 用操作符.访问属性时调用相关方法 
    
    语法说明
        a. var也可以是val,var表示变量可变,val表示变量只读没有setter访问器并且只能赋值一次
        b. 属性类型可以省略,前提条件是能通过属性初始化器或getter返回值让编译器推断出类型,
           作为静态类型语言,变量的类型必须在创建时就声明好,Kotlin编译器有类型推断机制来帮助我们代码简洁化 
        c. 属性的初始化器可以省略,但属性必须初始化
            c1.可以在定义时赋值(即,属性初始化器中赋值),也可以在init代码块中赋值,也可以在构造函数中赋值
                class Person(val firstName: String = "zhangsan", val lastName: String, var age: Int) {
                //在构造函数中赋值,在主构造函数中定义了3个属性,并给firstName了一默认值zhangsan
                }        
            c2.不想初始化,需要用by,abstract或lateinit关键字修饰属性,声明了lateinit的变量使用前若没有赋值,则会抛出UninitializedPropertyAccessException异常
            c3.变量不能赋值null,若想赋值为null需要在类型后加?关键字标明可以为空
        d. 属性访问器也可以省略,会有默认生成
         
3. 幕后字段与幕后属性
    幕后字段
        概念:定义属性时,kotlin会为该属性生成一个field(字段)、访问器方法(getter和setter方法),
            这个field只能在属性访问器内使用,指向代表该属性,被称为幕后字段
        生成条件:属性至少有一个访问器方法使用默认实现,或者自定义访问器通过field引用幕后字段
        存在意义:
        var var2 = 22
            set(value) {
                if (value >= 0)
                    field = value //使用field关键字--OK
                 // counter = value //若果这样写,会使属性访问器的setter方法陷入逻辑上的死循环
            }
            
    幕后属性
        指私有属性
            
4. 自定义属性访问器
    class VarTest {
        var var1: String = "zhangsan"
            private set //把默认的setter私有化
            get() = "lisi"
        var var2 = 22
            get() {
                return 110
            }
            set(value) {
                var1 = "wangwu" //访问器里直接设置其他属性,利用这个可以做一些联动操作
                if (value >= 0) {
                    field = value
                }
            }
    }  
      
5. 编译期常量const val与顶级声明中val差别
    const val TEST_CONST_VAL: String = "test_const_val"
          val test: String = "test"
    差别:      
    const val:位于顶级声明或object声明,定义时赋值(String或原生类型),不能有getter访问器,使用时直接类似"宏替换"   
          val:位于更多地方,赋值更灵活,使用时在调用访问器
    顶级声明时,字节码反编译到java时看到的差别: 
          const val 为public static final类型,使用地方直接替换成了字面值,无getter,setter方法
                val 为private static final类型,使用地方在调用其getter方法 
五 函数
5-1 Kotlin函数背景知识
1. Kotlin除支持面向对象外,是支持函数式编程的,普通表达式及lambda表达式的语法结构使用的比较多
2. λ演算:是在20世纪30年代提出,把函数抽象化定义后,来描述形式系统,在λ演算中,每个表达式都代表一个函数
3. 函数式编程:如何编写程序的方法论/编程方式,将电脑运算视为函数运算(一次吃多个指令的视角),λ演算是其基础
4. 函数式编程特点:函数是头等对象,头等公民,一个函数可以是另一个函数的参数或返回值,可以存储在变量中
   把运算过程尽量写成一系列嵌套的函数调用,每个表达式都代表一个函数(表达式漫天飞)
5. 指令式编程:如何编写程序的方法论/编程方式,汇编语言的抽象化,例如C(一次吃一个指令的视角)
6. 高阶函数:将函数用作参数或返回值的函数
7. 表达式: 一种有值的语法结构,它是由运算符将变量、常量、函数调用返回值结合而成的式子
8. lambda表达式:语言定义的特殊格式的复杂的表达式,又称匿名函数,java与kotlin语法差别如下
   java基本语法格式及特点
     格式  (parameters) -> expression 或 (parameters) ->{ statements;//多行 }
     特点a.可选类型声明:不需要声明参数类型,编译器可以统一识别参数值
     特点b.可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号
     特点c.可选的大括号:如果主体包含了一个语句,就不需要使用大括号
     特点d.可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值//大括号内有多少行代码?有多行的话是需要指定返回值的
     特点e.可以直接用一个表达式代替 @FunctionalInterface函数式接口,简化setOnClickListener写法,
     对应kotlin,SAM转换知识(Single Abstract Method)
   kotlin基本语法格式及特点
     格式   { variable -> body_of_function}
     特点a. 可直接作为函数字面值为函数类型变量赋值或当参数,此时与kotlin中匿名函数的差别在于后者可以显示指定返回类型,
     以往lambda被称做匿名函数,kotlin中更倾向把'匿名函数'用作对'没有函数名的普通函数'的称呼
     特点b. SAM转换,用来替代手动创建实现函数式接口的类,button.setOnClickListener { it.visibility = View.INVISIBLE }
     详见《Kotlin语法简洁的背后》http://192.168.120.239:4999/web/#/6/889
     特点c.  一个参数的lambda表达式,可以不声明参数并省略->,此时用it代表单个参数的隐式名称
     特点d. 支持解构声明语法 https://www.kotlincn.net/docs/reference/multi-declarations.html
     特点e. 作为函数参数时,lambda中return语句要有标签,若函数是内联函数,则不用标签直接可以进行非局部返回

9. 参考
     https://baike.baidu.com/item/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B?fromModule=lemma_inlink
     https://zh.wikipedia.org/wiki/%CE%9B%E6%BC%94%E7%AE%97
     https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B
     https://zh.wikipedia.org/wiki/%E6%8C%87%E4%BB%A4%E5%BC%8F%E7%B7%A8%E7%A8%8B
     https://blog.csdn.net/m0_49476241/article/details/120196991?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166987570916782425640671%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=166987570916782425640671&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-120196991-null-null.142^v67^control,201^v3^add_ask,213^v2^t3_esquery_v3&utm_term=%E8%A1%A8%E8%BE%BE%E5%BC%8F&spm=1018.2226.3001.4187
     https://baike.baidu.com/item/%E5%87%BD%E6%95%B0%E5%BC%8F%E8%AF%AD%E8%A8%80/7663961
5-2 普通函数
1. 定义函数需要加fun描述符,返回类型后置,方法默认是public final类型
      fun foo(): Int {return 1}
2. 参数
   函数参数可以有默认值,一旦有默认值会生成相关的重载函数
      fun foo(bar: Int = 0, baz: Int) {}//参数有默认值
      foo(baz = 1)
   如果最后一个参数是lambda表达式,则可以把表达式移到括号外,此时小括号也可以省略
      fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) {}
      foo { println("hello") } //表达式移到括号外,省略小括号
   可变长参数用vararg关键字
      fun foo(vararg strings: String) {} //可变长参数
3. 返回值
      可以省略:返回值默认Unit,功能类似java中void,可以省略
      不可省略:函数有方法体时,kotlin不会去自动推断其返回类型,此时要显示声明
4. 表达式函数
   kotlin中可以直接把表达式赋值给函数,简化函数写法,这种语法逻辑上的合理性由来参考上述λ演算,例如
      fun double(x: Int): Int = x * 2 省略返回值写成 fun double(x: Int) = x * 2
5-3 特殊函数(函数类型的变量)
   将函数用作参数或返回值是函数编程式编程的标配,此时函数可以赋值给变量,这个变量类型为函数类型,kotlin实现函数式编程
利用的是FunctionN的一系列接口(对invoke运算符进行了重载的接口),编译运行时把函数类型变量的字面值转成了这些对象,
每一个lambda表达式匿名函数都是一个对象
   例1:
      val sum: (x: Int, y: Int) -> Int = { x: Int, y: Int -> var tmp = x + y;tmp + 1 }
      函数类型变量 sum
      变量具体的函数类型 (x: Int, y: Int) -> Int 其中函数参数是 x: Int, y: Int 返回Int
      函数字面值,函数实例,lambda表达式 { x: Int, y: Int -> var tmp = x + y;tmp + 1 }
      函数体中语句 tmp = x + y;tmp + 1
      函数返回值tmp + 1,最后一行是返回值
   例2:
      val vv: (x: Int) -> Int = { x: Int -> x }
      print(vv(1))
   说明1. vv为函数类型变量,具体的函数类型是(x: Int) -> Int
   说明2. { x: Int -> x } lambda表达式是函数类型变量的字面值,编译运行时被转成了FunctionN系列的对象,
         官网称这个lambda表达式为函数类型变量的实例
   说明3. 第二行代码在打印 vv(1)函数调用结果
   说明4. 上面两行代码反编译成java大致如下
          Function1 vv = Function1.INSTANCE;
          System.out.print(vv.invoke(1));
   说明5. 带有接收者的函数类型变量A.(B) -> C可以跟(A, B)-> C函数类型变量相互赋值,例如
         val receiverFun: String.(Int) -> String = { times -> this.repeat(times) }
         val twoParameters: (String, Int) -> String = receiverFun // 变量相互赋值--OK
         两种调用方式如下
         print("zhangsan".receiverFun(10))
         print(receiverFun("zhangsan",10))
5-4 函数的几种写法汇总及双冒号运算符
kotlin函数常见的几种写法如下
    fun foo1(){println("zhangsan")}//普通函数
    fun foo2() = 1 //表达式函数
    fun foo3() = foo1() //方法内部调用了foo1
    val foo4 = { x: Int -> x } //函数变量,用lambda赋值
    val foo5 = ::foo1 //函数变量,取函数引用赋值
    
函数类型变量,双冒号运算符,并不是kotlin首次发明的,c语言中也可以这样干,例如
    typedef int (*fun_ptr)(int,int);
    int test(int,int){}
    fun_ptr fun = &test;//差别是kotlin要用双冒号运算符::去拿一个成员引用或者一个类引用
    fun();
5-5 其他函数概念
1. 内联函数
   内联函数就是指函数在被调用的地方直接展开,不用像一般函数那样有压栈出栈操作,从而提高程序运行速度,用空间换时间
      public final void doSomething() {
      // to do something
      }
   java没有inline之类关键字,声明了final后,由编译器决定该方法在被调用时是否在调用处直接展开使用,
   kotlin print()函数是stdlib中内联函数,编译阶段会在调用处展开转化成System.out.print

   kotlin想用内联解决的一个问题:
       kotlin高阶函数中,参数每一个函数都是一个对象,运行时间开销太大,可以用inline关键字内联化lambda表达式,
   不在创建一个函数对象达到消除这类开销的目的,inline修饰符影响函数本身和传给它的lambda表达式,所有这些都将内联到调用处
   
2. 扩展函数:扩展一个类的新功能而无需继承该类,新增函数就像那个原始类本来就有的函数一样,通过接受者对象调用该方法
   原理:反编译看扩展函数是一个把接受者作为参数的一个函数. 
   可简单记忆为:参数是接收者->方法体是对接收者处理->返回接收者(或其他类型)
   补充:标准库里扩展函数用的很多,有时接受者类型不仅可以是类类型,还可以函数类型,例如:
      fun ((ClassA) -> ClassB).method1() {}
      fun method2(block: ClassA.() -> ClassB) { block.method1() }

3. 中缀函数/infix函数,只有一个参数的扩展函数或成员函数,可以用infix标记,简化函数调用
   infix fun Int.shl(x: Int): Int { …… }
   传统调用方式: 1.shl(2)
   中缀表示法调用方式: 1 shl 2
   
4. reified,将内联函数的‘类类型参数’标记为在方法体可访问,作用是美化函数调用

5. 函数重载,java与kotlin对比如下
   java
     public void add(int a)
     public void add(int a, String b)
     public void add(int a, Boolean c)
     public void add(int a, String b, Boolean c)
   kotlin
     fun add(a :Int , b :String ="1",c :Boolean =false)

6. 运算符重载,利用扩展函数跟关键字实现
     operator fun OperatorTest.plus(other: OperatorTest):OperatorTest{
        return OperatorTest()
     }
   重载+运算符,跟c++不一样的地方是"重载运算符+时,要用plus,不能用+",若没有operator关键字,那这只是一个扩展函数
   
7. 作用域函数 let、run、with、apply also
     stdlib定义了几个函数去方便去操作对象(大多是泛型扩展函数)
     可以简单记忆为 let、also用it,also、apply返回调用对象
     
8. 挂起函数 -- 见<Kotlin协程代码执行顺序> 

六 类

定义类,用class,继承用冒号,无new关键字,与java主要差别在于引入了主构造函数,次构造函数

 1. 类默认是public final类型,要想继承需要用openabstract关键字修饰去替换默认final
 2. 类可以有一个主构造函数以及n个次构造函数
 3. 主构造函数是类头的一部分,跟在类名后,constructor可省略
    class Person constructor(name: String) {}

 4. 次构造函数在类体中声明,如果类有一个主构造函数,每个次构造函数需要委托给主构造函数
    class Person {//无主构造函数,不需要委托
        constructor(name: String) {}
    }
    class Person(name: String) {//有主构造函数,需要委托下
        constructor(name: String, sex: String) : this(name) {}//委托
    }
 5. 没有声明任何构造函数时,编译器会给你加一个默认无参主构造函数,一旦你定义了编译器就不管了
 6. 子类中必须体现出来要调用父类哪个构造函数 
    a.如果子类有主构造函数,父类必须就地初始化
    b.如果子类没有主构造函数,每个次构造函数默认加的是super()无参构造函数,不需要在指明
    open class A {
        constructor()
        constructor(x: Int) : this()
    }
    class B() : A() {}//有主构造函数,父类必须就地初始化
    class C : A() {} //没有任何次级构造函数,默认加主构造函数,此时也需要对A就地初始化
    class D : A { //没有主构造函数
        constructor():super() //默认加的是super()可省略,官网写的有点问题https://www.kotlincn.net/docs/reference/classes.html
        constructor(x: String, y: Int) //默认加的是super()可省略
    }
 7. 实例初始化时的代码执行顺序: 基类构造函数->主构造函数->init代码块与属性初始化->次构造函数,其中init代码块与成员变量的初始化交织在一起按照在类中出现顺序执行初始化
 8. 内部类跟java差别:标记为inner的嵌套类才能够访问其外部类的成员。此时内部类会带有一个对外部类的对象的引用
 9. 接口跟java差别:
    java中的接口默认是public abstract类型,成员变量是public static final类型,方法是public abstract
    kotlin中的接口可以有抽象属性,另外方法可以有方法体
10. 数据类,类似javaBean,kotlin默认为数据类实现了copy函数,重载了componentN()函数以方便'解构声明语法'的使用
      data class User(val name: String, val age: Int)//数据类
      val (name, age) = user  //这样的语法叫解构声明语法,定义了两个变量name age,前提User类要重载componentN()函数,数据类默认实现过了
七 对象及伴生对象
object概念
    object关键字在kotlin中既表示一个类也表示一个对象,声明一个类同时声明了其实例(例如object MyObject ,其中MyObject既是类类型,又是类对象)
    
反编译角度理解:
    object声明的类,被编译成了一个实现了单例模式的类,成员变量默认是static类型,成员函数若想是static类型,可以用@JvmStatic,kotlin中无static关键字
    object声明放到类A内部时,被编译成静态类,若用companion修饰,则单例对象的引用会变成类A的属性,kotlin想用伴生类成员替换java静态成员
    
object有三种使用方式,表达式用法,顶级声明用法,伴生对象用法
方式一:表达式用法,创建并返回匿名类对象
例1:
    v.setOnClickListener(object : OnClickListener {
        override fun onClick() {
        }
    })
例2:
    val l = object : ListenerHolder(), OnClickListener {
        override fun onClick() {
        }
    }
    v.setOnClickListener(l)
例3:
    var o = object {
        var x: Int = 1
        var y: Int = 2
    }
    println(o.x + o.y)

方式二: 顶级声明用法,定义一个具名类跟对象MyObject
    object MyObject : ListenerHolder(), OnClickListener {
        fun objectFun() {}
        override fun onClick() {
        }
    }
    v.setOnClickListener(MyObject)
    MyObject.objectFun() //用名称直接调用

方式三: 伴生对象用法,伴生对象指,object声明放到类A内部,并用companion修饰,每个类只能有一伴生对象
       kotlin想用伴生对象成员替换java静态成员
    class ClassCompanion {
        companion object Factory {
            fun create(): ClassCompanion = ClassCompanion()
        }
    }
    var v = ClassCompanion.create() //形似顶级声明object用法

    //用伴生对象实现工厂模式的一个例子
    class User private constructor(val nickName:String){
        companion object{
            fun createNameFromLocal(name: String) = User(name)
            fun createNameFromQq(qqEmail:String) = User(getNameFromQq(qqEmail))
            fun createNameFromWechat(weChat:String)= User(getNameFromWechat(weChat))
        }
    }
八 委托
定义:通过by关键字将接口的实现委托给另一个对象或将属性访问器的实现委托给另一个对象
    属性委托时时stdlib提供了对象生成的几个工厂方法 lazy() Delegates.observable()

例1:接口实现的委托
interface Base {
    fun printMessage()
    fun printMessageLine()
}
class OneImpl : Base {
    override fun printMessage() { print("printMessage") }
    override fun printMessageLine() { println("printMessageLine") }
}
//反编译看AnotherImpl将会持有OneImpl引用,AnotherImpl中继承自Base的方法将会调用OneImpl中方法
class AnotherImpl : Base by OneImpl(){
    override fun printMessage() {print("abc") }//显示重写后就不再调用OneImpl中方法
}
fun main() {
    AnotherImpl().printMessage()
}

例2:属性访问器的委托
class Delegate {
    private var value = "initialValue"
    operator fun getValue(example: Example, property: KProperty<*>): String {
        //return value
        return "$example, thank you for delegating '${property.name}' to me!"
    }
    operator fun setValue(example: Example, property: KProperty<*>, s: String) {
        //value= s
        println("$s has been assigned to '${property.name}' in $example.")
    }
}

class Example {
    var a: String by Delegate()
    var b: String by Delegates.observable("first")
    { property, old, new -> println("${property.name}: $old -> $new") }
    val c: String by lazy { "zhangsan" }
}

fun main() {
    var example = Example()
    example.a = "targetStr"
    println(example.a)

    println(example.b)
    example.b = "second"
    println(example.b)

    println(example.c)
}
九 常见的几个关键字
!  如果您使用Kotlin引用在Java Account类中定义的不带注释的name成员,编译器将不知道String映射到Kotlin中的String还是String? 这种不明确性通过平台类型String!表示
?  可空修饰符,数据类型以问号为后缀,表明变量可以被赋null值,kotlin默认引用类型变量不能赋值null
!! 非null断言运算符,下例中name为null则抛NPE异常,name不为null则继续往下执行
    val account = Account("name", "type")
    val accountName = account.name!!.trim()
?. 安全调用运算符,下例中name为null则name?.trim()整体返回null,name不为null则返回trim()调用结果
    val account = Account("name", "type")
    val accountName = account.name?.trim()
?: Elvis运算符,埃尔维斯运算符,下例中Elvis运算符左侧表达式的结果为null,则会将右侧的值赋予accountName
    val accountName = account?.name?.trim() ?: "Default name"
* 解包运算符,指的是对实际参数的解包,也叫伸展(spread)操作符,类似Python
    fun main(args: Array<String>) {
        val list = listOf("args: ", *args)
        println(list)
    }
:: 创建一个成员引用,功能类似c语言取地址运算符&
十 参考
1. 官网 http://kotlinlang.org/docs/reference/ 
   官网翻译 https://www.kotlincn.net/docs/reference/
   官网github https://github.com/JetBrains/kotlin
2. 谷歌官网 https://developer.android.com/kotlin/common-patterns#nullability
3. 维基百科 https://zh.wikipedia.org/wiki/Kotlin

4. 其他
    Scala 闭包 https://www.runoob.com/scala/scala-closures.html
    Kotlin闭包 https://www.jianshu.com/p/fd24a37ae411
    Kotlin运算符 https://blog.csdn.net/weixin_53966032/article/details/125678450
    Kotlin智能类型转换 https://blog.csdn.net/yihanss/article/details/126398883
    Kotlin 默认可见性为 public,是不是一个好的设计? https://blog.csdn.net/Android23333/article/details/127979604
    静态类型与动态类型编程语言之间的区别 https://zhuanlan.zhihu.com/p/109803872    
附录
附录一 kotlin编译
Kotlin编译过程和Java差不多,加载stdlib之后主要进行以下步骤
编译前端(字节码.class生成)
    1. 词法分析
       把关键字,结束符等统计出来,分组,将源代码文件分解成特定的语法词汇(标记)
    2. 语法分析
       根据上步整理好的词汇,生成语法树(ST/AST)
    3. 语义分析 //及中间代码生成
       检查语法树对不对,处理语法糖,例:变量运算符是否在乱用,这时给你一个编译失败的提示 //把语法树转成中间代码
    4. 字节码生成
       加入语言特性相关的一些代码,然后生成字节码
       例:自动生成Getter/Setter代码、Companion 转变成静态类、修改类属性为 final 不可继承等等
编译后端(JIT)
    1. 对字节码进行优化
    2. jvm将热点代码翻译成机器码(Just-In-Time compilation,程序执行过程中将字节码转换成机器码)

参考
    Java的编译原理 https://www.cnblogs.com/qingshanli/p/9281760.html
    java前端编译和后端编译理解 https://blog.csdn.net/qq_35207086/article/details/123758442
    Java和Kotlin编译基础知识 https://www.likecs.com/show-204739185.html#sc=848
    Kotlin 编译流程简介 https://blog.csdn.net/u010218288/article/details/86062858
    Kotlin 编译之路 "Kotlin编译器" https://zhuanlan.zhihu.com/p/76622754
    Kotlin 源代码编译过程分析 https://www.jianshu.com/p/0ba73a73f58d
附录二 使用kotlin编译工具编译(compiler安装及使用)
安装
    ubuntu可以直接在Ubuntu software安装,也可以用命令 sudo snap install kotlin --classic 进行安装

文件内容 HelloKotlin.kt
    fun main(args : Array<String>) {
        println("hi kt")
    }
使用
    方式一:
        kotlinc HelloKotlin.kt //编译
        kotlin HelloKotlinKt   //运行
    方式二:
        kotlinc HelloKotlin.kt -include-runtime -d HelloKotlin.jar  //jar解压后里面包含了kotlin的运行库
        java -jar HelloKotlin.jar 或 kotlin HelloKotlin.jar
    方式三:
        kotlinc HelloKotlin.kt -d HelloKotlin.jar //不含kotlin运行库
        kotlin -classpath HelloKotlin.jar HelloKotlinKt
附录三 AS环境搭建(原来是纯java的项目可以如下这样配置下)
step 1. 在项目build.gradle中添加kotlin语法扩展 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
step 2. 在app下的build.gradle中指明这是一个kotlin项目
    plugins {
        id 'com.android.application' //Android项目
        id 'kotlin-android'  //添加此行
    }
step 3. (非必须)在app下的build.gradle的dependencies中添加kotlin的一些库,implementation 'androidx.core:core-ktx:1.7.0'
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值