https://developer.android.google.cn/codelabs/java-to-kotlin?continue=https%3A%2F%2Fdeveloper.android.google.cn%2Fcourses%2Fpathways%2Fkotlin-for-java%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fjava-to-kotlin#0
知识点梳理:

数据类

User 类仅存放数据。对于具有这一角色的类,Kotlin 会提供对应的关键字:data
在将此类标记为 data 类后,编译器便会自动创建 getter 和 setter。此外,其还会派生 equals()、hashCode() 和 toString() 函数。
让我们向 User 类添加 data 关键字,具体如下:

  1. data class User(var firstName: String?, var lastName: String?)

构造函数均使用 constructor 关键字进行定义。
如要创建此类的实例,可以采用如下方法:

val user1 = User("Jane", "Doe")

相等性

Kotlin 分为两类相等性:

  • 构成相等使用 == 运算符,并调用 equals() 来确定两个实例是否相等。
  • 引用相等使用 === 运算符,以检查两个引用是否指向同一对象。

数据类主构造函数中定义的属性将被用于检查构成相等性。

val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false

默认参数与具名参数

在 Kotlin 中,我们可以对函数调用中的参数赋予默认值。当省略参数时,系统便会使用此默认值。在 Kotlin 中,构造函数也属于函数的一种,因此我们可以使用默认参数来将 lastName 的默认值指定为 null。为此,我们直接将 null 赋予 lastName。

data class User(var firstName: String?, var lastName: String? = null)

// usage
val jane = User ("Jane") // same as User("Jane", null)
val joe = User ("John", "Doe")

假如 firstName 将 null 用作其默认值,而 lastName 并不如此。在此情况下,由于默认参数将居于未设默认值的参数之前,因此您必须使用具名参数来调用此函数,具体如下:

data class User(var firstName: String? = null, var lastName: String?)

// usage
val jane = User (lastName = "Doe") // same as User(null, "Doe")
val john = User ("John", "Doe")

init 块

在 Kotlin 中,主构造函数无法包含任何代码,因此初始化代码会置于 init 块中。不过,二者功能完全相同。

class Repository private constructor() {
    ...
    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        users = ArrayList()
        users!!.add(user1)
        users.add(user2)
        users.add(user3)
    }

}

init 代码大都用于处理属性的初始化。这项操作也可在声明属性时完成。例如,在 Kotlin 版本的 Repository 类中,我们可以看到,users 属性已在声明时进行初始化。

private val users: MutableList<User>? = null

Kotlin 的”静态”属性与”静态”方法

在 Java 中,我们会在字段或函数中使用 static 关键字,以指出此等字段或函数属于某个类,但不属于该类的某个实例。因此,我们在 Repository 类中创建了 INSTANCE 静态字段。在 Kotlin 中,companion object 代码块与此等效。您还可在此处声明静态字段和静态函数。转换器已创建 INSTANCE 字段并将其移至此处。

companion object {

        private var INSTANCE: Repository? = null

        val instance: Repository?
            get() {
                if (INSTANCE == null) {
                    synchronized(Repository::class.java) {
                        if (INSTANCE == null) {
                            INSTANCE = Repository()
                        }
                    }
                }
                return INSTANCE
            }
    }

处理单一实例

由于只需要 Repository 类的一个实例,因此我们在 Java 中使用了单一实例模式。在 Kotlin 中,通过将 class 关键字替换为 object,我们可以在编译器级别强制使用此模式。现在,我们可以移除私有构造函数和伴生对象(companion object)

class Repository
private constructor() {

    private var users: MutableList<User>? = null

    val formattedUserNames: List<String>
        get() {
            val userNames = ArrayList<String>(users!!.size)
            for ((firstName, lastName) in users!!) {
                val name: String?

                if (lastName != null) {
                    if (firstName != null) {
                        name = "$firstName $lastName"
                    } else {
                        name = lastName
                    }
                } else if (firstName != null) {
                    name = firstName
                } else {
                    name = "Unknown"
                }
                userNames.add(name)
            }
            return userNames
        }

    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        users = ArrayList()
        users!!.add(user1)
        users!!.add(user2)
        users!!.add(user3)
    }

    fun getUsers(): List<User>? {
        return users
    }

    companion object {

        private var INSTANCE: Repository? = null

        val instance: Repository?
            get() {
                if (INSTANCE == null) {
                    synchronized(Repository::class.java) {
                        if (INSTANCE == null) {
                            INSTANCE = Repository()
                        }
                    }
                }
                return INSTANCE
            }
    }
}
object Repository {

