Kotlin基础 -- 1

一、定义和目的

1.定义

Kotlin是一种针对Java平台的新编程语言。Kotlin简洁、安全、务实,并且专注于与Java代码的互操作性。它几乎可以用在现在Java使用的任何地方:服务器端开发、Android应用,等等。Kotlin可以很好地和所有现存的Java库和框架一起工作,而且性能水平和Java旗鼓相当。

2.主要特征

  • 目标平台:服务器端、Android及任何Java运行的地方

  • 静态类型并支持类型推导

  • 函数式和面向对象

  • 免费并开源

3.编译Kotlin代码

Kotlin的源代码存放在后缀名为.kt的文件中。Kotlin编译器会分析源代码并生成.class文件,这和Java编译器做的没什么不同。用Kotlin编译器编译的代码依赖Kotlin运行时库。它包括了Kotlin自己的标准库类的定义,以及Kotlin对标准Java API的扩展。运行时库需要和你的应用程序一起分发。

二、Kotlin基础

1.函数

Kotlin函数声明:(代码块函数体)

fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}

fun method() {
    val result = max(8, 9)
    Log.d("TAG", "zwm, $result")
}

日志打印:
2020-08-18 13:59:15.306 3363-3363/com.tomorrow.kotlindemo D/TAG: zwm, 9

进一步简化:(表达式函数体)

fun max(a: Int, b: Int) = if (a > b) a else b

fun method() {
    val result = max(8, 9)
    Log.d("TAG", "zwm, $result")
}

日志打印:
2020-08-18 14:04:50.061 4307-4307/com.tomorrow.kotlindemo D/TAG: zwm, 9

lambda表达式函数体:

fun max(a: Int, b: Int) = { if (a > b) a else b }
或者:fun max(a: Int, b: Int): () -> Int = { if (a > b) a else b }

fun method() {
    val result = max(8, 9)
    Log.d("TAG", "zwm, ${result()}") //使用()调用
    Log.d("TAG", "zwm, ${result.invoke()}") //使用invoke调用
}

日志打印:
2020-08-18 14:08:10.698 5096-5096/? D/TAG: zwm, 9
2020-08-18 14:08:10.698 5096-5096/? D/TAG: zwm, 9

2.变量

val:不可变引用。使用val声明的变量不能在初始化之后再次赋值。它对应的是Java的final变量。

var:可变引用。这种变量的值可以被改变。这种声明对应的是普通(非final)的Java变量。

默认情况下,应该尽可能地使用val关键字来声明所有的Kotlin变量,仅在必要的时候换成var。使用不可变引用、不可变对象及无副作用的函数让你的代码更接近函数式编程风格。

3.字符串模板

引用局部变量:

var result = "Hello, Kotlin"
Log.d(TAG, "zwm, result: $result")

日志打印:
2020-08-13 22:53:49.744 26543-26543/com.tomorrow.kotlindemo D/MainActivity: zwm, result: Hello, Kotlin

引用表达式:

var result = "Hello, Kotlin"
var array = arrayOf("Hello", "Kotlin", "Perfect")
Log.d(TAG, "zwm, result: ${array[1]}")

日志打印:
2020-08-13 22:51:01.726 26246-26246/com.tomorrow.kotlindemo D/MainActivity: zwm, result: Kotlin

在双引号中直接嵌套双引号:

var result = 1
Log.d(TAG, "zwm, result: ${if (result > 0) "positive" else "negative"}")

日志打印:
2020-08-13 22:59:07.995 26868-26868/com.tomorrow.kotlindemo D/MainActivity: zwm, result: positive

4.类和属性

在类中声明属性:

class Person(val name: String, var isMarried: Boolean)

val person = Person("Bob", true)
Log.d(TAG, "zwm, name: ${person.name}, isMarried: ${person.isMarried}")

日志打印:
2020-08-13 23:39:34.647 30794-30794/com.tomorrow.kotlindemo D/MainActivity: zwm, name: Bob, isMarried: true

当声明属性的时候,就声明了对应的访问器(只读属性有一个getter,而可写属性既有getter也有setter)。访问器的默认实现非常简单:创建一个存储值的字段,以及返回值的getter和更新值的setter。但是如果有需要,可以声明自定义的访问器,使用不同的逻辑来计算和更新属性的值。

自定义访问器:

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() {
            Log.d("TAG", "zwm, isSquare get")
            return height == width
        }

    val isNotSquare: Boolean
       get() = height != width
}

val shape = Rectangle(100, 100)
Log.d(TAG, "zwm, isSquare: ${shape.isSquare}, isNotSquare: ${shape.isNotSquare}")

日志打印:
2020-08-14 00:41:37.249 4948-4948/com.tomorrow.kotlindemo D/TAG: zwm, isSquare get
2020-08-14 00:41:37.249 4948-4948/com.tomorrow.kotlindemo D/MainActivity: zwm, isSquare: true, isNotSquare: false

5.目录和包

Kotlin不区分导入的是类还是函数,而且它允许使用import关键字导入任何种类的声明。可以直接导入顶层函数的名称。在Kotlin中,可以把多个类放在同一个文件中,文件的名字还可以随意选择。

//shapes.kt
package com.tomorrow.kotlindemo.example

import android.util.Log

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() = height == width
}

class MyShape(val height: Int, val width: Int)

fun topMethod() {
    Log.d("TAG", "zwm, this is top method")
}

//MainActivity.kt
package com.tomorrow.kotlindemo

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.tomorrow.kotlindemo.example.Rectangle
import com.tomorrow.kotlindemo.example.topMethod

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val shape = Rectangle(100, 100)
        Log.d(TAG, "zwm, height: ${shape.height}, width: ${shape.width}")
        topMethod()
    }
}

日志打印:
2020-08-14 01:12:44.733 8517-8517/com.tomorrow.kotlindemo D/MainActivity: zwm, height: 100, width: 100
2020-08-14 01:12:44.733 8517-8517/com.tomorrow.kotlindemo D/TAG: zwm, this is top method

6.枚举和when

声明枚举类:

enum class Color {
    RED, GREEN, BLUE
}

声明一个带属性的枚举类:

enum class Color(val red: Int, val green: Int, val blue: Int) {
    RED(255, 0, 0),
    GREEN(0, 255, 0),
    BLUE(0, 0, 255);

    fun rgb() = (red * 256 + green) * 256 + blue
}

val result = Color.BLUE.rgb()
Log.d(TAG, "zwm, result: $result")

日志打印:
2020-08-14 01:20:38.516 8813-8813/com.tomorrow.kotlindemo D/MainActivity: zwm, result: 255

使用when处理枚举类:如果匹配成功,只有对应的分支会执行。

enum class Color {
    RED, GREEN, BLUE
}

