Kotlin知识点总结与初写时的一些建议

本文是在学习和使用kotlin时的一些总结与体会,一些代码示例来自于网络或Kotlin官方文档,持续更新...

对象相关

  • 对象表达式:相当于Java匿名类部类,在使用的地方被立即执行:

    val a = 10
    
    val listener = object : Info("submit"),IClickListener {
        override fun doClick() {
            println("a:$a")
        }
    
    }
    
    listener.doClick() // 打印 a:10
    //有时候我们只是需要一个没有父类的对象,我们可以这样写:
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    
    print(adHoc.x + adHoc.y)
    //像 java 的匿名内部类一样,对象表达式可以访问闭合范围内的变量 (和 java 不一样的是,这些变量不用是 final 修饰的)
    fun countClicks(window: JComponent) {
        var clickCount = 0
        var enterCount = 0
        window.addMouseListener(object : MouseAdapter() {
            override fun mouseClicked(e: MouseEvent) {
                clickCount++
            }
            override fun mouseEntered(e: MouseEvent){
                enterCount++
            }
        })
    }
    
  • 对象申明:Kotlin 中我们可以方便的通过对象声明来获得一个单例,对象声明是延迟加载的, 在第一次使用的时候被初始化,对象声明不是一个表达式,不能用在赋值语句的右边,对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中,

    object MyInfo: Info("submit"),IClickListener {
    
        override fun doClick() {
            println("MyInfo do click, $text") // Log: MyInfo do click, , submit
        }
    }
    
    fun main(args: Array<String>) {
    
        MyInfo.doClick()
    }
    //当对象声明在另一个类的内部时,这个类的实例并不能直接访问对象申明内部,而只能通过类名来访问,同样该对象也不能直接访问到外部类的方法和变量
    class Site {
        var name = "菜鸟教程"
        object DeskTop{
            var url = "www.runoob.com"
            fun showName(){
                print{"desk legs $name"} // 错误,不能访问到外部类的方法和变量
            }
        }
    }
    fun main(args: Array<String>) {
        var site = Site()
        site.DeskTop.url // 错误,不能通过外部类的实例访问到该对象
        Site.DeskTop.url // 正确
    }
    
  • 伴随(生)对象:相当于静态内部类+该类的静态属性,所在的类被加载,伴生对象被初始化(和 Java 的静态初始是对应):

    class Books(var name: String, val page: Int) {
        companion object ComBooks{
            val a : Int = 10
            fun doNote() {
                println("do note")
            }
        }
    }
    
    fun main(args: Array<String>) {
        Books.ComBooks.doNote()
    
        println("Book.a = ${Books.ComBooks.a}")
    
        println("-------------")
    
        Books.doNote()
    
    }
    
    // Log
    do note
    Book.a = 10
    -------------
    do note
    //伴随对象的成员可以通过类名做限定词直接使用:
    class MyClass {
        companion object Factory {
            fun create(): MyClass = MyClass()
        }
    }
    val instance = MyClass.create()
    //在使用了 companion 关键字时,伴随对象的名字可以省略:
    class MyClass {
        companion object {
    
        }
    }
    //尽管伴随对象的成员很像其它语言中的静态成员,但在运行时它们任然是真正类的成员实例,比如可以实现接口:
    interface Factory<T> {
        fun create(): T
    }
    
    class MyClass {
        companion object : Factory<MyClass> {
            override fun create(): MyClass = MyClass()
        }
    }
    //如果你在 JVM 上使用 @JvmStatic 注解,你可以有多个伴随对象生成为真实的静态方法和属性
    

属性字段相关

  • 备用字段:Kotlin中不能有field,但在自定义getter/setter的时候需要直接访问属性而不是又通过getter/settter来取值赋值(循环调用)。Kotlin自动提供一个备用字段(field),通过它可以直接访问属性,没有使用备用字段时不生成备用字段(使用了setter就会生成),:

    //使用field关键字
    public var fieldProp = ""
        get() = field
        set(value) {
            field = value;
        }
    //不生成:
    val isEmpty: Boolean
        get() = this.size == 0
    //生成:
    val Foo.bar = 1
    
  • 备用属性:功能与备用字段类似。:

    private var _table: Map<String, Int>? = null
    public val table: Map<String, Int>
        get() {
            if (_table == null) {
                _table = HashMap() // 参数类型是自动推导
            }
            return _table ?: throw AssertionError("Set to null by another thread")
        }
    

    Kotlin可以像python(@property)一样把方法变成属性调用,Kotlin是定义一个属性复写get()方法返回某个对象中其他的计算出来的值。

编译时常量

  • 相当于java static finial xxx,而val 只是fInal ,一个编译时常量,一个运行时常量。使用const必须:

    • 在kt文件中(类之外,Top-level)或在object{}中
    • 必须是基本类型或String
    • 必须没有自定义getter
    const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
    @Deprected(SUBSYSTEM_DEPRECATED) fun foo() { ... }
    

延迟初始化属性

  • 当定义属性时没有使用 ? ,那么说明是一个非空属性,这时必须要初始化,如果想在后面的方法中再去赋值要加上lateinit。:

    public class MyTest {
        lateinit var subject: TestSubject
    
        @SetUp fun setup() {
            subject = TestSubject()
        }
    
        @Test fun test() {
            subject.method()
        }
    }
    //这个修饰符只能够被用在类的 var 类型的可变属性定义中,不能用在构造方法中.并且属性不能有自定义的 getter 和 setter访问器.这个属性的类型必须是非空的,同样也不能为一个基本类型.在一个lateinit的属性初始化前访问他,会导致一个特定异常,告诉你访问的时候值还没有初始化
    

复写属性

  • 属性可复写,在主构造函数中就可使用override关键字作为属性声明。