    private val users: MutableList<User>? = null

    val formattedUserNames: List<String>
        get() {
            val userNames = ArrayList<String>(users!!.size)
            for ((firstName, lastName) in users) {
                val name: String?

                if (lastName != null) {
                    if (firstName != null) {
                        name = "$firstName $lastName"
                    } else {
                        name = lastName
                    }
                } else if (firstName != null) {
                    name = firstName
                } else {
                    name = "Unknown"
                }
                userNames.add(name)
            }
            return userNames
        }

    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList()
        users!!.add(user1)
        users.add(user2)
        users.add(user3)
    }

    fun getUsers(): List<User>? {
        return users
    }
}

使用 object 类时,我们直接在对象上调用函数和属性,如下所示:

val users = Repository.users

解构

Kotlin 允许使用名为解构声明的语法,将对象解构为多个变量。我们可以创建多个变量,并能独立使用这些变量。
例如,数据类支持解构,因此自动转换器能够将 for 循环中的 User 对象解构。如此一来,我们便可直接处理 firstName 和 lastName 值,具体如下:

for ((firstName, lastName) in users) {
       val name: String?

       if (lastName != null) {
          if (firstName != null) {
                name = "$firstName $lastName"
          } 
       ...

处理可空性

?.意思是这个参数可以为空,并且程序继续运行下去
!!.的意思是这个参数如果为空,就抛出异常

private val users: MutableList<User>? = null

为简单起见,我们可以使用 mutableListOf() 函数,提供列表元素类型,从 init 代码块中移除 ArrayList 构造函数调用,然后移除 users 属性的显式类型声明,具体如下:

private val users = mutableListOf<User>()

若 user.firstName 不为 null,以下代码便会返回此值firstName 。若 user.firstName 为 null,该表达式将返回右侧值 “Unknown”,具体如下:注意kotlin中 ?: 符号与java三目运算符有较大差异

if (lastName != null) {
    ...
} else {
    name = firstName ?: "Unknown"
}

字符串模板和 if 表达式

Kotlin 将能简化 String 的处理工作。字符串模板允许在字符串声明内引用变量。
自动转换器已对名字与姓氏的串联表达式进行更新,可让用户使用 $ 符号直接在字符串内引用变量名称,并可将表达式置于 { } 之间。

// Java
name = user.getFirstName() + " " + user.getLastName();

// Kotlin
name = "${user.firstName} ${user.lastName}"

在 Kotlin 中,if、when、for 和 while 均为表达式,它们有返回值。IDE 甚至会显示警告,指出应将赋值从 if 中移出,具体如下:

name = if (firstName != null) {
      // do something
      firstName 
  }
// name = firstName

kotlin 提供各类集合转换,通过扩充 Java Collections API 的功能,加快开发速度并提升安全性。
参考:https://book.kotlincn.net/text/d-collections.html
如map函数:

val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

属性和支持属性

属性的getter 和 setter方法:

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

范例:

范例一:
var lastName: String = "zhang"
        get() = field.toUpperCase()   // 将变量赋值后转换为大写
        set

修改了get方法

范例二
var no: Int = 100
        get() = field                // 后端变量
        set(value) {
            if (value < 10) {       // 如果传入的值小于 10 返回该值
                field = value
            } else {
                field = -1         // 如果传入的值大于等于 10 返回 -1
            }
        }        
修改了set方法

field属性说明:

  • 作用域仅仅存在于当前属性的setter/getter方法中
  • 就像当前属性的影子一样。
  • 自动生成

    Override 属性

    kotlin支持属性override,其原理实际上override的是属性的getter和setter方法,而不是生成的那个字段。
    如下 ```kotlin open class Animal { open val weight: Int = 100 open var name: String = “animal”
      set(value) {
          field = value
          println("Animal被设置为$value")
      }
    
    }

class Dog : Animal() { override val weight: Int = 200 override var name: String = “dog” set(value) { field = value println(“Dog被设置为$value”) } }

class Cat(override var weight: Int) : Animal() { }

```kotlin
fun runPropertyDemo() {
    val animal: Animal = Dog()

    //如果weight被override了就会输出Dog里的值200,否则输出Animal里的值100
    println("The weight of specific animal  is ${animal.weight}")

    println("The name of dog is ${animal.name}")
    //如果name被override了就会调用Dog里面的setter方法,否则调用Animal里的
    animal.name = "藏獒"
    println("The name of dog is ${animal.name}")
}

输出结果:

The weight of specific animal  is 200
The name of dog is dog
The name of dog is 藏獒

weight和name都被覆写了

代理属性

kotlin中声明一个代理属性使用by关键字,这个关键字是非常形象的。by左边是属性,右边是其代理,例如如下代码:读作MyDelegate()代理了属性p。这就意味着所有对p的读写操作都要其代理来代为处理了。

class Demo{
    var p: String by MyDelegate()
}

代理类要求:

  • 如果代理的是只读属性(使用val声明),只需要一个使用operator 标识的getValue(thisRef: Any?, property: KProperty<*>)方法
  • 如果代理的是可变属性(使用var声明),需要getValue(thisRef: Any?, property: KProperty<>)和 setValue(thisRef: Any?, property: KProperty<>, value: String)两个方法

方法参数的含义如下:

  • thisRef: Any? 用来调用属性的那个类实例,例如此处的Demo。
  • property: KProperty<*> 属性的元数据,例如此处是p的元数据,使用反射可以获取这些数据。
  • value: String 要为属性设置的值,例如此处的”ShuSheng007”。其类型要与我们声明的属性一致,此处我们属性p的类型是String,所以此value的类型也是String。

第三个参数还有点用,其他两个参数我们不用管,基本没什么用。

class MyDelegate{
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "你好:$thisRef, 我代理 '${property.name}' 完成取值"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("你好 $thisRef , 我代理 '${property.name}' 完成赋值: $value")
    }
}

属性代理接口

通过上面的讲解我们知道了如何定义代理类,但是我们需要记住那两个方法的签名,写起来还是有一定的困难。于是Kotlin 就很贴心的为我们提供了两个接口ReadOnlyProperty与ReadWriteProperty。顾名思义,一个用于只读代理属性,另一个用于读写代理属性。

//用于只读代理属性
public fun interface ReadOnlyProperty<in T, out V> {
    public operator fun getValue(thisRef: T, property: KProperty<*>): V
}
//用于读写代理属性
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
    public override operator fun getValue(thisRef: T, property: KProperty<*>): V
    public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}