fun getColorStr(color: Color) =
    when (color) {
        Color.RED -> "red"
        Color.GREEN, Color.BLUE -> "green_blue"
    }
    
Log.d(TAG, "zwm, result: ${getColorStr(Color.BLUE)}")

日志打印:
2020-08-14 01:27:57.098 9497-9497/com.tomorrow.kotlindemo D/MainActivity: zwm, result: green_blue

在when结构中使用任意对象:

enum class Color {
    RED, GREEN, BLUE
}

fun getColorStr(color1: Color, color2: Color) =
    when (setOf(color1, color2)) {
        setOf(Color.RED, Color.GREEN) -> "red"
        setOf(Color.GREEN, Color.BLUE) -> "green"
        else -> "blue"
    }
    
Log.d(TAG, "zwm, result: ${getColorStr(Color.BLUE, Color.GREEN)}")

日志打印:
2020-08-14 01:37:41.491 10829-10829/com.tomorrow.kotlindemo D/MainActivity: zwm, result: green

使用不带参数的when:

enum class Color {
    RED, GREEN, BLUE
}

fun getColorStr(color1: Color, color2: Color) =
    when {
        color1 == Color.RED && color2 == Color.GREEN -> "red"
        color1 == Color.GREEN && color2 == Color.BLUE -> "green"
        else -> "blue"
    }
    
Log.d(TAG, "zwm, result: ${getColorStr(Color.GREEN, Color.BLUE)}")

日志打印:
2020-08-14 01:42:35.493 12392-12392/com.tomorrow.kotlindemo D/MainActivity: zwm, result: green

7.智能转换:合并类型检查和转换

在Kotlin中,使用is检查来判断一个变量是否是某种类型后,就可以把它当做检查过的类型使用,而不再需要转换它,这种行为称为智能转换。

interface Expr
class Num(var value: Int): Expr
class Sum(var left: Expr, var right: Expr): Expr

fun eval(e: Expr): Int {
    if(e is Num) {
        return e.value
    }
    if(e is Sum) {
        return eval(e.left) + eval(e.right)
    }
    throw IllegalArgumentException("Unknown expression")
}

Log.d(TAG, "zwm, result: ${eval(Sum(Num(8), Num(9)))}")

日志打印:
2020-08-14 01:58:33.286 13062-13062/com.tomorrow.kotlindemo D/MainActivity: zwm, result: 17

使用as关键字来表示到特定类型的显示转换:

e as Num

用when代替if:

interface Expr
class Num(var value: Int): Expr
class Sum(var left: Expr, var right: Expr): Expr

fun eval(e: Expr): Int =
    when(e) {
        is Num -> e.value
        is Sum -> {
            Log.d("TAG", "zwm, Sum")
            eval(e.left) + eval(e.right)
        }
        else -> throw IllegalArgumentException("Unknown expression")
    }
    
Log.d(TAG, "zwm, result: ${eval(Sum(Num(8), Num(9)))}")

日志打印:
2020-08-14 02:11:06.443 14671-14671/com.tomorrow.kotlindemo D/TAG: zwm, Sum
2020-08-14 02:11:06.443 14671-14671/com.tomorrow.kotlindemo D/MainActivity: zwm, result: 17

8.while循环和for循环

while循环:

var i = 1
while (i < 3) {
    Log.d(TAG, "zwm, while i: $i")
    i++
}
do {
    Log.d(TAG, "zwm, do while i: $i")
    i--
} while (i > 0)

日志打印:
2020-08-14 02:22:44.316 16829-16829/com.tomorrow.kotlindemo D/MainActivity: zwm, while i: 1
2020-08-14 02:22:44.316 16829-16829/com.tomorrow.kotlindemo D/MainActivity: zwm, while i: 2
2020-08-14 02:22:44.316 16829-16829/com.tomorrow.kotlindemo D/MainActivity: zwm, do while i: 3
2020-08-14 02:22:44.316 16829-16829/com.tomorrow.kotlindemo D/MainActivity: zwm, do while i: 2
2020-08-14 02:22:44.316 16829-16829/com.tomorrow.kotlindemo D/MainActivity: zwm, do while i: 1

迭代数字:区间和数列

for (i in 1..3) {
   Log.d(TAG, "zwm, for i: $i")
}

日志打印:
2020-08-14 02:25:48.333 17148-17148/com.tomorrow.kotlindemo D/MainActivity: zwm, for i: 1
2020-08-14 02:25:48.334 17148-17148/com.tomorrow.kotlindemo D/MainActivity: zwm, for i: 2
2020-08-14 02:25:48.334 17148-17148/com.tomorrow.kotlindemo D/MainActivity: zwm, for i: 3

downTo:

for (i in 6 downTo 2 step 2) {
   Log.d(TAG, "zwm, downTo i: $i")
}

日志打印:
2020-08-14 02:30:20.321 17470-17470/com.tomorrow.kotlindemo D/MainActivity: zwm, downTo i: 6
2020-08-14 02:30:20.321 17470-17470/com.tomorrow.kotlindemo D/MainActivity: zwm, downTo i: 4
2020-08-14 02:30:20.321 17470-17470/com.tomorrow.kotlindemo D/MainActivity: zwm, downTo i: 2

until:

for (i in 2 until 6 step 2) {
   Log.d(TAG, "zwm, until i: $i")
}

日志打印:
2020-08-14 02:31:11.711 18147-18147/com.tomorrow.kotlindemo D/MainActivity: zwm, until i: 2
2020-08-14 02:31:11.711 18147-18147/com.tomorrow.kotlindemo D/MainActivity: zwm, until i: 4

迭代map:

val map = TreeMap<Char, String>()
map['A'] = "AAA"
map['C'] = "CCC"
map['B'] = "BBB"
for((key, value) in map) {
    Log.d(TAG, "zwm, key: $key, value: $value")
}

日志打印:
2020-08-14 02:35:24.113 18862-18862/? D/MainActivity: zwm, key: A, value: AAA
2020-08-14 02:35:24.113 18862-18862/? D/MainActivity: zwm, key: B, value: BBB
2020-08-14 02:35:24.113 18862-18862/? D/MainActivity: zwm, key: C, value: CCC

迭代集合:

val list = arrayListOf("AAA", "CCC", "BBB")
for((index, element) in list.withIndex()) {
    Log.d(TAG, "zwm, index: $index, element: $element")
}

日志打印:
2020-08-14 02:42:32.103 19365-19365/com.tomorrow.kotlindemo D/MainActivity: zwm, index: 0, element: AAA
2020-08-14 02:42:32.103 19365-19365/com.tomorrow.kotlindemo D/MainActivity: zwm, index: 1, element: CCC
2020-08-14 02:42:32.103 19365-19365/com.tomorrow.kotlindemo D/MainActivity: zwm, index: 2, element: BBB