代理(委托)模式

  • 类代理:
    Kotlin 在语法上支持代理 ,Derived 类可以继承 Base 接口并且指定一个对象代理它全部的公共方法:

    interface Base {
        fun print()
    }
    
    class BaseImpl(val x: Int) : Base {
        override fun print() { printz(x) }
    }
    
    class Derived(b: Base) : Base by b
    
    fun main() {
        val b = BaseImpl(10)
        Derived(b).print()
    }
    //在 Derived 的父类列表中的 by 从句会将 b 存储在 Derived 内部对象,并且编译器会生成 Base 的所有方法并转给 b
    

  • 代理属性:
    所谓的委托属性,就是对其属性值的操作不再依赖于其自身的getter()/setter()方法,是将其托付给一个代理类,从而每个使用类中的该属性可以通过代理类统一管理,再也不用在每个类中,对其声明重复的操作方法。语法:

    val/var <property name>: <Type> by <expression>
    //var/val:属性类型(可变/只读)
    //name:属性名称
    //Type:属性的数据类型
    //expression:代理类
    

    使用场景:

    • 延迟加载属性(lazy property): 属性值只在初次访问时才会计算
    • 可观察属性(observable property): 属性发生变化时, 可以向监听器发送通知
    • 将多个属性保存在一个 map 内, 而不是保存在多个独立的域内

    Kotlin标准库中已实现的代理:

    • 延迟加载(Lazy):lazy()是一个函数, 接受一个Lambda表达式作为参数, 返回一个Lazy类型的实例,这个实例可以作为一个委托, 实现延迟加载属性(lazy property): 第一次调用 get() 时, 将会执行 lazy() 函数受到的Lambda 表达式,然后会记住这次执行的结果, 以后所有对 get() 的调用都只会简单地返回以前记住的结果:

      val no: Int by lazy {
          200
      }
      
      val c = 200
      
      fun main(args: Array<String>) {
      
          val b = 200
      
          println(no) // Log : 200
          println(no) // Log : 200
      }
      
      

      注意:

      • var类型属性不能设置为延迟加载属性,因为在lazy中并没有setValue(…)方法
      • lazy操作符是线程安全的。如果在不考虑多线程问题或者想提高更多的性能,也可以使用 lazy(LazyThreadSafeMode.NONE){ … },lazy的三个参数为:
        • SYNCHRONIZED:锁定,用于确保只有一个线程可以初始化[Lazy]实例。
        • PUBLICATION:初始化函数可以在并发访问未初始化的[Lazy]实例值时调用几次,,但只有第一个返回的值将被用作[Lazy]实例的值。
        • NONE:没有锁用于同步对[Lazy]实例值的访问; 如果从多个线程访问实例,是线程不安全的。此模式应仅在高性能至关重要,并且[Lazy]实例被保证永远不会从多个线程初始化时使用。
    • 可观察属性(Observable):Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler).这种形式的委托,采用了观察者模式,其会检测可观察属性的变化,当被观察属性的setter()方法被调用的时候,响应器(handler)都会被调用(在属性赋值处理完成之后)并自动执行执行的lambda表达式,同时响应器会收到三个参数:被赋值的属性, 赋值前的旧属性值, 以及赋值后的新属性值。:

      var name: String by Delegates.observable("wang", {
          kProperty, oldName, newName ->
          println("kProperty:${kProperty.name} | oldName:$oldName | newName:$newName")
      })
      
      fun main(args: Array<String>) {
      
          println("name: $name") // Log:nam:wang
      
          name = "zhang" // Log:kProperty:name | oldName:wang | newName:zhang
      
          name = "li" // Log:kProperty:name | oldName:zhang | newName:li
      }
      //Delegates.observable(wang, hanler),完成了两项工作,一是,将name初始化(name=wang);二是检测name属性值的变化,每次变化时,都会打印其赋值前的旧属性值, 以及赋值后的新属性值。
      ​```
      
    • Vetoable:Delegates.vetoable()函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler),是可观察属性(Observable)的一个特例,不同的是在响应器指定的自动执行执行的lambda表达式中在保存新值之前做一些条件判断,来决定是否将新值保存。:

      var name: String by Delegates.vetoable("wang", {
          kProperty, oldValue, newValue ->
          println("oldValue:$oldValue | newValue:$newValue")
          newValue.contains("wang")
      })
      
      fun main(args: Array<String>) {
      
          println("name: $name")
          println("------------------")
          name = "zhangLing" 
          println("name: $name") 
          println("------------------")
          name = "wangBing" 
          println("name: $name") 
      }
      
      //Log 
      name: wang
      ------------------
      oldValue:wang | newValue:zhangLing
      name: wang
      ------------------
      oldValue:wang | newValue:wangBing
      name: wangBing
      ​```
      
    • Not Null:在实际开发时,我们可能会设置可为null的var类型属性,在我们使用它时,肯定是对其赋值,假如不赋值,必然要报NullPointException.一种解决方案是,我们可以在使用它时,在每个地方不管是不是null,都做null检查,这样我们就保证了在使用它时,保证它不是null。这样无形当中添加了很多重复的代码。在Kotlin中,用委托可以不用去写这些重复的代码,Not Null委托会含有一个可null的变量并会在我们设置这个属性的时候分配一个真实的值。如果这个值在被获取之前没有被分配,它就会抛出一个异常。

      class App : Application() {
          companion object {
              var instance: App by Delegates.notNull()
          } 
      
          override fun onCreate() {
              super.onCreate()
              instance = this
          }
      }
      
    • 将多个属性保存在一个map内:使用Gson解析Json时,可以获取到相应的实体类的实例,当然该实体类的属性名称与Json中的key是一一对应的。在Kotlin中,存在这么一种委托方式,类的构造器接受一个map实例作为参数,将map实例本身作为属性的委托,属性的名称与map中的key是一致的,也就是意味着我们可以很简单的从一个动态地map中创建一个对象实例:

      class User(val map: Map<String, Any?>) {
          val name: String by map
          val age: Int by map
      }
      
      fun main(args: Array<String>) {
      
          val user = User(mapOf(
                  "name" to "John Doe",
                  "age" to 25
          ))
      
          println(user.name) // 打印结果为: "John Doe"
          println(user.age) // 打印结果为: 25
      }
      //委托属性将从这个 map中读取属性值(使用属性名称字符串作为 key 值)。
      //如果不用只读的 Map , 而改用值可变的 MutableMap , 那么也可以用作 var 属性的委托。:
      class User(val map: MutableMap<String, Any?>) {
          val name: String by map
          val age: Int by map
      }
      
      fun main(args: Array<String>) {
      
          var map:MutableMap<String, Any?> = mutableMapOf(
                  "name" to "John Doe",
                  "age" to 25)
      
          val user = User(map)
      
          println(user.name) // 打印结果为: "John Doe"
          println(user.age) // 打印结果为: 25
      
          println("--------------")
          map.put("name", "Green Dao")
          map.put("age", 30)
      
          println(user.name) // 打印结果为: Green Dao
          println(user.age) // 打印结果为: 30
      
      }
      
    • 属性委托的前提条件:getValue(),setValue()。自定义委托必须要实现:ReadOnlyProperty和ReadWriteProperty。取决于我们被委托的对象是val还是var,如:

      public interface ReadWriteProperty<in R, T> {
          public operator fun getValue(thisRef: R, property: KProperty<*>): T
          public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
      }
      //定义一个NotNullVar
      private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> {
          private var value: T? = null
      
          public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
              return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
          }
      
          public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
              this.value = value
          }
      }
      //第一个thisRef表示持有该对象的对象,
      //第二个参数 property 是该值的类型,
      //第三个参数 value 就是属性的值了
      