其中泛型参数T为声明属性时的那个类的类型,V为属性的类型。
例如上面的例子:

class Demo{
    var p: String by MyDelegate()
}

可以用属性代理表示:

class MyDelegate :ReadWriteProperty<Demo,String> {
    override fun getValue(thisRef: Demo, property: KProperty<*>): String {
        ...
        return ""
    }

    override fun setValue(thisRef: Demo, property: KProperty<*>, value: String) {
       ...
    }
}

标准代理属性

1.Lazy

使用Layz代理的属性,只有第一次取值时候才会通过执行传入的Lambda表达式获得结果。以后多次调用时会直接返回第一次的值,而不是再执行一次Lambda表达式。

val lazyValue: String by lazy {
       println("我们大家一起喊:")
       "我们都爱 android"
   }
println("第1次:$lazyValue")
println("第2次:$lazyValue")

结果:只返回第一次的结果,不执行传入的Lambda表达式

我们大家一起喊:
第1次:我们都爱 android
第2次:我们都爱 android

注意:lazy中lambda的返回值的类型就是属性的值的类型。

2.Observable

在属性值改变后会收到通知。

var name: String by Delegates.observable("ben") {
           prop, old, new ->
           println("属性${prop.name}的值从: $old -> $new")
}

调用

name="android"
println("My name is:$name")

结果:

属性name的值从: ben -> android
My name is:android

可见,当我们修改name的值时,observable传入的lambda表达式被执行了。

Map

Map或者MutableMap可以作为以其key命名的属性的代理。 然后那个属性就和map里的对应key-value绑定在一起了。

val map = mapOf<String, Any?>(
    "netName" to "niuniu",
    "age" to 35
)

val netName: String by map
val age: Int by map

调用:

println("my netName is $netName and I am $age years old")

输出:

my netName is niuniu and I am 35 years old

注意:属性的名称必须与map中对应的key保持一致,不然会报错。
如果代理是MutableMap,则可以代理var属性,对属性值的改变会反映到map中,反之亦然。

扩展属性

扩展属性的语法非常简单:在普通属性名称前面加上.要扩展的类型,注意前面那个点。具体的扩展逻辑写在getter与setter方法里。
值得注意的是,扩展属性不可以有初始化器,即不可以赋初始值

//kotlin
public val <T> List<T>.lastIndex: Int
    get() {
        return this.size - 1
    }

调用:

  val lastIndex = list.lastIndex
  println(lastIndex)