使用in检查集合和区间的成员:

fun recognize (c: Char) = when(c) {
    in '0'..'9' -> "digit"
    in 'a'..'z' -> "letter"
    else -> "others"
}

val letter = 'x' in 'a'..'z'
val notLetter = 'x' !in 'a'..'z'
val set = "Kotlin" in setOf("Java", "Kotlin")
Log.d(TAG, "zwm, letter: $letter, notLetter: $notLetter, set: $set, recognize: ${recognize('8')}")

日志打印:
2020-08-14 02:52:03.511 20763-20763/com.tomorrow.kotlindemo D/MainActivity: zwm, letter: true, notLetter: false, set: true, recognize: digit

9.Kotlin中的异常

Kotlin中throw结构是一个表达式,能作为另一个表达式的一部分使用:

val number = 9
val percentage =
    if(number in 0..100)
        number
    else
        throw IllegalArgumentException("Wrong Value")
Log.d(TAG, "zwm, percentage: $percentage")

日志打印:
2020-08-14 02:59:14.753 22221-22221/? D/MainActivity: zwm, percentage: 9

try、catch和finally:

var result = 0
try {
    result = 100 / 0
} catch (e: ArithmeticException) {
    result = -1
} finally {
    Log.d(TAG, "zwm, result: $result")
}

日志打印:
2020-08-14 03:08:55.309 24026-24026/com.tomorrow.kotlindemo D/MainActivity: zwm, result: -1

try作为表达式:

fun recognize() {
    val result = try {
        100 / 0
    } catch (e: ArithmeticException) {
        return
    }
    Log.d("TAG", "zwm, result: $result")
}

三、函数的定义与调用

1.在Kotlin中创建集合

Kotlin没有采用它自己的集合类,而是采用标准的Java集合类。

val set = hashSetOf(1, 3, 5)
val list = arrayListOf(11, 33, 55)
val map = hashMapOf(111 to "one", 222 to "two", 333 to "three")
Log.d(TAG, "zwm, set: $set, list: $list, map: $map")
Log.d(TAG, "zwm, set: ${set.javaClass}, list: ${list.javaClass}, map: ${map.javaClass}")
Log.d(TAG, "zwm, list.last: ${list.last()}, set.max: ${set.max()}")

日志打印:
2020-08-14 03:37:12.317 27103-27103/com.tomorrow.kotlindemo D/MainActivity: zwm, set: [1, 5, 3], list: [11, 33, 55], map: {333=three, 222=two, 111=one}
2020-08-14 03:37:12.318 27103-27103/com.tomorrow.kotlindemo D/MainActivity: zwm, set: class java.util.HashSet, list: class java.util.ArrayList, map: class java.util.HashMap
2020-08-14 03:37:12.329 27103-27103/com.tomorrow.kotlindemo D/MainActivity: zwm, list.last: 55, set.max: 5

2.命名参数

当调用一个Kotlin定义的函数时,可以显示地标明一些参数的名称。如果在调用一个函数时,指明了一个参数的名称,为了避免混淆,那它之后的所有参数都需要标明名称。

fun getParams(name: String, age: String) = "name: $name, age: $age"

Log.d(TAG, "zwm, result: ${getParams(age = "10", name = "Android")}")

日志打印:
2020-08-16 15:05:57.287 18993-18993/com.tomorrow.kotlindemo D/MainActivity: zwm, result: name: Android, age: 10

3.默认参数值

在Kotlin中,可以在声明函数的时候,指定参数的默认值,这样就可以避免创建重载的函数。

fun getParams(name: String, age: String = "0") = "name: $name, age: $age"

Log.d(TAG, "zwm, result: ${getParams("Android")}")

日志打印:
2020-08-16 16:22:56.851 27933-27933/com.tomorrow.kotlindemo D/MainActivity: zwm, result: name: Android, age: 0

4.消除静态工具类:顶层函数和属性

可以把函数直接放到代码文件的顶层,不用从属于任何类。

//shapes.kt
package com.tomorrow.kotlindemo.example

import android.util.Log

fun topMethod() {
    Log.d("TAG", "zwm, this is top method")
}

//MainActivity.kt
package com.tomorrow.kotlindemo

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.tomorrow.kotlindemo.example.topMethod

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        topMethod()
    }
}

日志打印:
2020-08-16 16:32:56.418 29693-29693/com.tomorrow.kotlindemo D/TAG: zwm, this is top method

和函数一样,属性也可以放到文件的顶层。

//shapes.kt
package com.tomorrow.kotlindemo.example

const val PREFIX = "prefix"

var opCount = 0

fun performOperation() {
    opCount++
}

//MainActivity.kt
package com.tomorrow.kotlindemo

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.tomorrow.kotlindemo.example.PREFIX
import com.tomorrow.kotlindemo.example.opCount
import com.tomorrow.kotlindemo.example.performOperation

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        performOperation()
        Log.d(TAG, "zwm, ${PREFIX}: $opCount")
    }
}

日志打印:
2020-08-16 16:42:24.182 30585-30585/com.tomorrow.kotlindemo D/MainActivity: zwm, prefix: 1

5.给别人的类添加方法:扩展函数和属性

扩展函数非常简单,它就是一个类的成员函数,不过定义在类的外面。我们要做的就是把要扩展的类或者接口的名称,放到即将添加的函数前面,这个类的名称被称为接收者类型,用来调用这个扩展函数的那个对象,叫作接收者对象。

//shapes.kt
package com.tomorrow.kotlindemo.example

class Rectangle(val height: Int, val width: Int)

//extentions.kt
package com.tomorrow.kotlindemo.example

fun Rectangle.isSquare() = height == width

//MainActivity.kt
package com.tomorrow.kotlindemo

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.tomorrow.kotlindemo.example.Rectangle
import com.tomorrow.kotlindemo.example.isSquare

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val shape = Rectangle(100, 100)
        Log.d(TAG, "zwm, isSquare: ${shape.isSquare()}")
    }
}

日志打印:
2020-08-16 17:37:09.942 8526-8526/com.tomorrow.kotlindemo D/MainActivity: zwm, isSquare: true

在扩展函数中,可以直接访问被扩展的类的其它方法和属性,就好像是在这个类自己的方法中访问它们一样。不同的是,扩展函数不能访问私有的或者是受保护的成员。

可以使用关键字as来修改导入的类或者函数名称:

package com.tomorrow.kotlindemo

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.tomorrow.kotlindemo.example.Rectangle
import com.tomorrow.kotlindemo.example.isSquare as square

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val shape = Rectangle(100, 100)
        Log.d(TAG, "zwm, square: ${shape.square()}")
    }
}

从Java中调用扩展函数:

//shapes.kt
package com.tomorrow.kotlindemo.example