密封类

  • 密封类:类的扩展,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例,虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明,间接的子类不受限制。密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员。密封类不允许有非-private 构造函数(其构造函数默认为 private)。使用密封类的关键好处在于使用 when 表达式 的时候,能够验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了:

    sealed class Expr
    data class Const(val number: Double) : Expr()
    data class Sum(val e1: Expr, val e2: Expr) : Expr()
    object NotANumber : Expr()
    
    fun eval(expr: Expr): Double = when(expr) {
        is Const -> expr.number
        is Sum -> eval(expr.e1) + eval(expr.e2)
        NotANumber -> Double.NaN
        // 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
    }
    

接口

  • 与java8类似可有抽象方法与实现方法,不可保存状态,属性必须是抽象的或唯一值的(无备用属性)。

扩展

  • 不需要在类中去添加方法,在外部就可以给任何地方的类添加我们想要的方法,替换掉java中的FileUtil,xxxUtil等如:

    Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))
    //变成
    list.swap(list.binarySearch(otherList.max()), list.max())
    

    扩展是被静态解析的:扩展实际上并没有修改它所扩展的类,只是让这个类的实例对象能够通过"."调用新的函数。需要强调的是扩展函数是静态分发的,举个例子,它们并不是接受者类型的虚拟方法。这意味着扩展函数的调用是由发起函数调用的表达式的(对象)类型决定的,而不是在运行时动态获得的表达式的(对象)类型决定。比如:

    open class C 
    
    class D: C()
    
    fun C.foo() = "c" 
    
    fun D.foo() = "d"
    
    fun printFoo(c: C) { 
        println(c.foo())
    } 
    
    printFoo(D())
    //输出 c,因为这里扩展函数的调用决定于声明的参数 c 的类型,也就是 C。
    
    

    如果有同名同参数的成员函数和扩展函数,调用的时候会使用成员函数,比如:

    class C {
        fun foo() { println("member") }
    
    }
    fun C.foo() { println("extension") }
    C().foo()
    //输出"member",而不是"extension"
    //可以通过不同的函数签名的方式重载函数的成员函数:
    fun C.foo(i:Int) { println("extention") }
    C().foo(1)
    //输出 “extentions”。
    

    扩展的域:大多数时候我们在 top level 定义扩展(就在包下面直接定义):

    package foo.bar
    fun Baz.goo() { ... }
    //为了在除声明的包外使用这个扩展,我们需要在别的文件中使用时导入:
    //-------------------------------------------------//
    package com.example.usage
    
    import foo.bar.goo // 导入所有名字叫 "goo" 的扩展
    
                        // 或者
    
    import foo.bar.* // 导入foo.bar包下得所有数据
    
    fun usage(baz: Baz) {
        baz.goo()
    }
    

  • 函数扩展:

    fun <T> MutableList<T>.swap(x: Int, y: Int) {
      val tmp = this[x] // 'this' corresponds to the list
      this[x] = this[y]
      this[y] = tmp
    }
    //this 关键字对应接收者对象(MutableList<T>)
    //使用:
    val l = mutableListOf(1, 2, 3)
    l.swap(0, 2)// 在 `swap()` 函数中 `this` 持有的值是 `l`
    
  • 可空的接收者:
    使用空接收者类型进行定义。这样的扩展使得,即使是一个空对象仍然可以调用该扩展,然后在扩展的内部进行 this == null 的判断。这样你就可以在 Kotlin 中任意调用 toString() 方法而不进行空指针检查:空指针检查延后到扩展函数中完成:

    fun Any?.toString(): String {
        if (this == null) return "null"
        // 在空检查之后,`this` 被自动转为非空类型,因此 toString() 可以被解析到任何类的成员函数中
        return toString()
    }
    

    T.所以扩展是用.来使用的,如Kotlin的to函数就是A to B 以空格使用。

  • 属性扩展

    注意,由于扩展并不会真正给类添加了成员属性,因此也没有办法让扩展属性拥有一个备份字段.这也是为什么初始化函数不允许有扩展属性。扩展属性只能够通过明确提供 getter 和 setter方法来进行定义:

    //正确:
    val <T> List<T>.lastIndex:  Int
        get() = size-1
    //错误:
    val Foo.bar = 1 //error: initializers are not allowed for extension properties
    

  • 伴随对象扩展

    class MyClass {
        companion object {} 
    }
    fun MyClass.Companion.foo(){
    
    }
    //调用
    MyClass.foo()
    

数据类

我们经常创建一个只保存数据的类。在这样的类中一些函数只是机械的对它们持有的数据进行,如从服务端返回的Json字符串对象映射成Java类。data类使用:

data class 类名(var param1 :数据类型,...){}
data class 类名 可见性修饰符 constructor(var param1 : 数据类型 = 默认值,...)
//data为声明数据类的关键字,必须书写在class关键字之前。
//在没有结构体的时候,大括号{}可省略。
//构造函数中必须存在至少一个参数,并且必须使用val或var修饰。这一点在下面数据类特性中会详细讲解。
//参数的默认值可有可无。(若要实例一个无参数的数据类,则就要用到默认值)
// 定义一个名为Person的数据类:
data class Preson(var name : String,val sex : Int, var age : Int)

data类必须满足的条件:

  • 主构造函数需要至少有一个参数
  • 主构造函数的所有参数需要标记为 val 或 var;
  • 数据类不能是抽象、开放、密封或者内部的;
  • 数据类是可以实现接口的,如(序列化接口),同时也是可以继承其他类的,如继承自一个密封类。

约定俗成的规定:当构造函数中的参过多时,为了代码的阅读性,一个参数的定义占据一行。

编辑器为我们做的事情:

  • 生成equals()函数与hasCode()函数
  • 生成toString()函数,由类名(参数1 = 值1,参数2 = 值2,....)构成
  • 由所定义的属性自动生成component1()、component2()、...、componentN()函数,其对应于属性的声明顺序。
  • copy()函数。修改部分属性,但是保持其他不变。

copy函数的使用:

data class User(val name : String, val pwd : String)

val mUser = User("kotlin","123456")
println(mUser)
val mNewUser = mUser.copy(name = "new Kotlin")
println(mNewUser)

标准库提供的data类: Pair 和 Triple,源码如下:

@file:kotlin.jvm.JvmName("TuplesKt")
package kotlin

// 这里去掉了源码中的注释
public data class Pair<out A, out B>(
        public val first: A,
        public val second: B) : Serializable {
    
    // toString()方法
    public override fun toString(): String = "($first, $second)"
}

// 转换
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

// 转换成List集合
public fun <T> Pair<T, T>.toList(): List<T> = listOf(first, second)

// 这里去掉了源码中的注释
public data class Triple<out A, out B, out C>(
        public val first: A,
        public val second: B,
        public val third: C ) : Serializable {

    // toString()方法
    public override fun toString(): String = "($first, $second, $third)"
}

