博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Kotlin知识点总结与初写时的一些建议
阅读量:7235 次
发布时间:2019-06-29

本文共 36150 字,大约阅读时间需要 120 分钟。

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

对象相关

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

    val a = 10val 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
    ) { MyInfo.doClick()}//当对象声明在另一个类的内部时,这个类的实例并不能直接访问对象申明内部,而只能通过类名来访问,同样该对象也不能直接访问到外部类的方法和变量class Site { var name = "菜鸟教程" object DeskTop{ var url = "www.runoob.com" fun showName(){ print{
    "desk legs $name"} // 错误,不能访问到外部类的方法和变量 } }}fun main(args: Array
    ) { 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
    ) { Books.ComBooks.doNote() println("Book.a = ${Books.ComBooks.a}") println("-------------") Books.doNote()}// Logdo noteBook.a = 10-------------do note//伴随对象的成员可以通过类名做限定词直接使用:class MyClass { companion object Factory { fun create(): MyClass = MyClass() }}val instance = MyClass.create()//在使用了 companion 关键字时,伴随对象的名字可以省略:class MyClass { companion object { }}//尽管伴随对象的成员很像其它语言中的静态成员,但在运行时它们任然是真正类的成员实例,比如可以实现接口:interface Factory
    { fun create(): T}class MyClass { companion object : Factory
    { 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
    ? = nullpublic val table: Map
    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 bfun main() {    val b = BaseImpl(10)    Derived(b).print()}//在 Derived 的父类列表中的 by 从句会将 b 存储在 Derived 内部对象,并且编译器会生成 Base 的所有方法并转给 b复制代码

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

    val/var 
    :
    by
    //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 = 200fun main(args: Array
      ) { 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
      ) { 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
      ) { println("name: $name") println("------------------") name = "zhangLing" println("name: $name") println("------------------") name = "wangBing" println("name: $name") }//Log name: wang------------------oldValue:wang | newValue:zhangLingname: wang------------------oldValue:wang | newValue:wangBingname: 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
      ) { val name: String by map val age: Int by map}fun main(args: Array
      ) { 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
      ) { val name: String by map val age: Int by map}fun main(args: Array
      ) { var map:MutableMap
      = 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
      { public operator fun getValue(thisRef: R, property: KProperty<*>): T public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)}//定义一个NotNullVarprivate class NotNullVar
      () : ReadWriteProperty
      { 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 Exprdata 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.barfun Baz.goo() { ... }//为了在除声明的包外使用这个扩展,我们需要在别的文件中使用时导入://-------------------------------------------------//package com.example.usageimport foo.bar.goo // 导入所有名字叫 "goo" 的扩展                    // 或者import foo.bar.* // 导入foo.bar包下得所有数据fun usage(baz: Baz) {    baz.goo()}复制代码

  • 函数扩展:

    fun 
    MutableList
    .swap(x: Int, y: Int) { val tmp = this[x] // 'this' corresponds to the list this[x] = this[y] this[y] = tmp}//this 关键字对应接收者对象(MutableList
    )//使用: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 
    List
    .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
( public val first: A, public val second: B) : Serializable { // toString()方法 public override fun toString(): String = "($first, $second)"}// 转换public infix fun
A.to(that: B): Pair
= Pair(this, that)// 转换成List集合public fun
Pair
.toList(): List
= listOf(first, second)// 这里去掉了源码中的注释public data class Triple
( 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
Triple
.toList(): List
= listOf(first, second, third)复制代码

泛型

  • 两种型变:

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

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

      其余为不变。

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

  • 不变(java的泛型是不变的):

    ArrayList
    list = new ArrayList
    (); //type mismatch 复制代码
  • 协变:

    List
    list001 = new ArrayList
    (); List
    list002 = new ArrayList
    (); 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可以添加Interger及其子类,List可以添加Float及其子类,List、List都是List<? extends Animal>的子类型,如果能将Float的子类添加到List<? extends Animal>中,就说明Float的子类也是可以添加到List中的,显然是不可行。故java为了保护其类型一致,禁止向List<? extends Number>添加任意对象,不过却可以添加null。

  • 逆变:

    List
    list = new ArrayList<>(); List
    list001 = new ArrayList
    (); List
    list002 = new ArrayList
    (); 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
    { 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
    src) { for (E e : src) push(e); } //pop all// Wildcard type for parameter that serves as an E consumer public void popAll(Collection
    dst) { while (!isEmpty()) dst.add(pop()); } // java.util.Collections的copy方法public static
    void copy(List
    dest, List
    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
    di=dest.listIterator(); ListIterator
    si=src.listIterator(); for (int i=0; i
  • Kotlin 用out ,in修饰符使协变与逆变使用起来更方便,out表示只生成,in表示只消费,称之为声明处变型。这与 Java 中的使用处变型相反:

    abstract class Source
    { abstract fun nextT(): T}fun demo(strs: Source
    ) { val objects: Source
    = strs // This is OK, since T is an out-parameter // ...}//-------------------------//abstract class Comparable
    { abstract fun compareTo(other: T): Int}fun demo(x: Comparable
    ) { 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
    val y: Comparable
    = x // OK!}复制代码
  • 类型投影

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

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

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

  • 范型约束

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

    fun 
    > sort(list: List
    ) { // ...}sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable
    sort(listOf(HashMap
    ())) // Error: HashMap
    is not a subtype of Comparable
    >复制代码

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

    fun 
    cloneWhenGreater(list: List
    , threshold: T): List
    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) { ... }

    - 默认参数,函数参数可以设置默认值,当调用函数时参数被忽略会使用默认值。这样相比其他语言可以减少重载:```kotlinfun read(b: Array
    , 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 
      asList(vararg ts: T): List
      { val result = ArrayList
      () 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
    ) { 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
    () 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
    () 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 
    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
    List
    .map(transform: (T) -> R):List
    { val result = arrayListOf
    () 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
    (collection: Collection
    , 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 = 0ints.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 
    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 
    TreeNode.findParentOfType(clazz: Class
    ): T? { var p = parent while (p != null && !clazz.isInstance(p)) { p = p?.parent } @suppress("UNCHECKED_CAST") return p as T}//调用myTree.findParentOfType(javaClass
    () )//myTree.findParentOfType(MyTreeNodeType::class.java)//我们想要的仅仅是给这个函数传递一个类型,如果即像下面这样就很方便:myTree.findParentOfType
    ()//为了达到这个目的,内联函数支持具体化的类型参数申明 reifiedinline fun
    TreeNode.findParentOfType(): T? { var p = parent while (p != null && p !is T) { p = p?.parent } return p as T}复制代码

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

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

    inline fun methodsOf
    () = javaClass
    ().getMethods()fun main(s: Array
    ) { println(methodsOf
    ().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) = personprintln("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 
    Map
    .iterator(): Iterator
    > = entrySet().iterator()operator fun
    Map.Entry
    .component1() = getKey()operator fun
    Map.Entry
    .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
    () 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关键字配合,常见用法:```kotlin// 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 nothingfor (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) returnprint(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 Stringwhen (x) {    is Int -> print(x + 1)    is String -> print(x.length + 1)    is Array
    -> 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@lableclass 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);}//kotlindata?.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? = nullval names: ArrayList
    = 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://zhuchen.vip/2018/04/01/kotlin/kotlin-learn-summary.html

转载地址:http://wxlfm.baihongyu.com/

你可能感兴趣的文章
开机黑屏 仅仅显示鼠标 电脑黑屏 仅仅有鼠标 移动 [已成功解决]
查看>>
js控制select选中显示不同表单内容
查看>>
HTML5--Audio
查看>>
地铁线路图计算--路由计算及设计参考
查看>>
[] ubuntu 14.04 搜狗拼音输入法安装
查看>>
毕业了五年了--- 人生感想
查看>>
【转】深入浅出Android Support Annotation
查看>>
垂死挣扎还是涅槃重生 -- Delphi XE5 公布会归来感想
查看>>
MYSQL线程池总结(一)
查看>>
c++11 gcc4.8.x安装
查看>>
工厂方法模式
查看>>
(ETW) Event Trace for Windows 提高 (含pdf下载)
查看>>
WPS2012交叉引用提示word比wps这种强烈的更新参考
查看>>
微信支付[v3]
查看>>
linux网络配置相关命令、虚拟网络接口eth0:0
查看>>
意义和公式的协方差
查看>>
64脚和小于64脚的STM32进行AD时注意,参照电源处理方法(转)
查看>>
对想读社会科学各个领域研究生朋友的建议
查看>>
mysql_use_result & mysql_store_result & MYSQLI_ASYNC
查看>>
进程调度
查看>>