class Rectangle(val height: Int, val width: Int)

//extentions.kt
package com.tomorrow.kotlindemo.example

fun Rectangle.isSquare() = height == width

//JavaDemo.java
package com.tomorrow.kotlindemo;

import android.util.Log;

import com.tomorrow.kotlindemo.example.ExtentionsKt;
import com.tomorrow.kotlindemo.example.Rectangle;

public class JavaDemo {

    public void testDemo() {
        Rectangle rectangle = new Rectangle(100, 100);
        Log.d("TAG", "zwm, isSquare: " + ExtentionsKt.isSquare(rectangle));
    }
}

//MainActivity.kt
package com.tomorrow.kotlindemo

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val javaDemo = JavaDemo()
        javaDemo.testDemo()
    }
}

日志打印:
2020-08-16 18:04:34.705 11310-11310/com.tomorrow.kotlindemo D/TAG: zwm, isSquare: true
  • 扩展函数是静态函数的一个高效的语法糖,扩展函数的静态性质也决定了扩展函数不能被子类重写。

  • 如果一个类的成员函数和扩展函数有相同的签名,成员函数往往会被优先使用。

  • 成员函数和扩展函数之间最重要的区别:前者是动态调度的,后者总是静态调度的。

扩展属性:

//shapes.kt
package com.tomorrow.kotlindemo.example

class Rectangle(val height: Int, val width: Int)

//extentions.kt
package com.tomorrow.kotlindemo.example

import android.util.Log

val Rectangle.isSquare: Boolean
    get() = height == width

var Rectangle.tag: String
    get() = "Rectangle"
    set(value) {
        Log.d("TAG", "zwm, Rectangle#set")
        value
    }
    
//MainActivity.kt
package com.tomorrow.kotlindemo

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.tomorrow.kotlindemo.example.Rectangle
import com.tomorrow.kotlindemo.example.isSquare
import com.tomorrow.kotlindemo.example.tag

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val shape = Rectangle(100, 100)
        Log.d(TAG, "zwm, isSquare: ${shape.isSquare}, tag: ${shape.tag}")
        shape.tag = "Android"
        Log.d(TAG, "zwm, tag: ${shape.tag}")
    }
}

日志打印:
2020-08-16 20:15:21.576 23197-23197/com.tomorrow.kotlindemo D/MainActivity: zwm, isSquare: true, tag: Rectangle
2020-08-16 20:15:21.576 23197-23197/com.tomorrow.kotlindemo D/TAG: zwm, Rectangle#set
2020-08-16 20:15:21.576 23197-23197/com.tomorrow.kotlindemo D/MainActivity: zwm, tag: Rectangle

从Java中访问扩展属性:

//shapes.kt
package com.tomorrow.kotlindemo.example

class Rectangle(val height: Int, val width: Int)

//extentions.kt
package com.tomorrow.kotlindemo.example

import android.util.Log

val Rectangle.isSquare: Boolean
    get() = height == width

var Rectangle.tag: String
    get() = "Rectangle"
    set(value) {
        Log.d("TAG", "zwm, Rectangle#set")
        value
    }
    
//JavaDemo.java
package com.tomorrow.kotlindemo;

import android.util.Log;

import com.tomorrow.kotlindemo.example.ExtentionsKt;
import com.tomorrow.kotlindemo.example.Rectangle;

public class JavaDemo {

    public void testDemo() {
        Rectangle rectangle = new Rectangle(100, 100);
        Log.d("TAG", "zwm, isSquare: " + ExtentionsKt.isSquare(rectangle) + ", tag: " + ExtentionsKt.getTag(rectangle));
    }
}

//MainActivity.kt
package com.tomorrow.kotlindemo

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var javaDemo = JavaDemo()
        javaDemo.testDemo()
    }
}

日志打印:
2020-08-16 20:23:34.660 23873-23873/com.tomorrow.kotlindemo D/TAG: zwm, isSquare: true, tag: Rectangle

6.处理集合:可变参数、中缀调用和库的支持

可变参数:让函数支持任意数量的参数

//shapes.kt
package com.tomorrow.kotlindemo.example

import android.util.Log

fun printMsg(vararg elements: String) {
    if (elements.isNotEmpty()) {
        for ((index, element) in elements.asList().withIndex()) {
            Log.d("TAG", "zwm, index: $index, element: $element")
        }
    }
}

//MainActivity.kt
package com.tomorrow.kotlindemo

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.tomorrow.kotlindemo.example.printMsg

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val arr = arrayOf("CCC", "DDD")
        printMsg("AAA", "BBB", *arr) //展开运算符展开数组内容
    }
}

//日志打印
2020-08-16 20:52:16.948 26859-26859/com.tomorrow.kotlindemo D/TAG: zwm, index: 0, element: AAA
2020-08-16 20:52:16.948 26859-26859/com.tomorrow.kotlindemo D/TAG: zwm, index: 1, element: BBB
2020-08-16 20:52:16.948 26859-26859/com.tomorrow.kotlindemo D/TAG: zwm, index: 2, element: CCC
2020-08-16 20:52:16.948 26859-26859/com.tomorrow.kotlindemo D/TAG: zwm, index: 3, element: DDD

键值对的处理:中缀调用和解构声明

在中缀调用中,没有添加额外的分隔符,函数名称是直接放在目标对象名称和参数之间的。中缀调用可以与只有一个参数的函数一起使用,无论是普通的函数还是扩展函数。要允许使用中缀符号调用函数,需要使用infix修饰符来标记它。

//shapes.kt
package com.tomorrow.kotlindemo.example

infix fun Any.to(other: Any) = Pair(this, other)

//MainActivity.kt
package com.tomorrow.kotlindemo

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.tomorrow.kotlindemo.example.to

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val (number, name) = 1 to "one" //中缀调用、解构声明
        Log.d(TAG, "zwm, number: $number, name: $name")
    }
}

日志打印:
2020-08-16 21:11:51.655 29070-29070/? D/MainActivity: zwm, number: 1, name: one

7.字符串和正则表达式的处理

分割字符串:

var list = "12.345-6.A".split(".", "-")
Log.d(TAG, "zwm, list: $list")

日志打印:
2020-08-16 21:16:40.886 29328-29328/? D/MainActivity: zwm, list: [12, 345, 6, A]

使用String的扩展函数来解析文件路径:

val path = "/Users/temp/kotlin-book/chapter.doc"
val directory = path.substringBeforeLast("/")
val fullName = path.substringAfterLast("/")
val fileName = fullName.substringBeforeLast(".")
val extension = fullName.substringAfterLast(".")
Log.d(TAG, "zwm, \ndirectory: $directory\nfullName: $fullName\nfileName: $fileName\nextension: $extension")