// 转换成List集合
public fun <T> Triple<T, T, T>.toList(): List<T> = listOf(first, second, third)

泛型

  • 两种型变:

    • 协变:当A ≦ B时,如果有f(A) ≦ f(B),那么f叫做协变;

    • 逆变:当A ≦ B时,如果有f(B) ≦ f(A),那么f叫做逆变;

      其余为不变。

    协变,逆变,不变来原于子类可以安全的向上转型为父类。

  • 不变(java的泛型<T>是不变的):

    ArrayList<Number> list = new ArrayList<Integer>(); //type mismatch  
    
  • 协变:

    List<? extends Number> list001 = new ArrayList<Integer>();  
    List<? extends Number> list002 = new ArrayList<Float>(); 
    Number n1=list001.get(0);
    Number n2=list002.get(0);
    

    ​ “? extends Number”则表示通配符”?”的上界为Number,换句话说就是,“? extends Number”可以代表Number或其子类,但代表不了Number的父类(如Object),因为通配符的上界是Number。于是有“? extends Number” ≦ Number,则List<? extends Number> ≦ List< Number >。但是这里不能向list001、list002添加除null以外的任意对象。可以这样理解一下,List<Integer>可以添加Interger及其子类,List<Float>可以添加Float及其子类,List<Integer>、List<Float>都是List<? extends Animal>的子类型,如果能将Float的子类添加到List<? extends Animal>中,就说明Float的子类也是可以添加到List<Integer>中的,显然是不可行。故java为了保护其类型一致,禁止向List<? extends Number>添加任意对象,不过却可以添加null。

  • 逆变:

    List<? super Number> list = new ArrayList<>();  
    List<? super Number> list001 = new ArrayList<Number>();  
    List<? super Number> list002 = new ArrayList<Object>();  
    list001.add(new Integer(3));  
    list002.add(new Integer(3));  
    

    “? super Number” 则表示通配符”?”的下界为Number。为了保护类型的一致性,因为“? super Number”可以是Object或其他Number的父类,因无法确定其类型,也就不能往List<? super Number >添加Number的任意父类对象。但是可以向List<? super Number >添加Number及其子类。

  • PECS(《Effective Java》,producer-extends, consumer-super):协变只能取(生产者),逆变只能写(消费者),如java的几个api:

    public class Stack<E>{  
        public Stack();  
        public void push(E e):  
        public E pop();  
        public boolean isEmpty();  
    }  
    //push all
    // Wildcard type for parameter that serves as an E producer  
    public void pushAll(Iterable<? extends E> src) {  
        for (E e : src)  
            push(e);  
    } 
    //pop all
    // Wildcard type for parameter that serves as an E consumer  
    public void popAll(Collection<? super E> dst) {  
        while (!isEmpty())  
            dst.add(pop());  
    }  
    // java.util.Collections的copy方法
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {  
        int srcSize = src.size();  
        if (srcSize > dest.size())  
            throw new IndexOutOfBoundsException("Source does not fit in dest");  
      
        if (srcSize < COPY_THRESHOLD ||  
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {  
            for (int i=0; i<srcSize; i++)  
                dest.set(i, src.get(i));  
        } else {  
            ListIterator<? super T> di=dest.listIterator();  
            ListIterator<? extends T> si=src.listIterator();  
            for (int i=0; i<srcSize; i++) {  
                di.next();  
                di.set(si.next());  
            }  
        }  
    }  
    
  • Kotlin 用out ,in修饰符使协变与逆变使用起来更方便,out表示只生成,in表示只消费,称之为声明处变型。这与 Java 中的使用处变型相反:

    abstract class Source<out T> {
        abstract fun nextT(): T
    }
    
    fun demo(strs: Source<String>) {
        val objects: Source<Any> = strs // This is OK, since T is an out-parameter
        // ...
    }
    //-------------------------//
    abstract class Comparable<in T> {
        abstract fun compareTo(other: T): Int
    }
    
    fun demo(x: Comparable<Number>) {
        x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
        // Thus, we can assign x to a variable of type Comparable<Double>
        val y: Comparable<Double> = x // OK!
    }
    
  • 类型投影

    使用处变型:类型投影。有些类 不能 限制它只返回 T,如Array,T既要返回又要在参数中消费:

    class Array<T>(val size: Int) {
        fun get(index: Int): T { /* ... */ }
        fun set(index: Int, value: T) { /* ... */ }
    }
    //这个类既不能是协变的也不能是逆变的,这会在一定程度上降低灵活性。考虑下面的函数形式:
    fun copy(from: Array<out Any>, to: Array<Any>) {
     // ...
    }
    
  • 星投影

    有时你对类型参数一无所知,但任然想安全的使用它。保险的方法就是定一个该范型的投影,每个该范型的正确实例都将是该投影的子类,Foo<*>

  • 范型约束

    上界:最常用的类型约束是上界,在 Java 中对应 extends关键字,这里的上界只是约束在给泛型定型时要满足的条件。

    fun <T : Comparable<T>> sort(list: List<T>) {
        // ...
    }
    sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable<Int>
    sort(listOf(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>
    
    

    默认的上界是 Any?。在尖括号内只能指定一个上界。如果要指定多种上界,需要用 where 语句指定:

    fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
        where T : Comparable,
              T : Cloneable {
      return list.filter { it > threshold }.map { it.clone() }
    }
    

嵌套类

  • 嵌套类,与java的静态内部类相似:

    class Outer {
        private val bar: Int = 1
        class Nested {
            fun foo() = 2
        }
    }
    
    val demo = Outer.Nested().foo() //==2
    
  • 内部类,相当与java的内部类,持有一个外部类的引用,不能单独使用:

    class Outer {
        private val bar: Int = 1
        inner class Inner {
            fun foo() = bar
        }
    }
    
    val demo = Outer().Inner().foo() //==1
    
  • 匿名内部类,通过对象表达式创建:

    window.addMouseListener(object: MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            // ...
        }
    
        override fun mouseEntered(e: MouseEvent) {
            // ...
        }
    })
    //如果对象是函数式的 java 接口的实例(比如只有一个抽象方法的 java 接口),你可以用一个带接口类型的 lambda 表达式创建它
    val listener = ActionListener { println("clicked") }
    

函数

  • 函数参数

    • 标准参数:

    fun powerOf(number: Int, exponent: Int) {
    ...
    }

    
    - 默认参数,函数参数可以设置默认值,当调用函数时参数被忽略会使用默认值。这样相比其他语言可以减少重载:
    
    ```kotlin
    fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size ) {
    ...
    }
    
    • 命名参数:在调用函数时可以用参数的命名来赋值参数。这对于那种有大量参数的函数很方便:

      fun reformat(str: String, normalizeCase: Boolean = true,upperCaseFirstLetter: Boolean = true,
                   divideByCamelHumps: Boolean = false,
                   wordSeparator: Char = ' ') {
      ...
      }
      //调用:
      //使用默认参数:
      reformat(str)
      //调用非默认参数:
      reformat(str, true, true, false, '_')
      //使用命名参数:
      reformat(str,
          normalizeCase = true,
          uppercaseFirstLetter = true,
          divideByCamelHumps = false,
          wordSeparator = '_'
        )
      //不需要全部参数:
      reformat(str, wordSeparator = '_')
      //注意,命名参数语法不能够被用于调用Java函数中,因为Java的字节码不能确保方法参数命名的不变性
      

      默认参数可能只是给参数一个默认值,而命名参数则给参数一个有意义的名字。

    • 不带返回值的参数:

      如果函数不会返回任何有用值,那么他的返回类型就是 Unit .Unit 是一个只有唯一值Unit的类型.这个值并不需要被直接返回:

      fun printHello(name: String?): Unit {
          if (name != null)
              println("Hello ${name}")
          else
              println("Hi there!")
          // `return Unit` or `return` is optional
      }
      //Unit 返回值也可以省略:
      fun printHello(name: String?) {
          ...
      }
      
      
    • 变长参数:

      函数的参数(通常是最后一个参数)可以用 vararg 修饰符进行标记:

      fun <T> asList(vararg ts: T): List<T> {
          val result = ArrayList<T>()
          for (t in ts)
              result.add(t)
          return result
      }
      //标记后,允许给函数传递可变长度的参数:
      val list = asList(1, 2, 3)
      //只有一个参数可以被标注为 vararg 。加入vararg并不是列表中的最后一个参数,那么后面的参数需要通过命名参数语法进行传值,再或者如果这个参数是函数类型,就需要通过lambda法则.
      
      //当调用变长参数的函数时,我们可以一个一个的传递参数,比如 asList(1, 2, 3),或者我们要传递一个 array 的内容给函数,我们就可以使用 * 前缀操作符:
      val a = array(1, 2, 3)
      val list = asList(-1, 0, *a, 4)
      

      各种类型参数定义与使用与Python相似

  • 单表达式函数:

    //当函数只返回单个表达式时,大括号可以省略并在 = 后面定义函数体:
    fun double(x: Int): Int = x*2
    //在编译器可以推断出返回值类型的时候,返回值的类型可以省略:
    fun double(x: Int) = x * 2
    

  • 函数范围

    Kotlin 中可以在文件顶级声明函数,这就意味者你不用像在Java,C#或是Scala一样创建一个类来持有函数。除了顶级函数,Kotlin 函数可以声明为局部的,作为成员函数或扩展函数:

    //局部函数
    fun dfs(graph: Graph) {
      fun dfs(current: Vertex, visited: Set<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
          dfs(v, visited)
      }
    
      dfs(graph.vertices[0], HashSet())
    }
    //局部函数可以访问外部函数的局部变量(比如闭包)
    fun dfs(graph: Graph) {
        val visited = HashSet<Vertex>()
        fun dfs(current: Vertex) {
            if (!visited.add(current)) return 
            for (v in current.neighbors)
                dfs(v)
        }
        dfs(graph.vertices[0])
    }
    //局部函数甚至可以返回到外部函数
    fun reachable(from: Vertex, to: Vertex): Boolean {
        val visited = HashSet<Vertex>()
        fun dfs(current: Vertex) {
            if (current == to) return@reachable true
            if (!visited.add(current)) return
            for (v  in current.neighbors)
                dfs(v)
        }
        dfs(from)
        return false
    }
    
  • 成员函数

    跟java一样,类中的成员。

  • 泛型函数

    跟java一样。

  • 尾递归函数

    Kotlin 支持函数式编程的尾递归。这个允许一些算法可以通过循环而不是递归解决问题,从而避免了栈溢出。当函数被标记为 tailrec 时,编译器会优化递归,并用高效迅速的循环代替它:

    tailrec fun findFixPoint(x: Double = 1.0): Double 
        = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
    //使用 tailrec 修饰符必须在最后一个操作中调用自己。在递归调用代码后面是不允许有其它代码的,并且也不可以在 try/catch/finall 块中进行使用。当前的尾递归只在 JVM 的后端中可以用
    
  • 高阶函数

    高阶函数就是可以接受函数作为参数或者返回一个函数的函数:

    fun <T> lock(lock: Lock, body: () -> T ) : T {
        lock.lock()
        try {
            return body()
        }
        finally {
            lock.unlock()
        }
    }
    //body 是一个类型为 () -> T 的函数,可以这样使用
    fun toBeSynchroized() = sharedResource.operation()
    val result = lock(lock, ::toBeSynchroized)
    //更方便的是传一个字面函数(lambda表达式)
    val result = lock(lock, {
    sharedResource.operation() })
    //在 kotlin 中有一个约定,如果某一个函数的最后一个参数是函数,并且你向那个位置传递了一个 lambda 表达式,那么,你可以在括号外面定义这个 lambda 表达式:
    lock (lock) {
        sharedResource.operation()
    }
    //高阶函数map:
    fun <T, R> List<T>.map(transform: (T) -> R):
    List<R> {
        val result = arrayListOf<R>()
        for (item in this)
            result.add(transform(item))
        return result
    }
    //调用:
    val doubled = ints.map {it -> it * 2}
    //如果字面函数只有一个参数,则声明可以省略,名字就是 it :
    ints.map {it * 2}
    //这样就可以写LINQ-风格的代码了:
    strings.filter{ it.length == 5 }.sortedBy{ it }.map{ it.toUpperCase() }
    
    
  • 字面函数和函数表达式(lambda表达式),字面函数或函数表达式就是一个 "匿名函数",也就是没有声明的函数,但立即作为表达式传递下去:

    max(strings, {a, b -> a.length < b.length })
    //max 函数就是一个高阶函数,它接受函数作为第二个参数。第二个参数是一个表达式所以本生就是一个函数,即字面函数。作为一个函数,相当于:
    fun compare(a: String, b: String) : Boolean = a.length < b.length
    

    如果只有一个参数lambda中可以不写参数变量,直接用it表示参数,如:

    {it.length}
    
  • Lambda表达式接收器:

    (函数字面量接收器,在定义高阶函数参数时使用)是上面两者的结合——一个以指定接收器的扩展函数为参数的高阶函数。所以在我们传递的Lambda表达式中我们可以直接访问接收器的公共方法和属性(在接受器的上下文环境中),就好像在接收器内部一样:

    inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> Unit) {
        val fragmentTransaction = beginTransaction()
        fragmentTransaction.func()
        fragmentTransaction.commit()
    }
    //或:
    inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> FragmentTransaction) {
        beginTransaction().func().commit()
    }
    //这就是FragmentManager的扩展函数,接收一个Lambda表达式接收器作为参数,FragmentTransaction作为接收器
    
    //调用
    supportFragmentManager.inTransaction {
        //remove(fragmentA)    
        add(R.id.frameLayoutContent, fragmentB)
    }
    //需要说明的是在Lambda表达式中我们调用FragmentTransaction的方法如add或者remove时并没有使用修饰符,因为这是对FragmentTransaction的扩展函数.
    

  • 函数类型

    一个函数要接受另一个函数作为参数,我们得给它指定一个类型。比如上面的 max:

    fun max<T>(collection: Collection<out T>, less: (T, T) -> Boolean): T? {
        var max: T? = null
        for (it in collection)
            if (max == null || less(max!!, it))
                max = it
        return max
    }
    //参数 less 是 (T, T) -> Boolean类型,也就是接受俩个 T 类型参数返回一个 Boolean:如果第一个参数小于第二个则返回真。在函数体第四行, less 是用作函数。
    //一个函数类型可以像上面那样写,也可有命名参数
    val compare: (x: T,y: T) -> Int = ...
    
  • 函数文本语法

    函数文本的完全写法:

    val sum = {x: Int,y: Int -> x + y}
    //函数文本总是在大括号里包裹着,在完全语法中参数声明是在括号内,类型注解是可选的,函数体是在 -> 之后,像下面这样:
    val sum: (Int, Int) -> Int = {x, y -> x+y }
    //函数文本有时只有一个参数。如果 kotlin 可以从它本生计算出签名,那么可以省略这个唯一的参数,并会通过 it 隐式的声明它
    ints.filter {it > 0}//这是 (it: Int) -> Boolean  的字面意思
    //注意如果一个函数接受另一个函数做为最后一个参数,该函数文本参数可以在括号内的参数列表外的传递
    
  • 函数表达式

    指定返回值的函数在大多数情形中是不必要的,因为返回值是可以自动推断的。然而,如果你需要自己指定,可以用函数表达式来做:

    fun(x: Int, y: Int ): Int = x + y
    //函数表达式很像普通的函数声明,除了省略了函数名。它的函数体可以是一个表达式(像上面那样)或者是一个块:
    fun(x: Int, y: Int): Int {
        return x + y
    }
    //参数以及返回值和普通函数是一样的,如果它们可以从上下文推断出参数类型,则参数类型可以省略:
    ints.filter(fun(item) = item > 0)
    
  • 闭包

    一个字面函数或者表达式函数可以访问闭包,即访问自身范围外的声明的变量。不像 java 那样在闭包中的变量是被捕获修改的:

    var sum = 0
    
    ints.filter{it > 0}.forEach {
        sum += it
    }
    print(sum)
    
  • 函数表达式扩展

    表达式函数的扩展和普通的扩展区别是它有接收类型的规范:

    val sum = fun Int.(other: Int): Int = this + other
    //接收类型必须在表达式函数中明确指定,但字面函数不用。字面函数可以作为扩展函数表达式,但只有接收类型可以通过上下文推断出来,表达式函数的扩展类型是一个带接收者的函数:
    sum : Int.(other: Int) -> Int
    //使用
    1.sum(2)
    

    字面函数(lambda)用->分隔函数体,函数表达式用=分隔函数体。

  • 内联函数

    编译器将使用函数的定义体来替代函数调用语句,这种替代行为发生在编译阶段而非程序运行阶段,也就是说把被调用的函数体复制到调用处,好处:

    • 减少了方法调用,压栈,出栈的成本。
    • 在kotlin中,函数就是对象,当你调用某个函数的时候,就会创建相关的对象,内存的分配,虚拟调用都有开销,内联可以减少成本。

    使用:

    inline fun <T> check(lock: Lock, body: () -> T): T {
            lock.lock()
            try {
                return body()
            } finally {
                lock.unlock()
            }
        }
    
    //---------调用----------------//
    fun run() {
         check(l, {"我是lambda方法体"})//l是一个Lock对象
    }
    //编译器会把调用处换成这样:
    fun run() {
          l.lock()
            try {
                return "我是lambda方法体"
            } finally {
                l.unlock()
            }
    }
    //如一个函数是inline的,那么参数里的函数,lambda也默认为inline的。如果要部分参数为非inline,可以使用noinline关键字:
    inline fun doSomething(a:Int,b:Int,noinline doOther:(a:Int,b:Int)->Int){
        //...
    }
    
  • 非局部返回

    Kotlin在lambda中不能直接使用return,要使用return配合标签,但如果内联,则可以在lambda中直接使用return,该return直接作用于调用者函数,也就是说直接作用在调用的地方,谁调用退出谁。其他内联函数中的return一样(return也被复制到了调用内联函数的函数体里)。如果要只退出lambda,可以使用return@xxx。

  • 内联属性

    对属性来说,我们会有get,set的方法来操作这个属性。
    get,set就是个函数,我们可以标识他们为内联函数:

    val foo: Foo
        inline get() = Foo()
    
    var bar: Bar
        get() = ...
        inline set(v) { ... }
    //
    inline var bar: Bar
        get() = ...
        set(v) { ... }
    
  • 实例化参数类型

    有时我们需要访问传递过来的类型,把它作为参数:

    fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
        var p = parent
        while (p != null && !clazz.isInstance(p)) {
            p = p?.parent
        }
        @suppress("UNCHECKED_CAST")
        return p as T
    }
    //调用
    myTree.findParentOfType(javaClass<MyTreeNodeType>() )
    //
    myTree.findParentOfType(MyTreeNodeType::class.java)
    //我们想要的仅仅是给这个函数传递一个类型,如果即像下面这样就很方便:
    myTree.findParentOfType<MyTreeNodeType>()
    //为了达到这个目的,内联函数支持具体化的类型参数申明 reified
    inline fun <reified T> TreeNode.findParentOfType(): T? {
        var p = parent
        while (p != null && p !is T) {
            p = p?.parent
        }
        return p as T
    }
    

    我们用 refied 修饰符检查类型参数,既然它可以在函数内部访问了,也就基本上接近普通函数了。因为函数是内联的,所以不许要反射,像 !is `as`这样的操作都可以使用。同时,我们也可以像上面那样调用它了 myTree.findParentOfType<MyTreeNodeType>() 。普通的函数(没有标记为内联的)不能有实例化参数。

    在很多情况下会使用反射访问类型数据,我们仍然可以使用实例化的类型参数 javaClass() 来访问它:

    inline fun methodsOf<reified T>() = javaClass<T>().getMethods()
    
    fun main(s: Array<String>) {
        println(methodsOf<String>().joinToString('\n'))
    }
    

协程

。。。

空安全

  • Kotlin 类型系统致力于消灭空引用(NPE),在 Kotlin 类型系统中可以为空和不可为空的引用是不同的,属性默认是要赋初值的,不能为空:

    var a: String ="abc"
    a = null //编译错误
    //允许为空,我们必须把它声明为可空的变量
    var b: String? = "abc"
    b = null
    //调用 a 的方法,而不用担心 NPE 异常:
    val l = a.length()
    //使用 b 调用同样的方法就可能报错
    val l = b.length() //错误:b 可能为空
    
  • 使用可空属性(?)时的四种方式:

    • 在条件中检查 null:

      val l = if (b != null) b.length() else -1
      //更复杂的条件:
      if (b != null && b.length() >0)
        print("Stirng of length ${b.length}")
      else
        print("Empty string")
      
    • 安全调用,使用安全操作符,?.

      b?.length()
      //如果 b 不为空则返回长度,否则返回空。这个表达式的的类型是 Int?,安全调用在链式调用是是很有用的。比如,如果 Bob 是一个雇员可能分配部门(也可能不分配),如果我们想获取 Bob 的部门名作为名字的前缀,就可以这样做:
      bob?.department?.head?.name
      //这样的调用链在任何一个属性为空都会返回空
      
    • Elvis 操作符,?:

      val l = b.length()?: -1
      //如if表达式:
      val l: Int = if (b != null) b.length() else -1
      
      //如果 ?: 左边表达式不为空则返回,否则返回右边的表达式。注意右边的表带式只有在左边表达式为空是才会执行
      //注意在 Kotlin 中 throw return 是表达式,所以它们也可以在 Elvis 操作符右边。这是非常有用的,比如检查函数参数是否为空:
      fun foo(node: Node): String? {
        val parent = node.getParent() ?: return null
        val name = node.getName() ?: throw IllegalArgumentException("name expected")
      
        //...
      }
      
    • !! 操作符

      NPE-lovers,我们可以用 b!! ,这会返回一个非空的 b 或者抛出一个 b 为空的 NPE:

      val l = b !!.length()
      
  • 安全转换

    普通的转换可能产生 ClassCastException 异常。另一个选择就是使用安全转换,如果不成功就返回空:

    val aInt: Int? = a as? Int
    

等式

  • 在 kotlin 中有两种相等

    • 参照相等:参照相等是通过 === 操作符判断的(不等是!== ) a===b 只有 a b 指向同一个对象是判别才成立。另外,你可以使用内联函数 identityEquals() 判断参照相等:

      a.identityEquals(b)
      a identityEquals b
      
    • 结构相等:结构相等是通过 == 判断的。像 a == b 将会翻译成:

      a?.equals(b) ?: b === null
      //如果 a 不是 null 则调用 equals(Any?) 函数,否则检查 b 是否参照等于 null
      //注意完全没有必要为优化你的代码而将 a == null 写成 a === null 编译器会自动帮你做的
      

      kotlin中==相当于java的equals,===相当于java的==

多重申明(解构申明)

var (name, age) = person
  • 意思就是一次性申明多个变量,并把=号右边的对象的属性拆箱出来赋值给变量。如:

    data class Person(var name: String, var age: Int) {
    }
    
    var person: Person = Person("Jone", 20)
    var (name, age) = person
    
    println("name: $name, age: $age")// 打印:name: Jone, age: 20
    

    如果拆箱出对象的属性:

    val name = person.component1()
    val age = person.component2()
    

    person.component1,component2怎么来的呢,Kotlin的数据类编译器会根据主构造器中声明的属性, 自动推断生成componentN() 函数群, 这些函数与类的属性对应, 函数名中的数字1到N,与属性的声明顺序一致。那么如果不是数据类就要自己编写对象的componentN函数:

    class Person(val name: String, val age: Int) {
        operator fun component1(): String {
            return name
        }
    
        operator fun component2(): Int {
            return age
        }
    }
    
  • 解构申明可以用在for循环中:

    var personA: Person = Person("Door", 22, "ShanDong")
    var personB: Person = Person("Green", 30, "BeiJing")
    var personC: Person = Person("Dark", 23, "YunNan")
    var personD: Person = Person("Tool", 26, "GuanDong")
    var personE: Person = Person("Mark", 24, "TianJin")
    var pers = listOf(personA, personB, personC, personD, personE)
    for ((name, age) in pers) {
        println("name: $name, age: $age")
    }
    
  • Map使用结构申明,Kotlin的标准库中,对Map实现了这些扩展函数:

    operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator()
    operator fun <K, V> Map.Entry<K, V>.component1() = getKey()
    operator fun <K, V> Map.Entry<K, V>.component2() = getValue()
    

所以在使用Map.Entry.getkey时使用调用到component1(),getvalue时调用component2():

var personA: Person = Person("Door", 22, "ShanDong")
 var personB: Person = Person("Green", 30, "BeiJing")
 var personC: Person = Person("Dark", 23, "YunNan")
 var personD: Person = Person("Tool", 26, "GuanDong")
 var personE: Person = Person("Mark", 24, "TianJin")

 var map = HashMap<String, Person>()
 map.put("1", personA)
 map.put("2", personB)
 map.put("3", personC)
 map.put("4", personD)
 map.put("5", personE)
 for ((key, value) in map) {
     println("key: $key, value: $value")
 }
// Log打印
key: 1, value: Person(name='Door', age=22, addr='ShanDong', mobile=null)
key: 2, value: Person(name='Green', age=30, addr='BeiJing', mobile=null)
key: 3, value: Person(name='Dark', age=23, addr='YunNan', mobile=null)
key: 4, value: Person(name='Tool', age=26, addr='GuanDong', mobile=null)
key: 5, value: Person(name='Mark', age=24, addr='TianJin', mobile=null)

Ranges

  • 表示从多少到多少,可用于if判断和for循环,与in关键字配合,常见用法:

    // Checking if value of comparable is in range. Optimized for number primitives.
    if (i in 1..10) println(i)
    
    if (x !in 1.0..3.0) println(x)
    
    if (str in "island".."isle") println(str)
    
    // Iterating over arithmetical progression of numbers. Optimized for number primitives (as indexed for-loop in Java).
    for (i in 1..4) print(i) // prints "1234"
    
    for (i in 4..1) print(i) // prints nothing
    
    for (i in 4 downTo 1) print(i) // prints "4321"
    
    for (i in 1..4 step 2) print(i) // prints "13"
    
    for (i in (1..4).reversed()) print(i) // prints "4321"
    
    for (i in (1..4).reversed() step 2) print(i) // prints "42"
    
    for (i in 4 downTo 1 step 2) print(i) // prints "42"
    
    for (x in 1.0..2.0) print("$x ") // prints "1.0 2.0 "
    
    for (x in 1.0..2.0 step 0.3) print("$x ") // prints "1.0 1.3 1.6 1.9 "
    
    for (x in 2.0 downTo 1.0 step 0.3) print("$x ") // prints "2.0 1.7 1.4 1.1 "
    
    for (str in "island".."isle") println(str) // error: string range cannot be iterated over
    

    原理参见标准库中接口:Range ,Progressiont和和操作函数的扩展。

  • for in :

    如果你想通过 list 或者 array 的索引进行迭代,你可以这样做:

    for (i in array.indices)
        print(array[i])
    //-------------------------//
    for ((index, value) in array.withIndex()) {
        println("the element at $index is $value")
    }
    

类型检查和转换

  • 类型检查:is !is 表达式:

    //运行时检查一个对象是否是某个特定类:
    if (obj is String) {
        print(obj.length)
    }
    
    if (obj !is String) { // same as !(obj is String)
        print("Not a String")
    }
    else {
        print(obj.length)
    }
    //智能转换,编译器会跟踪 is 检查静态变量,并在需要的时候自动插入安全转换:
    fun demo(x: Any) {
        if (x is String) {
            print(x.length) // x is automatically cast to String
        }
    }
    if (x !is String) return
    print(x.length) //x 自动转换为 String
    //在 || && 操作符,when 表达式和 whie 循环中:
     // x is automatically cast to string on the right-hand side of `||`
      if (x !is String || x.length == 0) return
    
      // x is automatically cast to string on the right-hand side of `&&`
      if (x is String && x.length > 0)
          print(x.length) // x is automatically cast to String
    
    when (x) {
        is Int -> print(x + 1)
        is String -> print(x.length + 1)
        is Array<Int> -> print(x.sum())
    }
    
  • 转换:

    用as 操作符来转换类型:

    val x: String = y as String
    //null 不能被转换为 String 因为String不是 nullable,也就是说如果 y 是空的,则上面的代码会抛出空异常。为了 java 的转换语句匹配我们得像下面这样:
    val x: String?= y as String?
    

    "安全"转换:

    val x: String ?= y as? String
    //为了避免抛出异常,可以用 as? 这个安全转换符,这样失败就会返回 null
    

This表达式

  • 如果 this 没有应用者,则指向的是最内层的闭合范围。为了在其它范围中返回 this ,需要使用标签:

    //this@lable
    class A { // implicit label @A
      inner class B { // implicit label @B
        fun Int.foo() { // implicit label @foo
          val a = this@A // A's this
          val b = this@B // B's this
    
          val c = this // foo()'s receiver, an Int
          val c1 = this@foo // foo()'s receiver, an Int
    
          val funLit = @lambda {String.() ->
            val d = this // funLit's receiver
            val d1 = this@lambda // funLit's receiver
          }
    
          val funLit2 = { (s: String) ->
            // foo()'s receiver, since enclosing function literal 
            // doesn't have any receiver
            val d1 = this 
          }
        }
      }
    }
    

运算符号重载

。。。

operator fun get(position: Int) = dailyForecast[position]

//xxx[position]

一些使用时的笔记(建议)

  • 当需要把一个对象转成另一个,或有多个当前类或对象的.调用等,可以使用这些扩展和函数,提高效率:

    • let
    • apply
    • run
    • with,with是个函数
  • 使用.isNullOrEmpty(),isNullOrBlank(),isBlank(),isEmpty(),isNotBlank(),isNotEmpty()来代替TextUtils判断字符串。

  • 善用集合中的各种扩展函数,如reduce,filter,map,any,all,count,max,sumBy等等。

  • 构造函数里的变量如果要在类中使用(类属性)要标记定义关键字var或val,否则作用域不会是整个类,就像只是函数的参数一样。

  • 一些高阶函数,lambda {}里不要用return,是返回的最后一行,如果用return他又是inline的话会返回了外层的函数。

  • 利用默认参数减少(java)方法重载

  • 可空也是一种类型(可空的xx类型),可接受实参为空或具体类型的实例,可空类型的实例变量要解包(!!)后才可以使用原类型的属性、方法。

  • for( index in 5..1),其中5和1只能是数值,如果用变量要用(x-1)包起来并转成数字:for (i in (x+1)..(y+1))。

  • kotlin 没有Volatile等并发编程的关键字,这是kotlin有意为之,kotlin让为这应该让函数库来做,但并不是不能用,可以使用@Volatile,@Synchronized注解来使用相应功能,@Volatile标记jvm的备用字段为volatile。wait(), notify()等Object(在Kotlin的Any中没有这些方法)的方法可以这么使用:

    private val lock = java.lang.Object()
    
    fun produce() = synchronized(lock) {  
      while (items >= maxItems) {
        lock.wait()
      }
      Thread.sleep(rand.nextInt(100).toLong())
      items++
      println("Produced, count is $items: ${Thread.currentThread()}")
      lock.notifyAll()
    }
    
    fun consume() = synchronized(lock) {  
      while (items <= 0) {
        lock.wait()
      }
      Thread.sleep(rand.nextInt(100).toLong())
      items--
      println("Consumed, count is $items: ${Thread.currentThread()}")
      lock.notifyAll()
    }
    
  • 当碰到用java时常用的如果不如为就…时可以用let等扩展:

    if (data != null) {
       nameTv.setText(data.name);
    }
    //kotlin
    data?.apply {
        nameTv.text=name
        info{"xxx"}
    }
    data?.let{
        nameTv.text=it.name
    }
    mOnActionListener?.onAction()
    
  • 使用高阶函数+函数对象定义监听器(java中的onClickListener等)

  • 当可变属性(var)定义为可空(?)时编译器报错:Smart cast to 'Type' is impossible, because 'variable' is a mutable property that could have been changed by this time 解决的几种办法:

    var name: String? = null
    val names: ArrayList<String> = ArrayList()
    //1:如果能用只读,改成val。
    //2::用一个本地变量接收再使用:
    fun foo() {
      val nameLoc = a.name
      if(nameLoc != null) {
         names.add(name);
      }
    }
    //3:用?操作符
    name?.let{
         names.add(name);
    }
    //4:如果是在用Elvis操作符
    foo1(name?:"")
    //循环中
     names.add(name?:continue);
    

作者:竹尘居士
博客:http://zhuchenju.com

推荐阅读更多精彩内容

  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 88,940评论 26 538
  • Scala与Java的关系 Scala与Java的关系是非常紧密的!! 因为Scala是基于Java虚拟机,也就是...
    灯火gg阅读 1,693评论 0 24
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile丽语阅读 2,182评论 0 5
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    会飞的鱼69阅读 23,532评论 18 391
  • Kotlin编程语言 之 入门基础 1.Kotlin介绍 Kotlin 是一个基于 JVM 的新的编程语言,由 J...
    我的毕业设计阅读 2,485评论 0 2