日志打印:
2020-08-16 21:24:45.983 29328-29328/com.tomorrow.kotlindemo D/MainActivity: zwm, 
    directory: /Users/temp/kotlin-book
    fullName: chapter.doc
    fileName: chapter
    extension: doc

使用正则表达式解析文件路径:正则表达式写在一个三重引号的字符串中,不需要对任何字符进行转义,包括反斜线。

val path = "/Users/temp/kotlin-book/chapter.doc"
val regex = """(.+)/(.+)\.(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if(matchResult != null) {
    val (directory, fileName, extension) = matchResult.destructured
    Log.d(TAG, "zwm, \ndirectory: $directory\nfileName: $fileName\nextension: $extension")
}

日志打印:
2020-08-16 21:40:27.926 31713-31713/? D/MainActivity: zwm, 
    directory: /Users/temp/kotlin-book
    fileName: chapter
    extension: doc

多行三重引号的字符串:

val str = """Android
    Java
        Kotlin"""
Log.d(TAG, "zwm, \n${str.trimMargin()}")

日志打印:
2020-08-16 21:47:25.685 2485-2485/com.tomorrow.kotlindemo D/MainActivity: zwm, 
    Android
        Java
            Kotlin

8.让你的代码更整洁:局部函数和扩展

//shapes.kt
package com.tomorrow.kotlindemo.example

import android.util.Log

class Rectangle(val height: Int, val width: Int)

fun Rectangle.validate() { //扩展函数
    fun validate(value: Int) { //局部函数
        if (value < 0) {
            throw IllegalArgumentException("Value Illegal")
        }
    }
    validate(width)
    validate(height)
    Log.d("TAG", "zwm, validate success")
}

//MainActivity.kt
package com.tomorrow.kotlindemo

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.tomorrow.kotlindemo.example.Rectangle
import com.tomorrow.kotlindemo.example.validate

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var shape = Rectangle(100, 100)
        shape.validate()
    }
}

日志打印:
2020-08-16 22:14:24.729 6196-6196/com.tomorrow.kotlindemo D/TAG: zwm, validate success

四、类、对象和接口

1.定义类继承结构

Kotlin在类名后面使用冒号来代替了Java中的extends和implements关键字。和Java一样,一个类可以实现任意多个接口,但是只能继承一个类。

//shapes.kt
package com.tomorrow.kotlindemo.example

import android.util.Log

interface Clickable {
    fun click(): Int
    fun showOff(): Int = Log.d("TAG", "zwm, Clickable showOff called") //接口的默认实现
}

interface Focusable {
    fun showOff(): Int = Log.d("TAG", "zwm, Focusable showOff called") //接口的默认实现
}

class Button : Clickable, Focusable {
    override fun click() = Log.d("TAG", "zwm, click called")
    override fun showOff(): Int {
        super<Clickable>.showOff()
        super<Focusable>.showOff()
        return 1
    }
}

//MainActivity.kt
package com.tomorrow.kotlindemo

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.tomorrow.kotlindemo.example.Button

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var button = Button()
        button.click()
        button.showOff()
    }
}

日志打印:
2020-08-16 22:58:24.758 7785-7785/com.tomorrow.kotlindemo D/TAG: zwm, click called
2020-08-16 22:58:24.758 7785-7785/com.tomorrow.kotlindemo D/TAG: zwm, Clickable showOff called
2020-08-16 22:58:24.758 7785-7785/com.tomorrow.kotlindemo D/TAG: zwm, Focusable showOff called

2.open、final和abstract修饰符:默认为final

Java的类和方法默认是open的,而Kotlin中默认都是final的。如果你想允许创建一个类的子类,需要使用open修饰符来标示这个类。此外,需要给每一个可以被重写的属性或方法添加open修饰符。

interface Clickable {
    fun click(): Int
    fun showOff(): Int = Log.d("TAG", "zwm, Clickable showOff called")
}

open class RichButton: Clickable { //这个类是open的,其它类可以继承它

    fun disable() {} //这个函数是final的,不能在子类中重写它

    open fun animate() {} //这个函数是open的,可以在子类中重写它

    override fun click() = Log.d("TAG", "zwm, RichButton click called") //这个函数重写了一个open函数,默认也是open的,如果想禁止重写这个函数,需要在开头添加final关键字
}

在Kotlin中,同Java一样,可以将一个类声明为abstract的,这种类不能被实例化。一个抽象类通常包含一些没有实现并且必须在子类重写的抽象成员。抽象成员始终是open的,所以不需要显式地使用open修饰符。

abstract class Animated { //这个类是抽象的,不能创建它的实例
    abstract fun animate() //这个函数是抽象的,它没有实现必须被子类重写

    open fun stopAnimating() { //抽象类中的非抽象函数并不是默认open的,但是可以标注为open的
    }

    fun animateTwice() { //抽象类中的非抽象函数并不是默认open的,但是可以标注为open的
    }
}

在接口中,不能使用final、open或者是abstract。接口中的成员始终是open的,不能将其声明为final的。如果它没有函数体它就是abstract的,但是这个关键字并不是必需的。

类中访问修饰符的意义:

修饰符 相关成员 评注
final 不能被重写 类中成员默认使用
open 可以被重写 需要明确地表明
abstract 必需被重写 只能在抽象类中使用,抽象成员不能有实现
override 重写父类或者接口中的成员 如果没有使用final表明,重写的成员默认是open的

3.可见性修饰符:默认为public

Kotlin的可见性修饰符:

修饰符 类成员 顶层声明
public(默认) 所有地方可见 所有地方可见
internal 模块中可见 模块中可见
protected 子类中可见
private 类中可见 文件中可见
  • 一个模块就是一组一起编译的Kotlin文件。

  • Kotlin只把包作为在命名空间里组织代码的一种方式使用,并没有将其用作可见性控制。在Java中,可以从同一个包中访问一个protected的成员,但是Kotlin不允许这么做,在Kotlin中可见性规则非常简单,protected成员只在类和它的子类中可见。

  • 同样还要注意的是类的扩展函数不能访问它的private和protected成员。

  • 另一个Kotlin与Java可见性规则的区别就是在Kotlin中一个外部类不能看到其内部(或者嵌套)类中的private和protected成员。

internal open class TalkativeButton: Focusable {
    private fun yell() = println("Hey!")
    protected fun whisper() = println("Let's talk!")
}

fun TalkativeButton.giveSpeech() { //错误:public成员暴露了其internal接收者类型TalkativeButton
    yell() //错误:不能访问yell,它在TalkativeButton中是private的
    whisper() //错误:不能访问yell,它在TalkativeButton中是protected的
}

4.内部类和嵌套类:默认是嵌套类

Kotlin的嵌套类不能访问外部类的实例。Kotlin中没有显式修饰符的嵌套类与Java中的static嵌套类是一样的。要把它变成一个内部类来持有一个外部类的引用的话需要使用inner修饰符。

嵌套类和内部类在Java和Kotlin中的对应关系:

类A在另一个类B中声明 在Java中 在Kotlin中
嵌套类(不存储外部类的引用) static class A class A
内部类(存储外部类的引用) class A inner class A

在Kotlin中引用外部类实例的语法也与Java不同。需要使用this@Outer从Inner类去访问Outer类:

class Outer {
    inner class Inner {
        fun getOuterReference(): Outer = this@Outer
    }
}

5.密封类:定义受限的类继承结构

sealed类,为父类添加一个sealed修饰符,对可能创建的子类做出严格的限制,所有的直接子类必须嵌套在父类中。sealed修饰符隐含的这个类是一个open类,不再需要显式地添加open修饰符。

sealed class Expr {
    class Num(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr()
}

fun eval(e: Expr): Int =
    when(e) {
        is Expr.Num -> e.value
        is Expr.Sum -> eval(e.right) + eval(e.left)
    }

6.声明一个带非默认构造方法或属性的类

初始化类:主构造方法和初始化语句块

class User constructor(_nickname: String) { //带一个参数的主构造方法
    val nickname: String

    init { //初始化语句块
        nickname = _nickname
    }
}

constructor关键字用来开始一个主构造方法或从构造方法的声明。如果主构造方法没有注解或可见性修饰符,同样可以去掉constructor关键字。

init关键字用来引入一个初始化语句块。这种语句块包含了在类被创建时执行的代码,并会与主构造方法一起使用。

class User (_nickname: String) { //带一个参数的主构造方法
    val nickname = _nickname //用参数来初始化属性
}

最简洁的语法:

class User (val nickname: String) //val意味着相应的属性会用构造方法的参数来初始化

为构造方法参数声明默认值:

class User (val nickname: String, val isSubscribed: Boolean = true)

User("Kotlin")
User("Kotlin", false)
User(nickname = "Kotlin", isSubscribed = false)
User(isSubscribed = false, nickname = "Kotlin")

如果你的类具有一个父类,主构造方法同样需要初始化父类。可以通过在基类列表的父类引用中提供父类构造方法参数的方式来做到这一点:

open class User (val nickname: String)

class SubUser(nickname: String) : User(nickname)

如果没有给一个类声明任何构造方法,将会生成一个不做任何事情的默认构造方法:

open class Button //将会生成一个不带任何参数的默认构造方法

如果继承了Button类并且没有提供任何的构造方法,必须显式地调用父类的构造方法,即使它没有任何的参数:

open class Button

class RadioButton : Button() //注意:父类名称后面还需要一个括号

注意与接口的区别:接口没有构造方法,所以在实现一个接口的时候,不需要在父类型列表中它的名称后面再加上括号。

如果想要确保类不被其它代码实例化,必须把构造方法标记为private:

class User private constructor()

构造方法:用不同的方式来初始化父类

通常来讲,使用多个构造方法的类在Kotlin代码中不如在Java中常见。大多数在Java中需要重载构造方法的场景都被Kotlin支持参数默认值和参数命名的语法涵盖了。

open class View {
    constructor(ctx: Context) {
        //some code
    }

    constructor(ctx: Context, attr: String) {
        //some code
    }
}

class MyButton : View {
    constructor(ctx: Context): this(ctx, "") {
        //some code
    }

    constructor(ctx: Context, attr: String): super(ctx, attr) {
        //some code
    }
}

实现在接口中声明的属性:

在Kotlin中,接口可以包含抽象属性声明。

interface User {
    val nickname: String
    val number: String //属性没有支持字段,每次访问时计算
        get() = "968"
}

class PrivateUser(override  val nickname: String) : User //主构造方法属性

class SubscribingUser(val email: String) : User {
    override val nickname: String
        get() = email.substringBefore('@') //自定义getter,没有支持字段,每次访问时计算
}

class BookUser(email: String) : User {
    override val nickname = email.substringBefore('@') //属性初始化,有支持字段,只在类初始化计算
}

通过getter或setter访问支持字段:

class User(val name: String) {
    var address: String = "unspecified"
        get() {
            Log.d("TAG", "zwm, get name: $name address: $field") //读取支持字段的值
            return field
        }
        set(value) {
            Log.d("TAG", "zwm, set name: $name address: $field") //读取支持字段的值
            field = value //更新支持字段的值
        }
}

fun method() {
    val user = User("guangzhou")
    Log.d("TAG", "zwm, address: ${user.address}")
    user.address = "Android"
    Log.d("TAG", "zwm, address: ${user.address}")
}

日志打印:
2020-08-17 19:53:39.761 8736-8736/com.tomorrow.kotlindemo D/TAG: zwm, get name: guangzhou address: unspecified
2020-08-17 19:53:39.761 8736-8736/com.tomorrow.kotlindemo D/TAG: zwm, address: unspecified
2020-08-17 19:53:39.761 8736-8736/com.tomorrow.kotlindemo D/TAG: zwm, set name: guangzhou address: unspecified
2020-08-17 19:53:39.762 8736-8736/com.tomorrow.kotlindemo D/TAG: zwm, get name: guangzhou address: Android
2020-08-17 19:53:39.762 8736-8736/com.tomorrow.kotlindemo D/TAG: zwm, address: Android

如果显式地引用或者使用默认的访问器实现,编译器会为属性生成支持字段。如果你提供了一个自定义的访问器实现并且没有使用field(如果属性是val类型,就是getter;而如果是可变属性,则是两个访问器),支持字段将不会被呈现出来。

修改访问器的可见性:

访问器的可见性默认与属性的可见性相同。但是如果需要可以通过在get和set关键字前放置可见性修饰符的方式来修改它。

class User(val name: String) {
    var address: String = "unspecified"
        get() {
            Log.d("TAG", "zwm, get name: $name address: $field") //读取支持字段的值
            return field
        }
        private set(value) { //不能在类外部修改这个属性
            Log.d("TAG", "zwm, set name: $name address: $field") //读取支持字段的值
            field = value //更新支持字段的值
        }
}

7.编译器生成的方法:数据类和类委托

如果你要在Kotlin声明一个数据类,必须满足以下几点条件:

  • 数据类必须拥有一个构造方法,该方法至少包含一个参数,一个没有数据的数据类是没有任何用处的。
  • 与普通的类不同,数据类构造方法的参数强制使用var或者val进行声明。
  • data class之前不能用abstract、open、sealed或者inner进行修饰。
  • 在Kotlin1.1版本前数据类只允许实现接口,之后的版本既可以实现接口也可以继承类。

数据类:自动生成通用方法的实现

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

fun method() {
    val user = User("Kotlin", "guangzhou")
    val user2 = User("Kotlin", "guangzhou")
    val user3 = User("Java", "guangzhou")
    Log.d("TAG", "zwm, $user")
    Log.d("TAG", "zwm, ${user.hashCode()} ${user2.hashCode()} ${user3.hashCode()}")
    Log.d("TAG", "zwm, ${user == user2} ${user == user3}") // == 比较内容
    Log.d("TAG", "zwm, ${user.equals(user2)} ${user.equals(user3)}") // equals 比较内容
}

日志打印:
2020-08-17 20:15:40.122 12767-12767/com.tomorrow.kotlindemo D/TAG: zwm, User(name=Kotlin, address=guangzhou)
2020-08-17 20:15:40.123 12767-12767/com.tomorrow.kotlindemo D/TAG: zwm, 834326719 834326719 -225911874
2020-08-17 20:15:40.123 12767-12767/com.tomorrow.kotlindemo D/TAG: zwm, true false
2020-08-17 20:15:40.123 12767-12767/com.tomorrow.kotlindemo D/TAG: zwm, true false

数据类和不可变性:copy()方法

强烈推荐数据类只使用只读属性(val),让数据类的实例不可变。

为了让使用不可变对象的数据类变得更容易,Kotlin编译器为它们多生成了一个方法:一个允许copy类的实例的方法,并在copy的同时修改某些属性的值。创建副本通常是修改实例的好选择,副本有着单独的生命周期而且不会影响代码中引用原始实例的位置。

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

fun method() {
    val user = User("Kotlin", "guangzhou")
    val user4 = user.copy("Android")
    Log.d("TAG", "zwm, $user4")
    Log.d("TAG", "zwm, ${user.hashCode()} ${user4.hashCode()}")
    Log.d("TAG", "zwm, ${user == user4}") // == 比较内容
    Log.d("TAG", "zwm, ${user.equals(user4)}") // equals 比较内容
}

日志打印:
2020-08-17 20:27:40.210 14378-14378/com.tomorrow.kotlindemo D/TAG: zwm, User(name=Android, address=guangzhou)
2020-08-17 20:27:40.210 14378-14378/com.tomorrow.kotlindemo D/TAG: zwm, 834326719 -1165939375
2020-08-17 20:27:40.210 14378-14378/com.tomorrow.kotlindemo D/TAG: zwm, false
2020-08-17 20:27:40.210 14378-14378/com.tomorrow.kotlindemo D/TAG: zwm, false

类委托:使用by关键字

无论什么时候实现一个接口,都可以使用by关键字将接口的实现委托到另一个对象。

interface Operation {
    fun develop()
    fun fix()
}

class Robot : Operation{
    override fun develop() {
        Log.d("TAG", "zwm, Robot develop")
    }

    override fun fix() {
        Log.d("TAG", "zwm, Robot fix")
    }
}

class Man(val name: String, val robot: Robot = Robot()) : Operation by robot 

fun method() {
    val man = Man("Android")
    man.develop()
    man.fix()
}

日志打印:
2020-08-17 20:42:30.844 15527-15527/com.tomorrow.kotlindemo D/TAG: zwm, Robot develop
2020-08-17 20:42:30.844 15527-15527/com.tomorrow.kotlindemo D/TAG: zwm, Robot fix

8.object关键字:将声明一个类与创建一个实例结合起来

对象声明:创建单例易如反掌

对象声明通过object关键字引入。一个对象声明可以非常高效地以一句话来定义一个类和一个该类的变量。与类一样,一个对象声明也可以包含属性、方法、初始化语句块等的声明。唯一不允许的就是构造方法(包括主构造方法和从构造方法)。与普通类的实例不同,对象声明在定义的时候就立即创建了,不需要在代码的其它地方调用构造方法。因此,为对象声明定义一个构造方法是没有意义的。

interface Operation {
    fun develop()
    fun fix()
}

object Robot : Operation{
    override fun develop() {
        Log.d("TAG", "zwm, Robot develop")
    }

    override fun fix() {
        Log.d("TAG", "zwm, Robot fix")
    }
}

fun method() {
    Robot.develop()
    Robot.fix()
}

日志打印:
2020-08-17 20:57:48.848 16085-16085/com.tomorrow.kotlindemo D/TAG: zwm, Robot develop
2020-08-17 20:57:48.848 16085-16085/com.tomorrow.kotlindemo D/TAG: zwm, Robot fix

Java匿名内部类:

List<String> list = Arrays.asList("AAA", "BBB", "CCC");
Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        if(o1 == null)
            return -1;
        if(o2 == null)
            return 1;
        return o1.compareTo(o2);
    }
});

object表达式:

val list = listOf("AAA", "BBB", "CCC")
val comparator = object : Comparator<String> {
    override fun compare(o1: String?, o2: String?): Int {
        if (o1 == null)
            return -1
        if (o2 == null)
            return 1
        return o1.compareTo(o2)
    }
}
Collections.sort(list, comparator)

Lambda表达式:

val list = listOf("AAA", "BBB", "CCC")
val comparator = Comparator<String> { o1, o2 ->
    if (o1 == null)
        return@Comparator -1
    if (o2 == null)
        return@Comparator 1
    return@Comparator o1.compareTo(o2)
}
Collections.sort(list, comparator)

对象表达式与Lambda表达式哪个更适合代替匿名内部类:

当你的匿名内部类使用的类接口只需要实现一个方法时,使用Lambda表达式更适合;当匿名内部类内有多个方法实现的时候,使用object表达式更加合适。

在Java中使用Kotlin对象:

//shapes.kt
interface Operation {
    fun develop()
    fun fix()
}

object Robot : Operation{
    override fun develop() {
        Log.d("TAG", "zwm, Robot develop")
    }

    override fun fix() {
        Log.d("TAG", "zwm, Robot fix")
    }
}

//JavaDemo.java
public class JavaDemo {

    public void testDemo() {
        Robot.INSTANCE.develop();
        Robot.INSTANCE.fix();
    }
}

//MainActivity.kt
class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val javaDemo = JavaDemo()
        javaDemo.testDemo()
    }
}

日志打印:
2020-08-17 21:00:30.267 16653-16653/com.tomorrow.kotlindemo D/TAG: zwm, Robot develop
2020-08-17 21:00:30.268 16653-16653/com.tomorrow.kotlindemo D/TAG: zwm, Robot fix

9.伴生对象:工厂方法和静态成员的地盘

Kotlin中的类不能拥有静态成员,Java的static关键字并不是Kotlin语言的一部分。作为替代,Kotlin依赖包级别函数和对象声明,在大多数情况下,还是推荐使用顶层函数。但是顶层函数不能访问类的private成员。

在类中定义的对象之一可以使用一个特殊的关键字来标记:companion。如果这样做,就获得了直接通过容器类名称来访问这个对象的方法和属性的能力,不再需要显式地指明对象的名称。最终的语法看起来非常像Java中的静态方法调用。

”伴生“是相较于一个类而言的,意为伴随某个类的对象,它属于这个类所有,因此伴生对象跟Java中static修饰效果性质一样,全局只有一个单例。它需要声明在类的内部,在类被装载时会被初始化。

class A {
    companion object {
        fun bar() {
            Log.d("TAG", "zwm, bar")
        }
    }
}

fun method() {
    A.bar()
}

日志打印:
2020-08-17 22:37:00.904 21463-21463/com.tomorrow.kotlindemo D/TAG: zwm, bar

定义一个拥有多个构造方法的类:

class User {
    val nickname: String

    constructor(first: String) { //从构造方法
        nickname = first
    }

    constructor(first: String, second: String) { //从构造方法
        nickname = first + second
    }
}

fun method() {
    var user1 = User("Tomy")
    var user2 = User("Tomy", "Zhang")
}

使用工厂方法来代替从构造方法:

class User private constructor(val nickname: String) { //将主构造方法标记为私有
    companion object { //伴生对象可以访问容器类的private成员
        fun newUser(first: String) = User(first) //用工厂方法创建容器类对象
        fun newUser(first: String, second: String) = User(first + second) //用工厂方法创建容器类对象
    }
}

fun method() {
    var user1 = User.newUser("Tomy")
    var user2 = User.newUser("Tomy", "Zhang")
}

作为普通对象使用的伴生对象:

伴生对象是一个声明在类中的普通对象,它可以有名字,实现一个接口或者有扩展函数或属性。

class User private constructor(val nickname: String) {
    companion object BuildUser {
        fun newUser(first: String) = User(first)
    }
}

fun method() {
    var user1 = User.BuildUser.newUser("Tomy")
    var user2 = User.newUser("Tomy")
}

在伴生对象中实现接口:

interface Operation {
    fun code()
}

class User private constructor(val nickname: String) {

    companion object BuildUser : Operation{
        fun newUser(first: String) = User(first)

        override fun code() {
            Log.d("TAG", "zwm, companion code")
        }
    }
}

fun method() {
    var user1 = User.BuildUser.newUser("Tomy")
    var user2 = User.newUser("Tomy")
    User.code()
}

日志打印:
2020-08-17 23:18:28.697 22500-22500/com.tomorrow.kotlindemo D/TAG: zwm, companion code

在Java中调用使用伴生对象:

//shapes.kt
class User private constructor(val nickname: String) {
    companion object{ //伴生对象没有命名
        fun newUser(first: String) = User(first)
    }
}

//JavaDemo.java
public class JavaDemo {

    public void testDemo() {
        User.Companion.newUser("Android");
    }
}
//shapes.kt
class User private constructor(val nickname: String) {
    companion object BuildUser { //伴生对象有命名
        fun newUser(first: String) = User(first)
    }
}

//JavaDemo.java
public class JavaDemo {

    public void testDemo() {
        User.BuildUser.newUser("Android");
    }
}

伴生对象扩展:

//shapes.kt
package com.tomorrow.kotlindemo.example

class User(val username: String) {
    companion object
}

//extensions.kt
package com.tomorrow.kotlindemo.example

import android.util.Log

fun User.Companion.code(language: String) {
    Log.d("TAG", "zwm, I can code $language")
}

//MainActivity.kt
package com.tomorrow.kotlindemo

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.tomorrow.kotlindemo.example.User
import com.tomorrow.kotlindemo.example.code

class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        User.code("Android")
    }
}

日志打印:
2020-08-18 00:40:33.809 27382-27382/? D/TAG: zwm, I can code Android

对象表达式:改变写法的匿名内部类

object关键字不仅仅能用来声明单例式的对象,还能用来声明匿名对象。匿名对象替代了Java中匿名内部类的用法。

interface Clickable {
    fun click()
}

class Button(val listener: Clickable) {
    fun perform() {
        listener.click()
    }
}

fun method() {
    val button = Button(
        object : Clickable { //匿名对象
            override fun click() {
                Log.d("TAG", "zwm, button click")
            }
        }
    )
    button.perform()
}

日志打印:
2020-08-18 00:59:53.301 27756-27756/com.tomorrow.kotlindemo D/TAG: zwm, button click
interface Clickable {
    fun click()
}

class Button(val listener: Clickable) {
    fun perform() {
        listener.click()
    }
}

fun method() {
    val listener = object : Clickable { //用变量保存匿名对象
        override fun click() {
            Log.d("TAG", "zwm, button click")
        }
    }
    val button = Button(listener)
    button.perform()
}

日志打印:
2020-08-18 01:05:09.892 28182-28182/com.tomorrow.kotlindemo D/TAG: zwm, button click

与Java匿名内部类只能扩展一个类或实现一个接口不同,Kotlin的匿名对象可以实现多个接口或者不实现接口:

fun method() {
    val listener = object : Clickable, Focusable { //匿名对象实现多个接口
        override fun focus() {
            Log.d("TAG", "zwm, button focus")
        }

        override fun click() {
            Log.d("TAG", "zwm, button click")
        }
    }
    listener.click()
    listener.focus()
}
fun method() {
    val listener = object { //匿名对象不实现接口
          fun click() {
            Log.d("TAG", "zwm, button click")
        }
    }
    listener.click()
}

与对象声明不同,匿名对象不是单例的。每次对象表达式被执行都会创建一个新的对象实例。

与Java的匿名类一样,在对象表达式中的代码可以访问创建它的函数中的变量。但是与Java不同,访问并没有被限制在final变量,还可以在对象表达式中修改变量的值:

interface Clickable {
    fun click()
}

fun method() {
    var clickCount = 0
    val listener = object : Clickable {
        override fun click() {
            clickCount++
            Log.d("TAG", "zwm, button click: ${clickCount}")
        }
    }
    listener.click()
}

日志打印:
2020-08-18 01:27:40.775 30071-30071/com.tomorrow.kotlindemo D/TAG: zwm, button click: 1
interface Clickable {
    fun click()
}

class Outer {
    var param = 99

    val listener = object : Clickable {
        override fun click() {
            this@Outer.param++
            Log.d("TAG", "zwm, button click: ${this@Outer.param}")
        }
    }

    fun perform() {
        listener.click()
    }
}

fun method() {
    Outer().perform()
}

日志打印:
2020-08-18 01:43:26.467 30492-30492/com.tomorrow.kotlindemo D/TAG: zwm, button click: 100

对象表达式在需要在匿名对象中重写多个方法时是最有用的。如果只需要实现一个单方法的接口(就像Runnable),可以将你的实现写作函数字面值(lambda)并依靠Kotlin的SAM转换,把函数字面值转换成单抽象函数接口的实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335