Kotlin 快速入门

前言

人生苦多,快来 Kotlin ,快速学习Kotlin!html

什么是Kotlin?

Kotlin 是种静态类型编程语言 用于现代多平台应用 100%可与Java™和Android™互操做,它是[JetBrains]开发的基于JVM的语言 开发IDE : Intellij / AndroidStudio3.0 previewjava

参考: Kotlin 官网 / Kotlin 语言中文站node

Example

Github KotlinDemo Hexo Siteandroid

源文件与包

Kotlin 源文件以 kt 结尾. 源文件全部内容(不管是类仍是函数)都包含在声明的包内. NOTE: 源文件一般以包声明开头, 没有指明包,则该文件的内容属于无名字的默认包(属于root package)。git

package demo 
复制代码

NOTE: 若声明的包路径与文件路径不一致,亦能够正常编译. 不过会有如Java 同样的警告 Package directive doesn't match file locationgithub

默认导入

Kotlin 如同Java 同样 默认源文件会导入如下包:web

kotlin.*
kotlin.annotation.*
kotlin.collections.*
kotlin.comparisons.* (自 1.1 起)
kotlin.io.*
kotlin.ranges.*
kotlin.sequences.*
kotlin.text.*
//根据目标平台还会导入额外的包:
JVM:
java.lang.*
kotlin.jvm.*
JS:
kotlin.js.*
复制代码

import

导入语法express

import (used by preamble)
  : "import" SimpleName{"."} ("." "*" | "as" SimpleName)? SEMI?
  ;`
复制代码

Java vs Kotlin 1.若是出现名字冲突,Kotlin 可使用 as 关键字在本地重命名冲突项来消歧义 2.Kotlin 的关键字 import 不只仅限于导入类,还能够导入顶层函数及属性,在对象声明中声明的函数和属性,枚举常量等.编程

NOTE: 与 Java 不一样,Kotlin 没有单独的 import static 语法; 全部这些声明都用 import 关键字导入。小程序

顶层声明

1.同 package 下的Kotlin 源文件,在顶层所声明的常量,变量以及函数等不容许重复定义,不然报Conflicting 错误。

2.若声明用 private 可见性修饰符修饰时,属于当前文件私有。

基本数据类型

Numbers

Kotlin 一切都是对象。Kotlin提供如下表明数字、字符的内置类型(这很接近Java)

Type Bit width 包装器类型
Double 64 Double
Float 32 Float
Long 64 Long
Int 32 Integer
Short 16 Short
Byte 8 Byte
Char 16 (Unicode character) Character

NOTE:仅 Char 不是Kotlin的数字。以下

val c:Char='c'
val i: Int = c.toInt()
println(c) // 'c'
println(i) // 99
复制代码

字面常量

Kotlin 惟独不支持八进制

  • 十进制: 123
  • 二进制: 0b00001011
  • 十六进制: 0x0F

Kotlin 数值表示方法

  • 默认 Double:123.5123.5e10
  • Float 用 f 或者 F 标记: 123.5f
  • Long 用大写 L 标记: 123L

NOTE:支持数字字面值中的下划线(自 kotlin1.1 起)

val oneMillion = 1_000_000
复制代码

Kotlin 装箱机制

Kotlin 内置类型在 Java 平台上是存储为 JVM 的原生类型,但一个可空的引用(如 Int?)或泛型状况下(如 Array<Int>,List<Int> ...) 会把数字和字符自动装箱成相应包装类, 请参考 Numbers

val low = -127
val high = 127
val noInIntegerCache = 128
var boxedA: Int? = low
var anotherBoxedA: Int? = low
println(boxedA == anotherBoxedA) //true
println(boxedA === anotherBoxedA) //true
boxedA = high
anotherBoxedA = high
println(boxedA == anotherBoxedA) //true
println(boxedA === anotherBoxedA) //true
boxedA = noInIntegerCache
anotherBoxedA = noInIntegerCache
println(boxedA == anotherBoxedA) //true
println(boxedA === anotherBoxedA) //false
复制代码

===== 请参考 类型相等性

val anIntegerA: Int? = 123 //对应 java.lang.Integer 一个装箱的 Int
val anIntegerB: Int? = 123 //对应 java.lang.Integer
println(anIntegerA === anIntegerB) //true
println(anIntegerA?.javaClass) //int
println((anIntegerA as Number).javaClass) //java.lang.Integer
val anIntegerArray: Array<Int> = arrayOf(1,2,3)
val anIntegerList: List<Int> = listOf(1,2,3)
println(anIntegerArray.toString())
println(anIntegerList.toString())
println((anIntegerList[0] as Number).javaClass) //
复制代码

NOTE:一个可空的引用(如 Int?)能不能装换成 Int ,答案是确定的。强制转换或者 !!

val anIntegerA: Int? = 123 
 val anNewIntA: Int = anIntegerA  //编译错误
 val anNewIntB: Int = anIntegerA!!  //或者 anIntegerA as Int
 val anNewIntC: Int = anIntegerA //Start cast to kotlin.Int

复制代码

显式转换

每一个数字类型支持以下的转换:

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double
  • toChar(): Char

以下示例 Java 中,包装类不能隐式转换, Kotlin 也是如此, 不一样类型之间是不能相互隐式转换的。

Byte a  = 1;
Integer b = a;//error
复制代码
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b // 错误
复制代码

运算

Kotlin支持数字运算的标准集,运算被定义为相应的类成员(但编译器会将函数调用优化为相应的指令)。 参见操做符重载

对于位运算,没有特殊字符来表示,而只可用中缀方式调用命名函数,例如:

val x = (1 shl 2) and 0x000FF000
复制代码

这是完整的位运算列表(只用于 IntLong):

  • shl(bits) – 有符号左移 (Java 的 <<)
  • shr(bits) – 有符号右移 (Java 的 >>)
  • ushr(bits) – 无符号右移 (Java 的 >>>)
  • and(bits) – 位与
  • or(bits) – 位或
  • xor(bits) – 位异或
  • inv() – 位非

字符串

字符串用 String 类型表示。字符串是不可变的。 字符串的元素——字符可使用索引运算符访问: s[i]。 能够用 for 循环迭代字符串:

val s = "Hello, world!\n" //字符串字面值
//字符串
for (c in s) {
    print(c)
}
//原生字符串 使用三个引号(""")分界符括起来
val text = """ for (c in s) { print(c) } """
println(text)
//字符串模板
val str = "$s.length is ${s.length}"
println(str)
复制代码

NOTE: 模板表达式以美圆符($)开头,若要对象属性时要花括号括起来,若要表示字面值 $ 字符z则:

val price = "${'$'}9.99"
println(price)
复制代码

数组

数组在 Kotlin 中使用 Array 类来表示,它定义了 getset 函数(按照运算符重载约定这会转变为 [])和 size 属性,以及一些其余有用的成员函数:

class Array<T> private constructor() {
    val size: Int
    operator fun get(index: Int): T
    operator fun set(index: Int, value: T): Unit

    operator fun iterator(): Iterator<T>
    // ……
}
复制代码

Library.ktarrayOf() arrayOfNulls 函数以及Array构造函数能建立数数组:

val args: Array<Int> = arrayOf(1, 2, 3)
val arrayOfNulls = arrayOfNulls<Int>(10) //空数组
val initArray = Array(5, { i -> (i * i).toString() }) //构造函数init
println(arrayOfNulls.size)
复制代码

NOTE: 与 Java 不一样的是,Kotlin 中数组是不型变的(invariant)。这意味着 Kotlin 不让咱们把 Array<String>赋值给 Array<Any>,以防止可能的运行时失败(可是你可使用 Array<out Any>, 参见类型投影)。

以前所说的在泛型状况下Kotlin 会把数字和字符自动装箱成相应包装类, Arrays.kt 中有如下

ByteArray CharArray ShortArray IntArray LongArray FloatArray DoubleArray BooleanArray

无装箱开销的专门的类来表示原生类型数组, 和 Array 并无继承关系,可是它们有一样的方法属性集。它们也都有相应的工厂方法。

val x: IntArray = intArrayOf(1, 2, 3)
 x[0] = x[1] + x[2]
复制代码

数组迭代经过 iterator() 函数返回 Iterator<T> 对象进行迭代:

val iterator = args.iterator()
 while (iterator.hasNext()) {
     print("" + iterator.next())
 }
 println()
 //forEach
 args.iterator().forEach { print(it) }
 println()
 //for-
 for (it in initArray/*.iterator()*/) {
     print(it)
 }
 println()
 //下标索引
 args.forEachIndexed { index, i -> println("$index = $i") }
复制代码

NOTE: forEach forEachIndexed 这些是Array 的扩展函数, 背后实现也是 [for 循环 ](#For 循环)

区间

区间表达式由具备操做符形式 ..rangeTodownTo 函数辅以 in!in 造成。 区间是为任何可比较类型(Comparable<in T>)定义的,但对于整型原生类型(Int ,Long,Char),Ranges.kt 中实现了经常使用的整型区间(IntRangeLongRangeCharRange),而在 Primitives.kt中的 Int ,Long,Char 类实现了rangeTo 函数。如下是使用区间的一些示例

println((1.rangeTo(3)).contains(1)) //使用区间rangeTo函数
println(1 in (1..3)) //使用区间操做符
复制代码

.. 建立一个区间, 实际是调用 rangeTo 函数返回原生类型 *Range 对象, in 则调用 contains函数。in *Range 还能够用在迭代(for-循环)中。

for (index in 1..4) print(index)
复制代码

NOTE:rangeTo 建立的区间, 范围值是小到大, downTo 反之。他们默认 step 分别为1,-1

// val intRange = 1..4 //step 1 default
    val intRange = 1..4 step 2 //step 2
    val is2 = 2 in intRange
    val is4 = 4 in intRange
    println("first = ${intRange.first},last = ${intRange.last},step = ${intRange.step}")
    println(is2)
    println(is4)
    println(is2 or is4)

// for (index in 1..4) print(index)
    for (index in intRange) print(index)
    println()
	for (index in intRange.reversed()) print(index)
	println()

    for (index in 10..1) print(index) //Nothing
    println()
    val intProgression = 10 downTo 1 /*step 2*/ //step默认为1 倒序迭代
    println("first = ${intProgression.first},last = ${intProgression.last},step = ${intProgression.step}")
    for (index in intProgression) print(index)
    println()

    for (index in 1..4) print(index) // 输出“1234”

    val isIn = 3 in 1.rangeTo(100)
    println(isIn)

    for (i in 'a'..'z') print(i)
复制代码

背后实现原理

区间实现了该库中的一个公共接口:ClosedRange<T>

ClosedRange<T> 在数学意义上表示一个闭区间,它是为可比较类型定义的。 它有两个端点:startendInclusive 他们都包含在区间内。 其主要操做是 contains,一般以 in/!in 操做符形式使用。

整型数列(IntProgressionLongProgressionCharProgression)表示等差数列。 数列由 first 元素、last 元素和非零的 step 定义。 第一个元素是 first,后续元素是前一个元素加上 steplast 元素总会被迭代命中,除非该数列是空的。

数列是 Iterable<N> 的子类型,其中 N 分别为 IntLong 或者 Char,因此它可用于 for-循环以及像 mapfilter 等函数中。 对 Progression 迭代至关于 Java/JavaScript 的基于索引的 for-循环:

for (int i = first; i != last; i += step) {
  // ……
}

复制代码

对于整型类型,.. 操做符建立一个同时实现 ClosedRange<T>*Progression 的对象。 例如,IntRange 实现了 ClosedRange<Int> 并扩展自 IntProgression,所以为 IntProgression 定义的全部操做也可用于 IntRangedownTo()step() 函数的结果老是一个 *Progression

数列由在其伴生对象中定义的 fromClosedRange 函数构造:

IntProgression.fromClosedRange(start, end, step)

复制代码

数列的 last 元素这样计算:对于正的 step 找到不大于 end 值的最大值、或者对于负的 step 找到不小于 end 值的最小值,使得 (last - first) % increment == 0

一些实用函数

  • rangeTo()
  • downTo()
  • reversed()
  • step()

程序结构

变量

分别使用 var ,val 声明可变和不可变的变量.例子以下

val s = "Example" // A Immutable String
var v = "Example" // A Mutable String
复制代码

声明可变变量语法

var <propertyName>[: <PropertyType>] [= <property_initializer>]
复制代码

声明不可变变量(仅赋值一次只读变量)语法

var <propertyName>[: <PropertyType>] = <property_initializer>
复制代码

默认 Kotlin 变量类型是能经过赋值时智能推断该变量的类型,且该var变量只能该类型的的值。显式肯定变量类型,必需要接收该类型的初始化。经过一个简单例子说明

val aImmutableIntVariable = 0x001 //aImmutableIntVariable 类型为 Int
var aMutableIntVariable: Int = "0x002" //语法error
var bMutableIntVariable: Int = 0x002
var cMutableVariable: Any //显式肯定变量类型,必需要接收该类型的初始化。

aImmutableIntVariable = 1 //不能从新分配 Val cannot be reassigned
bMutableIntVariable = ""//一旦类型肯定,只能接受该类型的值
复制代码

NOTE: var 变量直接赋值为 null ,该变量则不符合预期的类型 简单来讲(Nothing),再次赋值时报错。

var aNullable = null
aNullable = 1;//Nothing
复制代码

更详细的类型介绍:类型安全和智能转换

常量 (编译期)

已知值的属性可使用 const 修饰符标记为 编译期常量.必须知足如下需求

  1. 位于顶层或者是 object 的一个成员
  2. String 或原生类型 值初始化
  3. 没有自定义 getter
const val CONST_VAL = 1
//const val CONST_VAL_GET get() = 1 //Error: Const 'val' should not have a getter
//const val CONST_VAL_TEST :Any = 1 //error
fun testConstInFunction() {
// const val CONST_VAL = 1 //error
}
object Kotlin {
    const val CONST_VAL: String = "object 常量"
}
复制代码

幕后字段

Kotlin 中类不能有字段。然而,当使用自定义访问器时,有时有一个幕后字段(backing field)有时是必要的。为此 Kotlin 提供一个自动幕后字段,它可经过使用 field 标识符访问。

var counter = 0 // 此初始器值直接写入到幕后字段
set(value) {
  if (value >= 0)
  field = value
}
复制代码

field 标识符只能用在属性的访问器内。

若是属性至少一个访问器使用默认实现,或者自定义访问器经过 field 引用幕后字段,将会为该属性生成一个幕后字段。

例如,下面的状况下, 就没有幕后字段:

val isEmpty: Boolean
get() = this.size == 0
复制代码

若是你的需求不符合这套“隐式的幕后字段”方案,那么总可使用 幕后属性(backing property)

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // 类型参数已推断出
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }
复制代码

从各方面看,这正是与 Java 相同的方式。由于经过默认 getter 和 setter 访问私有属性会被优化,因此不会引入函数调用开销。

控制流:if、when、for、while

if 语句、if - else 表达式

在 Kotlin 中,没有Java中三元运算符(条件 ? 而后 : 不然), 由于if - else 是一个表达式,即它会返回一个值。

val num1 = 1
val num2 = 2
val max = if (num1 > num2) num1 else num2
println(max)
println(if (num1 < num2) "if - else 表达式" else num2)
复制代码

if的分支能够是代码块,最后的表达式做为该块的值:

println(
        if (num1 < num2) {
            println("num1 < num2")
            "if - else 表达式"
        } else num2
)
复制代码

When 表达式

在 Kotlin 中,when 取代了Java中 switch 。声明语法以下:

when[(表达式)]{
  [条件分支1,条件分支2(可多个 逗号分隔)] -> controlStructureBody [SEMI]
  [else 分支] -> controlStructureBody [SEMI]
}
复制代码

SEMI 表明 ;或者 换行 , 在controlStructureBody 是代码块且有变量声明时使用, 示例:

when {
} //最简单的形式

val randomNum = Random().nextInt(5)
when (randomNum) {
  0, 1 -> println("randomNum == 0 or randomNum == 1") //多个分支条件放在一块儿,用逗号分隔
  2 -> println("randomNum == 2")
  else -> println("otherwise")
}

//若是不提供参数,全部的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支
when {
  randomNum == 0 -> {
    var a = 2; println("is 0") //;
    var b = 3
    println("is 0") //换行
  }
}
//其余分支都不知足条件将会求值 else 分支
when {
  randomNum == 0 -> println("randomNum is 0")
  randomNum == 1 -> println("randomNum is 1")
  else -> println("otherwise")
}
复制代码

NOTE: when 做为一个表达式使用,则必须有 else 分支, 除非全部的可能状况都已经覆盖了。

val an = when (1) {
    1 -> 1
    else -> "never arrive"
}
println(an)
when (randomNum == 3) {
    true -> println("is 3")
    false -> println(randomNum)
}
复制代码

For 循环

for 循环能够对任何提供迭代器(iterator)的对象进行遍历。语法

for (item in collection) print(item)
for (index in collection.indices) print(collection[index])
复制代码

示例

val array = arrayOf(1, 2, 3)
//for
for (index in array.indices) print(array[index]);println() //索引遍历一个数组或者一个 list
for (item in array) print(item);println()
//库函数 forEachIndexed
array.forEachIndexed { index, item ->  print("[$index] = $item \t")}
println()
//库函数 withIndex
for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}
复制代码

如上所述,for 能够循环遍历任何提供了迭代器的对象。即:

  • 有一个成员函数或者扩展函数 iterator(),它的返回类型 Iterator<T>
  • 有一个成员函数或者扩展函数 next()
  • 有一个成员函数或者扩展函数 hasNext() 返回 Boolean

这三个函数都须要标记为 operator

While 循环

whiledo..while 照常使用。小白应该也能够搞掂吧。。。

循环中的Break和continue

在循环中 Kotlin 支持传统的 breakcontinue 操做符。

返回和跳转

Kotlin 有三种结构化跳转表达式:

  • return。默认从最直接包围它的函数或者匿名函数返回。
  • break。终止最直接包围它的循环。
  • continue。继续下一次最直接包围它的循环。

标签

在 Kotlin 中任何表达式均可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@fooBar@都是有效的标签(参见语法)。 要为一个表达式加标签,咱们只要在其前加标签便可。

Break 和 Continue 的标签控制跳转, return 标签控制返回目标,示例:

out@ for (i in 1..3) {
    for (j in 1..3) {
        if (j == 2) break@out;print("[$i , $j] ")
    }
}
println()
out@ for (i in 1..3) {
    for (j in 1..3) {
        if (j == 2) continue@out;print("[$i , $j] ")
    }
}
println()
var nullInt: Int? = 1
  nullInt = null
var anLong = nullInt?.toLong() ?: return
fun performLabelReturn() {
    val array = arrayOf(1, 2, 3)
    array.forEach {
        if (it == 2) return  //
        println(it)
    }
    println("performLabelReturn can't reach")
}
performLabelReturn()
println()
fun performLabelLambdaLimitReturn() {
    val array = arrayOf(1, 2, 3)
    array.forEach {
        if (it == 2) return@forEach //
        println(it)
    }
    println("performLabelLambdaLimitReturn can reach")
}
performLabelLambdaLimitReturn()
println()
fun performLabelLimitReturn() {
    val array = arrayOf(1, 2, 3)
    array.forEach limit@ {
        if (it == 2) return@limit //
        println(it)
    }
    println("performLabelLimitReturn can reach")
}
performLabelLimitReturn()
println()
fun performLabelAnonymousLambdaLimitReturn() {
    val array = arrayOf(1, 2, 3)
    array.forEach(fun(value: Int) {
        if (value == 2) return  // local return to the caller of the anonymous fun, i.e. the forEach loop
        println(value)
    })
    println("performLabelAnonymousLambdaLimitReturn can reach")
}
performLabelAnonymousLambdaLimitReturn()
println()
fun a(): Int {
    return@a 12
}
println(a())
复制代码

面向对象

类与对象

声明类(class)语法

[访问修饰符 默认public] [非访问修饰符 默认 final] class 类名 
	[访问修饰符 默认public] [主构造函数] [参数] [: 父类 默认为 Any]  [类体]
复制代码

定义类,咱们经过下面的例子来讲明:

class EmptyClass 
println(EmptyClass() is Any)
复制代码

NOTE: [] 表明能够省略. Kotliin 中修饰符 与Java 略不一样,Java语言提供了不少修饰符,主要分为如下两类:

  • 访问修饰符
  • 非访问修饰符

更详细的 Java 修饰符 请参考 Java 修饰符 _ 菜鸟教程

Kotliin 中没显式声明修饰符 ,默承认见性是 public

访问控制修饰符

类、对象、接口、构造函数、方法、属性和它们的 setter 均可以有 可见性修饰符。 (getter 老是与属性有着相同的可见性。) 在 Kotlin 中有这四个可见性修饰符:privateprotectedinternalpublic

修饰符 是否支持顶级声明 当前文件 同一模块
private Y Y N
protected N ~~~~ ~~~~
internal Y Y Y
public Y Y Y

NOTE:

  1. protected 不支持顶级声明,由于文件没有继承关系。

  2. internal 是编译在一块儿的一套 Kotlin 文件:

    • 一个 IntelliJ IDEA 模块;
    • 一个 Maven 项目;
    • 一个 Gradle 源集;
    • 一次 <kotlinc> Ant 任务执行所编译的一套文件。
  3. 对于类和接口内部声明的成员可见修饰符与Java 相似:

    • private 仅该类和接口内部可见;
    • protectd 该类和接口内部可见且子类可见
    • internal 该模块内 可见
    • public 均可见

非访问控制修饰符

kotlin 定义类、对象、构造函数、方法、属性时默认加了 final 修饰符, 接口默认是 open 与之相反。能被继承、被覆盖。

NOTE:在 final 修饰 class 下 用 open 修饰该类的成员无效,在 final 缺省修饰符下 再用 final 修饰显得 Redundant 冗余,但在 override 时可以使用final 关键字再度修饰

咱们经过下面的例子来讲明:

open class Father {
    private val name = "哔哔" //private can't open
    protected open val bloodType = "AB"
    internal val number = 1000
    open val age = 28

    protected class Nested {
        val body = {}
        private val cipher = null

        private fun print() {
            //can't access private
// println(name)
// println(bloodType)
// println(number)
// println(age)

            body

        }
    }

    open fun print() {
        println(name) //can't access private
        println(bloodType)
        println(number)
        println(age)

        Nested().body

// Nested().cipher//Kotlin 中外部类不能访问内部类的 private 成员

    }

}

class Son : Father() {
    override final val bloodType: String = "O" //protected // final Redundant
// override public val bloodType: String = "O" // 能覆盖

    override val age: Int = 10 // public

    override open fun print() { //Warning: 'open' has no effect in a final class
// println(name) //can't access private
        println(bloodType)
        println(number)
        println(age)

        Nested().body
    }

}

open class BigSon : Father() {
    override final val bloodType: String = "AB"  //can use final
}
复制代码

NOTE:局部变量、函数和类不能有可见性修饰符。Kotlin 中外部类不能访问内部类的 private 成员(与Java不一样)。

类成员

类能够包含

构造函数

一个类能够有一个主构造函数和一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名(和访问修饰符 [默认 public])后。主构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,而且这些修饰符在它前面。非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数。构造函数的可见性是 public。

NOTE:若要修改主构造函数的可见性,须要添加一个显式 constructor 关键字

class A private constructor() { …… }
复制代码

Kotlin 十分简便, 能够在主构造函数内声明属性(可变的(var)或只读的(val))以及初始化属性默认值(次构造函数是不容许的), 且为该类成员属性, 主构造函数内不能包含除了声明属性任何的代码。提供了 init 关键字做为前缀的初始化块(initializer blocks)

次构造函数

声明在类体内以 constructor 关键字的函数。若该类有主构造函数,次构造函数都须要用 this 关键字直接或间接委托给主构造函数。

open class Person /*private*/ constructor(firstName: String) {
    class A //empty class 下面接着是次构造函数 ,Error: Expecting member declaration, 期待成员声明

    val money = 1000_000

    init {
        println("init block: firstName= $firstName")
        println("init block: money= $money")
    }

    //次构造函数
    constructor(firstName: String, age: Int) : this(firstName) {
        println("secondary constructor: firstName= $firstName")
        println("secondary constructor: age= $age")
        println("init block: money= $money")
    }

    constructor (firstName: String, age: Int, money: Int) : this(firstName, age) {
        println("secondary constructor: firstName= $firstName")
        println("secondary constructor: age= $age")
        println("init block: money= $money")
    }

}
复制代码
注意:在 JVM 上,若是主构造函数的全部的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值。这使得 Kotlin 更易于使用像 Jackson 或者 JPA 这样的经过无参构造函数建立类的实例的库。

class Customer(val customerName: String = "")
复制代码

建立类的实例

Kotlin 并不须要 new 关键字建立实例, 像普通函数同样调用构造函数便可。

继承

Java 的超类是 Object , 而 Kotlin 的是 Any。

若父类有主构造函数且带参数,子类必须用主构造函数将参数初始化,以下:

class Student(firstName: String) : Person(firstName) {
}
复制代码

注意:参数初始化时,子父类必须一致。

父类没有主构造函数, 那么每一个次构造函数必须使用 super 关键字初始化其基类型。

open class Human {
    constructor(name: String) {

    }

    constructor(name: String, age: Int) {

    }
}

class Woman : Human {
    constructor(name: String) : super(name)
    constructor(name: String, age: Int) : super(name, age)
}

//容许经过主构造函数覆盖次构造函数
class Man(name: String) : Human(name)
复制代码

覆盖(override)

final , open 是否可覆盖修饰符 和 override 标注覆盖类、对象、接口、构造函数、方法、属性。

覆盖规则

在 Kotlin 中,实现继承由下述规则规定:若是一个类从它的直接超类继承相同成员的多个实现, 它必须覆盖这个成员并提供其本身的实现(也许用继承来的其中之一)来消除歧义。 为了表示采用 从哪一个超类型继承的实现,咱们使用由尖括号中超类型名限定的 super ,如 super :

open class Thread {
    open fun run() {
        println("Thread#run")
    }

    fun start() {
        println("Thread#start")
    }
}

interface Runnable {
    fun run() {
        println("Thread#run")
    } // 接口成员默认就是“open”的


}

class HandlerThread() : Runnable, Thread() {
    //编译器要求覆盖 run():
    override fun run() {
        super<Thread>.run() // 调用 Thread.run()
        super<Runnable>.run() // 调用 Runnable.run()
    }
}
复制代码

抽象类

类和其中的某些成员能够声明为 abstract 。 抽象成员在本类中能够不用实现。 须要注意的是,咱们并不须要用 open 标注一个抽象类或者函数——由于这不言而喻。

abstract class AbstractClass{ //open 多余的,由于抽象类终究是父类,因此更不能用final 修饰
    open fun doSomething() {
    }

    abstract fun fly() //子类必须 override
}

class AbstractClassImpl : AbstractClass() {
    override fun fly() {
    }

    override fun doSomething() {//override 开放成员
        super.doSomething()
    }
}
复制代码

接口

用关键字 interface 来定义接口。Kotlin 的接口函数能够有实现, 属性必须是抽象的(默认抽象), 或者提供 get 访问器实现, 且不能有幕后字段(backing field)。

fun main(args: Array<String>) {
    val kotlinLanguage = KotlinLanguage()
    println(kotlinLanguage.language)
    println(kotlinLanguage.that)
    println(kotlinLanguage.that === kotlinLanguage)
    kotlinLanguage.onReady()
    kotlinLanguage.onUpgrade()

    MultipurposePrinter().print()
}

interface KotlinInterface {
    val language get() = "Kotlin"
    val that: KotlinInterface

    fun onUpgrade() {
        println("call#onUpgrade")
    }

    fun onReady() //

}

class KotlinLanguage : KotlinInterface {
    override val that: KotlinInterface
        get() = this

    override fun onReady() {
        println("call#onReady")
    }

}

interface Printer {
    fun print()
}

interface ColorPrinter : Printer {
    override fun print() {
        println("ColorPrinter#print")
    }

// val printerType get() = "ColorPrinter"
}


interface BlackPrinter : Printer {
    override fun print() {
        println("BlackPrinter#print")
    }

    val printerType get() = "BlackPrinter"
}

class MultipurposePrinter : ColorPrinter, BlackPrinter {

    override fun print() {
        println("MultipurposePrinter#print")
        super<BlackPrinter>.print()
        super<ColorPrinter>.print()

        super.printerType
    }
}
复制代码

嵌套类和内部类

类能够嵌套在其余类中

fun main(args: Array<String>) {
    println(KotlinNestedInnerClass.KotlinNestedClass().bra())
    println(KotlinNestedInnerClass().KotlinInnerClass().bra())
    println(KotlinNestedInnerClass().KotlinInnerClass().reference())
}

private class KotlinNestedInnerClass {
    private val bra: String = "C"

    class KotlinNestedClass {
        fun bra() = KotlinNestedInnerClass().bra
    }

    //内部类 标记为 inner 以便可以访问外部类的成员。内部类会带有一个对外部类的对象的引用
    inner class KotlinInnerClass {
        fun bra() = bra
        fun reference() = this@KotlinNestedInnerClass  //This 表达式
    }

    //匿名内部类 @see 对象声明(object)
  
}
复制代码

若是对象是函数式 Java 接口(即具备单个抽象方法的 Java 接口)的实例, 你可使用带接口类型前缀的lambda表达式建立它:

val run  = Runnable {  }
复制代码

对象(object)

在Java 中, 匿名内部类随处可见。然而 Kotlin 用 object 关键字提供了对象声明以及对象表达式特性, 建立单例、匿名对象, 伴生对象(类内部的对象声明) so easy。

val point = object /*: Any()*/ { //默认继承 Any
    var x: Int = 0 //必须进行初始化
    var y: Int = 0
    override fun toString(): String {
        return "point[$x$y]"
    }
}
point.x = 100
point.y = 300
println(point)
val singleton = Singleton
val singleton1 = Singleton
println(singleton === singleton1)

//对象声明
object Singleton { //决不能声明局部做用域(函数中)
}
复制代码

NOTE: 如何区分对象声明和对象表达式, 顾名思义, 有名字的是对象声明(object Singleton), 没名字的是对象表达式(anonymous object)。

关于 object 使用细节,下面经过一个简单例子为你们演示:

class KotlinObject {

    private fun privateObject() = object { //返回: <anonymous object : Any>
        val name = "123"
    }

    fun publicObject() = object { // 返回Any 建议private
        val name = "ABC"
    }

    fun run() {
        println(privateObject().name)
        //println(publicObject().name) //错误:未能解析的引用“name”
        var visible = true
        call(object : CallBack {
            override fun call() {
                visible //对象表达式中的代码能够访问来自包含它的做用域的变量
                println("Anonymous#call@${this.hashCode()}")
            }

        })
        call (object : CallBack {
            override fun call() {
                visible //对象表达式中的代码能够访问来自包含它的做用域的变量
                println("Anonymous#call@${this.hashCode()}")
            }

        })
        call(OneCallBack)
        call(OneCallBack)

    }

    object OneCallBack : CallBack {
        //由于对象表达式不能绑定名字,这称为对象声明
        override fun call() {
            println("OneCallBack#call@${this.hashCode()}")
        }
    }

    fun call(call: CallBack) {
        call.call()
    }

    interface CallBack {
        fun call(): Unit
    }
}

fun main(args: Array<String>) {
	KotlinObject().run()
}

复制代码

私有函数时,返回object类型是匿名对象类型, 不然是 Any。与Java 不一样内部类也可访问非 final 变量。对象声明实则是单例。

伴生对象(companion object)

与 Java 或 C# 不一样,在 Kotlin 中类没有静态方法。在大多数状况下,它建议简单地使用包级函数。

类内部的对象声明能够用 companion 关键字标记:

open class World {

    //Companion 是companion object 默认名字可省略,仅且有一个伴生对象
    companion object Companion : Observer {
        @JvmField //@JvmField 标注这样的属性使其成为与属性自己具备相同可见性的静态字段。
        val time = System.nanoTime()

        const val VERSION = "1.1.4.2" //kotlin 常量(const 标注的(在类中以及在顶层的)属性), 在 Java 中会成为静态字段:

        override fun update(o: Observable?, arg: Any?) {
        }


        // @JvmStatic //打开注释编译报错,存在相同的函数声明, 这充分地证实了伴生对象的成员看起来像其余语言的静态成员,在运行时他们仍然是真实对象的实例成员
        fun sayHello() {
            println("sayHello@${this.hashCode()} ")
        }
    }

    fun sayHello() {
        println("sayHello@${this.hashCode()} ")
    }

}

fun main(args: Array<String>) {
    World.sayHello()
    World.Companion.sayHello()
    World().sayHello()
}
复制代码

Java 中调用

public class StaticTest {
    public static void main(String[] args) {
        System.out.println(World.Companion);
        System.out.println(World.VERSION);
        System.out.println(World.time);
    }
}
复制代码

NOTE:伴生对象实际是对象的实例成员, JVM 平台,若是使用 @JvmStatic 注解,你能够将伴生对象的成员生成为真正的静态方法和字段。更详细信息请参见Java 互操做性一节 。

对象表达式和对象声明之间有一个重要的语义差异:

  • 对象表达式是在使用他们的地方当即执行(及初始化)的
  • 对象声明是在第一次被访问到时延迟初始化的
  • 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配

数据类

咱们常常建立一些只保存数据的类。在这些类中,一些标准函数每每是从数据机械推导而来的。在 Kotlin 中,这叫作 数据类 并标记为 data

fun main(args: Array<String>) {
    val user1 = KotlinDataClass.User("小明", 19)
    val user2 = KotlinDataClass.User("小明", 19)
    println(user1 == user2)
    println(user1)
    val copyXiaoMing = user1.copy(age = 20)
    println(copyXiaoMing)
    println(user1.component1())
    val bb = KotlinDataClass.User("bb")
    println(bb)

    //数据类和解构声明
    val (name, age) = KotlinDataClass.User("Lisa", 18)
    println("$name, $age years of age")

    //标准数据类
    val anPair: Pair<Char, Char> = Pair('A', 'B')
    println("first = ${anPair.first}, second = ${anPair.second}")
    val (a,b,c) = Triple('A','B','C')
    println("($a, $b, $c)")
}
private class KotlinDataClass {

    open class Person

    //数据类自己是 final,必须有主构造器,至少一个参数
    data class User(val name: String, val age: Int = 0) : Person() {

        //编译器会根据主构造函数的参数生成如下函数,根据需求 override

// override fun equals(other: Any?): Boolean {
// return super.equals(other)
// }
//
// override fun hashCode(): Int {
// return super.hashCode()
// }
//
// override fun toString(): String {
// return super.toString()
// }

// Error: Conflicting overloads:
// fun component1(){
//
// }
    }

}
复制代码

编译器自动从主构造函数中声明的全部属性导出如下成员:

  • equals()/hashCode() 对,
  • toString() 格式是 "User(name=John, age=42)"
  • componentN() 函数 按声明顺序对应于全部属性,
  • copy() 函数, 复制一个对象仅改变某些属性。

为了确保生成的代码的一致性和有意义的行为,数据类必须知足如下要求:

  • 主构造函数须要至少有一个参数;
  • 主构造函数的全部参数须要标记为 valvar
  • 数据类不能是抽象、开放、密封或者内部的;
  • (在1.1以前)数据类只能实现接口。

自 1.1 起,数据类能够扩展其余类(示例请参见密封类)。

在 JVM 中,若是生成的类须要含有一个无参的构造函数,则全部的属性必须指定默认值。 (参见构造函数)。

密封类

密封类用来表示受限的类继承结构:当一个值为有限集中的类型、而不能有任何其余类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每一个枚举常量只存在一个实例,而密封类的一个子类能够有可包含状态的多个实例。

NOTE: sealed 不能修饰 interface ,abstract class(会报 warning,可是不会出现编译错误)

fun main(args: Array<String>) {
    val kotlinSealedClass = ChildrenKotlinSealedClass()
    println(eval(kotlinSealedClass))
}

sealed class KotlinSealedClass

class ChildrenKotlinSealedClass : KotlinSealedClass()

class GirlKotlinSealedClass : KotlinSealedClass()

private fun eval(k: KotlinSealedClass): String = when (k) {
    is ChildrenKotlinSealedClass -> "ChildrenKotlinSealedClass"
    is GirlKotlinSealedClass -> "GirlKotlinSealedClass"
    //再也不须要 else 分支 已经覆盖了全部的状况
}
复制代码

枚举类

枚举类的最基本的用法是实现类型安全的枚举, 每一个枚举常量都是一个对象, 需用逗号分开。示例以下

fun main(args: Array<String>) {
    for (it in KotlinEnumClass.Direction.values()) {
        println(it)
    }
    //必须与声明枚举类型名称一致, 不然抛出 IllegalArgumentException 异常。
    val north = KotlinEnumClass.Direction.valueOf("NORTH")
    println(north === KotlinEnumClass.Direction.NORTH)

    //枚举常量都具备在枚举类声明中获取其名称和位置的属性
    val (name, ordinal) = KotlinEnumClass.Direction.EAST
    println("$name $ordinal")


    KotlinEnumClass().printAllValues<KotlinEnumClass.ProtocolState>()
    println()
    KotlinEnumClass().printValue<KotlinEnumClass.ProtocolState>("WAITING")
}


private class KotlinEnumClass {
    //类型安全的枚举
    enum class Direction {
        NORTH, SOUTH, WEST, EAST;
    }

    //枚举都是枚举类的实例,能够初始化
    enum class Color(val rgb: Int) {
        RED(0xFF0000),
        GREEN(0x00FF00),
        BLUE(0x0000FF)
    }

    //枚举常量也能够声明本身的匿名类
    enum class ProtocolState {
        WAITING {
            override fun signal() = TALKING
        },

        TALKING {
            override fun signal() = WAITING
        };

        abstract fun signal(): ProtocolState
    }

    //列出定义的枚举常量
    inline fun <reified T : Enum<T>> printAllValues() {
        print(enumValues<T>().joinToString { it.name })
    }

    //经过名称获取枚举常量
    inline fun <reified T : Enum<T>> printValue(name: String) {
        print(enumValueOf<T>(name))
    }

}
复制代码

枚举常量还实现了 Comparable 接口, 其中天然顺序是它们在枚举类中定义的顺序。

NOTE: val (name, ordinal) = KotlinEnumClass.Direction.EAST 之因此能够编译经过, 由于我对枚举类进行解构声明

//学而致用
operator fun <E : Enum<E>> Enum<E>.component1() = this.name
operator fun <E : Enum<E>> Enum<E>.component2() = this.ordinal
复制代码

注解类

学习Java 的应该对注解不陌生,不了解能够先看看 Java的注解

注解声明

[访问修饰符 默认public] [非访问修饰符 默认只能为 final 不能显式修饰] annotation class 类名 
	[访问修饰符 只能为public] [主构造函数 constructor 关键字无关紧要] [val参数] 
复制代码
internal annotation class KotlinFileName(val name:String)
复制代码

容许的参数类型有:

  • 对应于 Java 原生类型的类型(Int、 Long等)以及字符串
  • KClass、枚举
  • 其余注解
  • 上面已列类型的数组

NOTE: 注解参数不能有可空类型,由于 JVM 不支持将 null 做为注解属性的值存储。若是注解用做另外一个注解的参数,则其名称不以 @ 字符为前缀, 且新的注解类访问权限不能比其中一个注解的参数的访问权限要大

internal annotation class FileScope 
		constructor(@ApplicationScope val file: KotlinFileName)
复制代码

注解的附加属性能够经过用元注解标注注解类来指定:

  • @Target 指定能够用该注解标注的元素的可能的类型(类、函数、属性、表达式等);
  • @Retention 指定该注解是否存储在编译后的 class 文件中,以及它在运行时可否经过反射可见 (默认都是 true);
  • @Repeatable 容许在单个元素上屡次使用相同的该注解;
  • @MustBeDocumented 指定该注解是公有 API 的一部分,而且应该包含在生成的 API 文档中显示的类或方法的签名中。
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation private class ApplicationScope
复制代码

Lambda 表达式

注解也能够用于 lambda 表达式。它们会被应用于生成 lambda 表达式体的 invoke() 方法上。

annotation class Anonymous
val run = @KotlinAnnotation.Anonymous { println("run") }

复制代码

Use-site Targets (使用处 目标)

当对属性或主构造函数参数进行标注时,从相应的 Kotlin 元素生成的 Java 元素会有多个,所以在生成的 Java 字节码中该注解有多个可能位置 。支持的使用处目标的完整列表为:

  • file
  • property(具备此目标的注解对 Java 不可见)
  • field
  • get(属性 getter)
  • set(属性 setter)
  • receiver(扩展函数或属性的接收者参数)
  • param(构造函数参数)
  • setparam(属性 setter 参数)
  • delegate(为委托属性存储其委托实例的字段)

可使用相同的语法来注释整个文件。要执行此操做,请将目标文件的注释放在文件的顶层,在包指令以前或在全部导入以前,若是文件位于默认包中:

@file:JvmName("KotlinAnnotationKt")

package demo
复制代码

若是要指定精确地指定应该如何生成该注解,请使用如下语法:

@处目标元素:[注解A 注解B ] ... //同一目标只有1个注解时方括号能够省略
复制代码

简单示例以下:

class User(@field:FieldScope val name: String, @get:[ApplicationScope FunScope] val age: Int)
复制代码

若是不指定使用处目标,则根据正在使用的注解的 @Target 注解来选择目标 。

Java 注解

Java 注解与 Kotlin 100% 兼容:

kotlin

//声明注解
annotation class Targets(vararg val value: KClass<*>)
annotation class TargetArrays(val value: Array<KClass<*>>)

@JavaAnnotation.Describe("see")
class See
@JavaAnnotation.SinceJava(name = "jdk", version = 1_8_0)
class JDK
@JavaAnnotation.Targets(Any::class, String::class)
class Targets
@JavaAnnotation.Targets(*arrayOf(Any::class, String::class))
class Targets2
fun printId(intId: JavaAnnotation.IntId) {
    println(intId.value)
}
@JavaAnnotation.IntId(Int.MAX_VALUE)
class Res
printId(Res::class.annotations[0] as JavaAnnotation.IntId)
复制代码

java

@KotlinAnnotation.ApplicationScope
public class JavaAnnotation {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("jsource.JavaAnnotation");
            Annotation annotation = clazz.getAnnotation(KotlinAnnotation.ApplicationScope.class);
            System.out.println(annotation);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    @KotlinAnnotation.Targets({String.class, Integer.class})
    class TargetClass {
    }

    @KotlinAnnotation.TargetArrays({String.class, Integer.class})
    class TargetArrays {
    }

    public @interface Describe {
        String value();
    }

    public @interface SinceJava {
        String name();

        int version();
    }

    public @interface Targets {
        Class[] value();
    }

    @Retention(RetentionPolicy.RUNTIME)
    public @interface IntId {
        int value() default -1;
    }

}
复制代码

泛型

与 Java 相似,Kotlin 中的泛型,以下示例:

fun main(args: Array<String>) {

    val emptyListString = List<String>()
    val listString = List("C", "D")
    assertEquals(0, emptyListString.size, "empty")
    printList(listString)
}

//泛型类
private class List<T>(vararg elements: T) : Iterable<T> {
    override fun iterator(): Iterator<T> {
        return elementsArray.iterator()
    }

    val elementsArray = mutableListOf(*elements)

    operator fun get(index: Int): T = elementsArray[index]

    val size: Int = elementsArray.size
}

// 泛型方法 printList
private fun <E> printList(inputList: List<E>) {
    for (element in inputList) {
        println("$element ")
    }
    println()
}
复制代码

与 Java 不一样,Kotlin 中的泛型没有通配符类型,它有:声明处型变(declaration-site variance)与类型投影(type projections)。

型变

Java 中的泛型是不型变的,这意味着 List<String>不是List<Object> 的子类型。

List<String> strs = new ArrayList<String>();
List<Object> objs =(List) strs;
objs.add(1);
String s = strs.get(0); // !!! ClassCastException:没法将整数转换为字符串
复制代码

PECS原则,在Java <? extends T>、<? super T> 通配符类型参数,前者只能读取, 不能写入,后者反之。便有一条规律,”Producer Extends, Consumer Super”:

  • Producer Extends – 若是你须要一个只读List,用它来produce T,那么使用? extends T
  • Consumer Super – 若是你须要一个只写List,用它来consume T,那么使用? super T
  • 若是须要同时读取以及写入,那么咱们就不能使用通配符了。

一样PECS原则适用于 Kotlin:

  • Producer Extends – 使得类型是协变的(covariant)
  • Consumer Super – 使得类型是逆变性(contravariance)

NOTE: *PECS 表明生产者-Extens,消费者-Super(Producer-Extends, Consumer-Super)。*一个生产者对象,只是保证类型安全

声明处型变

Java 中List<String> 不能直接赋值List<Object> ,在 Kotlin 中,提供 out 修饰符确保接口或类成员中返回out(生产),并从不被 in (消费)。

val stringList = listOf<String>()
val anyList: List<Any> = stringList
复制代码

kotlin List 接口声明:

public interface List<out E> : Collection<E> 
复制代码

in。它使得一个类型参数逆变:只能够被消费而不能够被生产。逆变类的一个很好的例子是 Comparable

abstract class Comparable<in T> {
    abstract fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
    x.compareTo(1.0) // 1.0 拥有类型 Double,它是 Number 的子类型
    // 所以,咱们能够将 x 赋给类型为 Comparable <Double> 的变量
    val y: Comparable<Double> = x // OK!
}
复制代码

类型参数 T 被声明为 out 时,虽然 **<Base> 能够安全地做为 **<Derived>的超类, 就只能出现输出-位置。

由于它在类型参数声明处提供,因此被称作声明处型变。 这与 Java 的使用处型变相反,其类型用途通配符使得类型协变。in 反之。

**NOTE:消费者 in, 生产者 out **

类型投影 (使用处型变)

将类型参数 T 声明为 out 很是方便,而且能避免使用处子类型化的麻烦,可是有些类实际上不能限制为只返回 T 好比 Array:

val ints: Array<out Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
//out 生产者 至关于Java ? extends T
fun copy(from: Array<out Any>, to: Array<Any>) {
    for (index in from.indices) {
        to[index] = from[index]
    }
}
copy(from = ints, to = any)
for (items in any) {
    println(items)
}
//out 消费者 至关于Java ? super T
fun fill(dest: Array<in Int>, value: Int) {
    for (index in dest.indices) {
        dest[index] = (dest[index] as? Int)!!.times(value)
    }
}
fill(any, 2)
for (items in any) {
    println(items)
}
复制代码

上面out in 类型投影, 也就是Java 的使用处型变 ? [extends][super] T

星投影

若类型参数一无所知,但仍然但愿以安全的方式使用它。 这里的安全方式是定义泛型类型的这种投影,该泛型类型的每一个具体实例化将是该投影的子类型。

Kotlin 为此提供了所谓的星投影语法:

val star: List<*> = listOf("C", "D", 1, 2)
val any1: Any? = star[0]
fun compareTo2(x: Comparable<*>) 
复制代码
  • 对于 Foo <out T>,其中 T 是一个具备上界 TUpper 的协变类型参数,Foo <*> 等价于 Foo <out TUpper>。 这意味着当 T 未知时,你能够安全地从 Foo <*> 读取 TUpper 的值。
  • 对于 Foo <in T>,其中 T 是一个逆变类型参数,Foo <*> 等价于 Foo <in Nothing>。 这意味着当 T 未知时,没有什么能够以安全的方式写入 Foo <*>
  • 对于 Foo <T>,其中 T 是一个具备上界 TUpper 的不型变类型参数,Foo<*> 对于读取值时等价于 Foo<out TUpper> 而对于写值时等价于 Foo<in Nothing>

若是泛型类型具备多个类型参数,则每一个类型参数均可以单独投影。 例如,若是类型被声明为 interface Function <in T, out U>,咱们能够想象如下星投影:

  • Function<*, String> 表示 Function<in Nothing, String>
  • Function<Int, *> 表示 Function<Int, out Any?>
  • Function<*, *> 表示 Function<in Nothing, out Any?>

注意:星投影很是像 Java 的原始类型,可是安全。

泛型约束

可以替换给定类型参数的全部可能类型的集合能够由泛型约束限制。

最多见的约束类型是与 Java 的 extends 关键字对应的 上界

fun <T : Number> add(t: T) {
    // ……
}

add(1)
add("") //not allow
复制代码

默认的上界(若是没有声明)是 Any?。在尖括号中只能指定一个上界。 若是同一类型参数须要多个上界,咱们须要一个单独的 where-子句:

fun <T> cloneWhenGreater(t: T)
        where T : Number,
              // T : String, 只指定一个class ,接口能够多个
              T : kotlin.Comparable<T>,
              T : Cloneable {
}
复制代码

扩展

Kotlin 同 C# 和 Gosu 相似,可以扩展一个类的新功能而无需继承该类或使用像装饰者这样的任何类型的设计模式。 这经过叫作 扩展 的特殊声明完成。Kotlin 支持 扩展函数扩展属性

扩展函数和属性

声明一个扩展函数和属性,咱们须要用一个 接收者类型 也就是被扩展的类型来做为他的前缀。

class KotlinExtension {
    //成员函数比扩展函数优先
    fun member() {
        println("call#member")
    }

    fun fileName(): String {
        return "KotlinExtension.class"
    }

    companion object
}

//扩展的对象类型 KotlinExtension
fun KotlinExtension.extensionFun() {
    println("this@${this} call#extensionFun") //
}

fun KotlinExtension.member() {
    println("call#extension") //
}

//接收者类型表达式中使用泛型 要在函数名前声明泛型参数
fun <E> List<E>.addAll(){
    //...
}

//扩展属性(Extension Property) 实际扩展get* 函数而已
val KotlinExtension.fileName
    get() = "KotlinExtension.kt"
复制代码

NOTE: this 关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象)

可空接收者

可空的接收者类型也能定义扩展,在对象变量上调用值为 null时,而且能够在函数体内检测 this == null

检测发生在扩展函数的内部。最好的例子,如 Library.kt中:

public fun Any?.toString(): String
复制代码

伴生对象的扩展

伴生对象的扩展和定义扩展函数和属性一致:

val KotlinExtension.Companion.anProperty: Int get() = 1
fun KotlinExtension.Companion.extensionFun() {
    println("call#Companion.extensionFun")
}
复制代码

扩展的做用域

大多数在顶层定义扩展,要使用所定义包以外的一个扩展,导包就可使用它。类内部也能够声明扩展(我认为这并没有卵用)在这样的扩展内部,该类的对象和接收者的对象成员,自由访问。扩展声明所在的类的实例称为 分发接收者,扩展方法调用所在的接收者类型的实例称为 扩展接收者

class KotlinInteriorExtension {
    fun start() {
        println("call#start")
    }

    fun KotlinExtension.stop(){
        start()
        member() //扩展声明为成员时 扩展函数优先
        this@KotlinInteriorExtension.member() //使用 限定this
    }

    fun member() {
        println("call#member")
    }
}
复制代码

扩展是静态解析的

谨记扩展不能真正的修改他们所扩展的类, 仅仅是能够经过该类型的变量用点表达式去调用这个新函数。

扩展函数是静态分发的,是由函数调用所在的表达式的类型来决定。

//扩展是静态解析的
open class LocalBookmark

class CloudBookmark : LocalBookmark()

open class LocalBookmarkManage {

    open fun LocalBookmark.sync() {
        println("syncToCloud")
    }

    open fun CloudBookmark.sync() {
        println("syncFromCloud")
    }

    fun syncLocal(localBookmark: LocalBookmark) {
        localBookmark.sync()
    }
}

class CloudBookmarkManage : LocalBookmarkManage() {

    override fun LocalBookmark.sync() {
        println("syncFromLocal")
    }

    override fun CloudBookmark.sync() {
        println("syncToLocal")
    }

}

//run
LocalBookmarkManage().syncLocal(localBookmark) //输出 syncToCloud
CloudBookmarkManage().syncLocal(cloudBookmark) //输出 syncFromLocal —— 分发接收者虚拟解析

LocalBookmarkManage().syncLocal(cloudBookmark)//输出 syncToCloud —— 扩展接收者静态解析
CloudBookmarkManage().syncLocal(localBookmark)//输出 syncFromLocal —— 分发接收者虚拟解析
复制代码

函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型必定是静态的。

委托

kotlin 支持委托类和属性, 使用关键字 by .

类委托

interface Printer {
    fun print()
}
class ColorPrinter : Printer {
    override fun print() {
        println("ColorPrinter#print")
    }
}
class BlackPrinter : Printer {
    override fun print() {
        println("BlackPrinter#print")
    }
}
class MultipurposePrinter(val printer: Printer) : Printer by printer {
    //可覆盖 , 不覆盖转发printer print 方法
    override fun print() {
        printer.print()
        println("override#print")
    }
}

fun main(args: Array<String>) {
    MultipurposePrinter(ColorPrinter()).print()
    MultipurposePrinter(BlackPrinter()).print()
}
复制代码

by xxa -子句表示xxa 将会在 类中内部存储。 而且编译器将生成转发给 xxa 的全部成员函数。

委托属性

kotlin 标准库实现以下常见的属性类型:

  • 延迟属性(lazy properties): 其值只在首次访问时计算,
  • 可观察属性(observable properties): 监听器会收到有关此属性变动的通知,
  • 把多个属性储存在一个映射(map)中,而不是每一个存在单独的字段中。
延迟属性 Lazy

lazy() 是接受一个 lambda 并返回一个 Lazy <T> 实例的函数,返回的实例能够做为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

默认状况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,而且全部线程会看到相同的值。若是初始化委托的同步锁不是必需的,这样多个线程能够同时执行,那么将 LazyThreadSafetyMode.PUBLICATION 做为参数传递给 lazy() 函数。 而若是你肯定初始化将老是发生在单个线程,那么你可使用 LazyThreadSafetyMode.NONE 模式, 它不会有任何线程安全的保证和相关的开销。

val lazyValue by lazy<String>(LazyThreadSafetyMode.SYNCHRONIZED) {
    println("computed!")
    "Hello" //同步锁的(synchronized)
}
println(lazyValue)
println(lazyValue)
复制代码

这个例子输出:

computed!
Hello
Hello
复制代码
可观察属性 Observable

Delegates.observable() 接受两个参数:初始值和修改时处理程序(handler)。 每当咱们给属性赋值时会调用该处理程序(在赋值执行)。它有三个参数:被赋值的属性、旧值和新值。

若是你想可以截获一个赋值并“否决”它,就使用 vetoable() 取代 observable()。 在属性被赋新值生效以前会调用传递给 vetoable 的处理程序。

var name by Delegates.observable("No Name") { prop, old, new ->
    println("被赋值的属性:${prop.name}, $old > $new")
}
name = "両儀式"
name = "式"
var skip by Delegates.vetoable("Null") { property, oldValue, newValue ->
    println("被赋值的属性:${property.name}, $oldValue > $newValue")
    false
}
skip = "Test"
println(skip)
复制代码

这个例子输出:

被赋值的属性:name,  No Name > 両儀式
被赋值的属性:name,  両儀式 > 式
被赋值的属性:skip,  Null > Test
Null
复制代码
把属性储存在映射中

Map 可做为委托来实现委托属性。

val languageMap = mapOf("language" to "kotlin")
val language by languageMap //变量名就是map的key 不然找不到该key Exception: NoSuchElementException
println(language)
复制代码

若要 var 属性只须要使用 MutableMap 。一样也适用于类

class User(map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
    fun make() {
        println("make")
    }
    fun enable() = true
}

val user = User(mapOf(
        "name" to "John Doe",
        "age" to 25
))
println("${user.name} ${user.age}")//ok
复制代码
局部委托属性

what? 看 lazy() 强大的初始化:

fun letMake(take: () -> User) {
    val lazyUser by lazy(take)
    //todo change true
    if (false && lazyUser.enable()) {
        lazyUser.make()
    }
}

//... 
letMake { ->
    println("init")
    User(mapOf("Twins" to 17))
}
复制代码
自定义委托

var 属性须要实现 getValue() setValue() 函数,val 只是须要getValue() 便可。两函数都须要用 operator 关键字来进行标记。

委托类还能够实现包含所需 operator 方法的 ReadOnlyPropertyReadWriteProperty 接口之一。 这俩接口是在 Kotlin 标准库中声明的:

class Delegate {
  operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    return "$thisRef, thank you for delegating '${property.name}' to me!"
  }

  operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    println("$value has been assigned to '${property.name} in $thisRef.'")
  }
}

class ReadDelegate : ReadOnlyProperty<Any?, String> {
  override operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    return "$thisRef, thank you for delegating '${property.name}' to me!"
  }
  //不须要 setValue
}


//test
var p: String  by Delegate()
p = "default"
p = "$p \nchange"
val read by ReadDelegate()
println(read)
复制代码
背后原理

在使用委托的时, 不难发现该属性是委托类型。好比: p is String ,输出false。

在每一个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。 例如,对于属性 prop,生成隐藏属性 prop$delegate,而访问器的代码只是简单地委托给这个附加属性:

class C {
    var prop: Type by MyDelegate()
}

// 这段是由编译器生成的相应代码:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
复制代码

Kotlin 编译器在参数中提供了关于 prop 的全部必要信息:第一个参数 this 引用到外部类 C 的实例而 this::propKProperty 类型的反射对象,该对象描述 prop 自身。

提供委托

kotlin 提供 provideDelegate 操做符,能够扩展建立属性实现所委托对象的逻辑。使用场景是在建立属性时(而不只在其 getter 或 setter 中)检查属性一致性。

class R {
  object id {
    val textView = 0x003
    val imageView = 0x004
  }

  object string {
    val hello_world = 0x001
  }

  object drawable {
    val icon_launch = 0x002
  }
}

open class View(val id: Int)
open class ImageView(id: Int) : View(id)
open class TextView(id: Int, var text: String = "") : View(id)


class MyActivity {

  val helloWorld by findResourceById<String>(R.string.hello_world)
  val textView by findResourceById<TextView>(R.id.textView)

  inline fun <reified T> findResourceById(id: Int): ResourceLoader<T> {
    return ResourceLoader<T>(id)
  }

  fun draw() {
    println(helloWorld)
    textView.text = "Hello"
    println(textView.text)
  }
}

class ResourceLoader<out T>(val id: Int) {
  operator fun provideDelegate( thisRef: MyActivity, prop: KProperty<*> ): ReadOnlyProperty<MyActivity, T> {
    return ResDelegate<T>(id)
  }

  private class ResDelegate<out V>(val id: Int) : ReadOnlyProperty<MyActivity, V> {
    val cacheKProperty = mutableMapOf<String, Any>()

    override fun getValue(thisRef: MyActivity, property: KProperty<*>): V {
      val last = cacheKProperty[property.name]
      if (last != null) {
        return last as V
      }
      val value = when (property.returnType.classifier) {
        String::class -> property.name as V
        View::class -> View(id) as V
        TextView::class -> TextView(id) as V
        ImageView::class -> ImageView(id) as V
        else -> throw NoSuchElementException()
      }
      cacheKProperty.put(property.name, value!!)
      return value
    }
  }
}
复制代码

提供委托, 并不复杂。经过一个函数去获取委托而已。provideDelegate 方法只影响辅助属性的建立,并不会影响为 getter 或 setter 生成的代码。

函数

函数用法

Kotlin 中的函数使用 fun 关键字声明

fun funName(参数)[: returnType(默认 Unit)] ...
复制代码

函数参数规则

  • 函数参数使用 Pascal 表示法定义,即 name: type , 参数用逗号隔开。
  • 每一个参数必须有显式类型, 参数还能够有默认值,当省略相应的参数时使用默认值, 以减小重载数量。
  • 覆盖带有默认参数值的方法时,默认参数值省略。
  • 若是一个默认参数在一个无默认值的参数以前,那么该默认值只能经过使用命名参数调用该函数来使用
  • 若是最后一个 lambda 表达式参数从括号外传给函数函数调用,那么容许默认参数不传值
fun invoke(method: String, invoke: Any = this) {
  println("call#method= $method $invoke")
}

fun invokeWithNameParameter(status: Int = 0, method: String, invoke: Any = this) {
  println("call#method= $method $invoke")
}

fun invokeWithLambda(status: Int = 0, method: String = "invokeWithLambda", invoke: Any = this, apply: () -> Unit) {
  println("call#method= $method $invoke")
}

abstract class Source {
  abstract fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size)
}

class FileSource : Source() {
  override fun read(b: Array<Byte>, off: Int, len: Int) {
    println("b.length = ${b.size} off = $off len = $len")
  }
}

//test
invoke("invoke")
invokeWithNameParameter(method = "invokeWithNameParameter")
invokeWithLambda(status = 1) { println("invokeWithLambda") }
invokeWithLambda { println("invokeWithLambda") }
FileSource().read(arrayOf('A'.toByte(), 'B'.toByte()))
复制代码

可变数量的参数(Varargs)

函数的参数(一般是最后一个)能够用 vararg 修饰符标记:

fun varargFun(method: String = "varargFun", vararg s: Int) {
  s.forEach { print(it) }
}

val b = intArrayOf(6, 8)
// vararg 参数 类型是基本类型,便是 *Array 类型 不然 Array<out T>
varargFun("1", 2, 4, *b, 10)
复制代码

**伸展(spread)**操做符(在数组前面加 *),能够数组元素添加到vararg 变量中去

返回 Unit 的函数

若是一个函数不返回任何有用的值,它的返回类型是 UnitUnit 是一种只有一个值——Unit 的类型。这个值不须要显式返回。Unit 就像Java 的 Void

fun printHello(name: String?): Unit {
  if (name != null)
  	println("Hello ${name}")
  else
  	println("Hi there!")
  // `return Unit` 或者 `return` 是可选的
}
复制代码

单表达式函数

当函数返回单个表达式时,能够省略花括号而且在 = 符号以后指定代码体便可。当返回值类型可由编译器推断时,显式声明返回类型是可选的, 但具备块代码体的函数必须始终显式指定返回类型。

fun double(x: Int) = x * 2
复制代码

中缀表示法

Kotlin支持数字运算的标准集,正是用了中缀表示法,当函数知足如下条件就能用 infix 关键字标注

  • 他们是成员函数或扩展函数
  • 他们只有一个参数
infix fun String.append(s: String): String {
    return "$this$s"
}
infix fun call(method: String) {
    println("call#method= $method")
}

val s = "infix" append " gc"
println(s)
this call ("append")
复制代码

函数做用域

在 Kotlin 中函数能够在文件顶层声明,这意味着你不须要像一些语言如 Java、C# 或 Scala 那样建立一个类来保存一个函数。此外除了顶层函数,Kotlin 中函数也能够声明在局部做用域、做为成员函数以及扩展函数。

  • 在类或对象内部定义的函数——成员函数
  • 一个函数在另外一个函数内部——局部函数
//成员函数
fun memberFun() {
  val visited = ""
  fun partialFun() {    //局部函数
    println(visited)
  }
  partialFun()
}
复制代码

泛型函数

函数能够有泛型参数,经过在函数名前使用尖括号指定。

fun <T> singletonList(item: T): List<T> {
  return listOf(item)
}
复制代码

高阶函数

高阶函数是将函数用做参数或返回值的函数。

//函数用做参数 () -> Unit 不带参数并 且返回 Unit 类型值的函数
fun post(runnable: () -> Unit) {
  println("post before")
  runnable()
  println("post after")
}

fun postDelay(delay: Int, runnable: () -> Unit) {
  println("postDelay before")
  runnable()
  println("postDelay after")
}
fun test() {
  post(this::sayHi) //函数引用
  post { println("post") }
  postDelay(1000) { println("postDelay") }
}
复制代码

() -> Unit 被称为函数类型 , :: 操做符可参见函数引用, 当一个函数接受另外一个函数做为最后一个参数,lambda 表达式参数能够在圆括号参数列表以外传递。 参见 callSuffix 的语法。

Lambda 表达式与匿名函数

一个 lambda 表达式或匿名函数是一个“函数字面值”,即一个未声明的函数, 做为表达式传递。

Lambda 表达式语法

lambda 表达式老是被大括号括着,完整语法形式的参数声明放在括号内,并有可选的类型标注, 函数体跟在一个 -> 符号以后。

println({}) //输出: () -> kotlin.Unit
println({ "String" })//输出: () -> kotlin.String
val string = { "String" }
println(string())//输出: String
复制代码

挖槽,上面的是什么鬼。没了解Lambda 表达式 的,固然会困惑不已。

fun explicitAnonymous(): () -> Int {
  return { -> 1 } //没参数不能有括号() -> 也可略
}
复制代码

这样一来就简单明了。{} 声明了个匿名函数,编译器做如下处理

local final fun <anonymous>(): Unit
复制代码

当一个空参数的匿名函数, 如 { "String" } ,编译器会将lambda 主体中的最后一个或多是单个)表达式会视为返回值。如果{ "String";1 } 则输出 () -> kotlin.Int

可选的类型标注,单表达式函数时,显式声明返回类型是可选的,匿名的参数类型也是可选的。非单表达式函数时,则变量名可选。

val sum = { x: Int, y: Int -> x + y }  //val sum: (Int, Int) → Int
val sum2: (Int, Int) -> Int = { x, y -> x + y } //val sum2: (Int, Int) → Int
fun sum3(sum: (Int, Int) -> Int) {
  println(sum(0,0))
}
fun sum4(sum: (a: Int, b: Int) -> Int) {
  println(sum)
}
sum3 { a, b -> 1 + 3 }
println(sum(1, 2))
复制代码

在 Kotlin 中Lambda表达式约定

  • 函数的最后一个参数是一个函数,而且你传递一个 lambda 表达式做为相应的参数,你能够在圆括号以外传递
  • lambda 是该调用的惟一参数,则调用中的圆括号能够彻底省略。
  • 函数字面值只有一个参数时, 那么它的声明能够省略(连同 ->),其名称是 it
  • 未使用的变量可用下划线取代其名称
  • lambda 隐式返回最后一个表达式的值,能够用限定的返回语法显式返回
fun <T> filter(predicate: (T) -> Boolean) {
  TODO()
}
filter<Int>() { it > 0 } //() 可略
filter<Int> { it > 0 }
filter<Int> { _ -> false }
filter<Int> {
  val shouldFilter = it > 0
  return@filter shouldFilter
}
复制代码
匿名函数

顾名思义,与常规函数相同不须要指定函数名

val sumAnonymous = fun(x: Int, y: Int) = x + y //返回类型能够自动推断
println(sumAnonymous(1, 3))
val sumAnonymous2 = fun(x: Int, y: Int): Int {
  return x + y
}
filter<Int>(fun(item) = item > 0) //推断出的参数类型能够省略. 只能在括号内传递
复制代码

匿名函数和lambda 是有区别的,匿名函数参数只能在括号内传递。 容许将函数留在圆括号外的简写语法仅适用于 lambda 表达式。Lambda表达式与匿名函数之间的另外一个区别是非局部返回的行为。一个不带标签的 return 语句老是在用 fun 关键字声明的函数中返回。这意味着 lambda 表达式中的 return 将从包含它的函数返回,而匿名函数中的 return将从匿名函数自身返回。

闭包

Lambda 表达式或者匿名函数(以及局部函数对象表达式) 能够访问其 闭包 ,即在外部做用域中声明的变量。 与 Java 不一样的是能够修改闭包中捕获的变量:

var aNumber = 0
run {
  aNumber += 1
}
val add = fun() {
  aNumber += 1
}
add()
println("aNumber: $aNumber")
复制代码
带接收者的函数字面值

Kotlin 提供了使用指定的 接收者对象 调用函数字面值的功能。 在函数字面值的函数体中,能够调用该接收者对象上的方法而无需任何额外的限定符。 这相似于扩展函数,它容许你在函数体内访问接收者对象的成员。 其用法的最重要的示例之一是类型安全的 Groovy-风格构建器

val sumR = fun Int.(other: Int): Int = this + other //val sumR: Int.(Int) → Int
println(1.sumR(2))
复制代码

内联函数

要知道使用高阶函数时,每个函数都是一个对象,且会捕获一个闭包。 因此带来一些运行时的效率损失,即那些在函数体内会访问到的变量。 内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销。

kotlin 支持 inline 修饰具备lambda参数的函数,以消除这类的开销。(仅支持顶层、成员函数,即不支持局函数)

inline fun <T> lockInline(lock: Lock, body: () -> T): T {
  lock.lock()
  try {
    return body()
  } finally {
    lock.unlock()
  }
}
复制代码

内联原理实际上是编译器拷贝代码副本(如:body () -> T),这可能致使生成的代码增长,但在循环中的“超多态(megamorphic)” 状况下,将在性能上有所提高。

不具备lambda参数的函数:

inline fun test() { //warn 内联函数最适用于具备lambda参数的函数
复制代码

NOTE:内联函数不支持局部函数

禁用内联

对于具备多个lambda参数的内联函数来讲,默认内联, 可用 noinline 修饰lambda参数,禁用内联。

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
  // ……
}
复制代码

noinline 仅在内联函数中可以使用

inline fun foo(noinline notInlined: () -> Unit) {
  // …… 仅一个参数又用 noinline 修饰, inline 将无效
}
复制代码

非局部返回

lambda 表达式内部不容许无标签的return , 但传给的函数是内联的,该 return 也能够内联,因此它是容许返回。称为非局部返回

fun <T> lock(body: () -> T): Unit {
}
inline fun <T> lockInline(body: () -> T): Unit {
}

lock {
    return  // 不容许不带标签的return. return@lock
}
lockInline{
  return
}
复制代码

循环中经常使用这种结构

fun hasZeros(ints: List<Int>): Boolean {
  ints.forEach {
    if (it == 0) return true // 从 hasZeros 返回
  }
  return false
}
复制代码

一些内联函数可能调用传给它们的不是直接来自函数体、而是来自另外一个执行上下文的 lambda 表达式参数,例如来自局部对象或嵌套函数。在这种状况下,该 lambda 表达式中也不容许非局部控制流。为了标识这种状况,该 lambda 表达式参数须要用 crossinline 修饰符标记:

inline fun post(crossinline body: () -> Unit) {
  Runnable { body() }
}
复制代码

具体化的类型参数

内联函数能有具体化的类型参数(Reified type parameters),用 reified 修饰符来限定类型参数

在前面泛型函数学习中,是不能有具体化参数,获取 class时带来不便。

fun <T> findType(t: T) {
  //由于T 不是静态已知的 Kotlin 类的引用,因此不能 T::class
  println((t as Any)::class)
}

//内联函数支持具体化的类型参数,不须要反射,正常的操做符如 !is 和 as 能正常使用
inline fun <reified T : Number> findReifiedType(t: T) {
  println(T::class)
  println(Int.MIN_VALUE is T)
}
复制代码

内联属性

inline 修饰符还能够修饰没有幕后字段的属性的访问器(有setter/getter),可单独标注。

val max inline get() = Int.MAX_VALUE
inline val max1 get() = Int.MAX_VALUE
inline val max2 inline get() = Int.MAX_VALUE //编译也ok 。。。

//Inline property cannot have backing field
var count = 0
var counter
inline get() = count //set/get 其中一个标注为inline, 都不能使用 backing field
inline set(value) {
  count = value
}
//
inline var doubleCounter
get() = count * 2 //set/get 其中一个标注为inline, 都不能使用 backing field
set(value) {
  count *= value
}
复制代码

公有 API 内联函数的限制

当一个内联函数是 publicprotected 而不是 privateinternal 声明的一部分时,就会认为它是一个模块级的公有 API。能够在其余模块中调用它,而且也能够在调用处内联这样的调用。

这带来了一些由模块作这样变动时致使的二进制兼容的风险——声明一个内联函数但调用它的模块在它修改后并无从新编译。

为了消除这种由公有 API 变动引入的不兼容的风险,公有 API 内联函数体内不容许使用非公有声明,即,不容许使用 privateinternal 声明以及其部件。

一个 internal 声明能够由 @PublishedApi 标注,这会容许它在公有 API 内联函数中使用。当一个 internal 内联函数标记有 @PublishedApi 时,也会像公有函数同样检查其函数体。

//公有 API 内联函数限制使用private 与 internal 声明以及其部件 (顶层声明)
inline fun publishApi(body: () -> Unit) {
    privateFun()
    internalFun()
}

@PublishedApi //检查其函数体加以限制
internal inline fun internalApi(body: () -> Unit) {
    privateFun()
    internalFun()
}

private fun privateFun(): Unit {

}

internal fun internalFun(): Unit {

}
复制代码

Kotlin与 Java 混合开发

Kotlin 中调用 Java

已映射类型

在Kotlin 中使用Java 代码,编译期间, Java 的原生类型映射到相应的 Kotlin 类型,运行时表示保持不变。

Kotlin 类型 Java 类型
kotlin.Byte byte
kotlin.Short short
kotlin.Int int
kotlin.Long long
kotlin.Char char
kotlin.Float float
kotlin.Double double
kotlin.Boolean boolean

Java 的装箱原始类型映射到可空的 Kotlin 类型:

Kotlin 类型 Java 类型
kotlin.Byte? java.lang.Byte
kotlin.Short? java.lang. Short
kotlin.Int? java.lang.Integer
kotlin.Long? java.lang.Long
kotlin.Char? java.lang.Character
kotlin.Float? java.lang.Float
kotlin.Double? java.lang.Double
kotlin.Boolean? java.lang. Boolean

一些非原生的内置类型也会做映射:

Kotlin 类型 Java 类型
kotlin.Any! java.lang.Object
kotlin.Cloneable! java.lang.Cloneable
kotlin.Comparable! java.lang.Comparable
kotlin.Enum! java.lang.Enum
kotlin.Annotation! java.lang.Annotation
kotlin.Deprecated! java.lang.Deprecated
kotlin.CharSequence! java.lang.CharSequence
kotlin.String! java.lang.String
kotlin.Number! java.lang.Number
kotlin.Throwable! java.lang.Throwable

NOTE: String!平台类型表示法

集合类型在 Kotlin 中能够是只读的或可变的,所以 Java 集合类型做以下映射: (下表中的全部 Kotlin 类型都驻留在 kotlin.collections包中):

Java 类型 Kotlin 只读类型 Kotlin 可变类型 加载的平台类型
Iterator Iterator MutableIterator (Mutable)Iterator!
Iterable Iterable MutableIterable (Mutable)Iterable!
Collection Collection MutableCollection (Mutable)Collection!
Set Set MutableSet (Mutable)Set!
List List MutableList (Mutable)List!
ListIterator ListIterator MutableListIterator (Mutable)ListIterator!
Map<K, V> Map<K, V> MutableMap<K, V> (Mutable)Map<K, V>!
Map.Entry<K, V> Map.Entry<K, V> MutableMap.MutableEntry<K,V> (Mutable)Map.(Mutable)Entry<K, V>!

请注意,用做类型参数的装箱原始类型映射到平台类型: 例如,List<java.lang.Integer> 在 Kotlin 中会成为 List<Int!>

Java 的数组按以下所述映射:

Java 类型 Kotlin 类型
int[] kotlin.IntArray!
String[] kotlin.Array<(out) String>!

空安全和平台类型

Java 中任何引用均可能是 null,而Kotlin 类型安全(空安全)。 Java 声明的类型在 Kotlin 中空检查跟Java相同(可空,非空)称为平台类型。平台类型可用助记符加在后面来表示,但切记不能在程序中这样写,kotlin 并无相应语法,IDE Doc 能够显示。

val nullAny = JavaDataType.nullObj //实际: val nullAny: Any!
val safeNullAny: Any? = JavaDataType.nullObj

println(safeNullAny?.hashCode())
println(nullAny?.hashCode()) //null check

val notNullAny: Any = JavaDataType.nullObj //赋值时 NPE
nullAny.hashCode() //使用时 NPE
复制代码

NOTE:只要不是Java基本类型,在Kotlin中都会映射为 T!

Getter 和 Setter

遵循 Java 约定的 getter 和 setter 的方法(名称以 get 开头的无参数方法和以 set 开头的单参数方法)在 Kotlin 中表示为属性。 Boolean 访问器方法(其中 getter 的名称以 is 开头而 setter 的名称以 set 开头)会表示为与 getter 方法具备相同名称的属性。 例如:

import java.util.Calendar

fun calendarDemo() {
    val calendar = Calendar.getInstance()
    if (calendar.firstDayOfWeek == Calendar.SUNDAY) {  // 调用 getFirstDayOfWeek()
        calendar.firstDayOfWeek = Calendar.MONDAY      // 调用ll setFirstDayOfWeek()
    }
    if (!calendar.isLenient) {                         // 调用 isLenient()
        calendar.isLenient = true                      // 调用 setLenient()
    }
}

复制代码

请注意,若是 Java 类只有一个 setter,它在 Kotlin 中不会做为属性可见,由于 Kotlin 目前不支持只写(set-only)属性。

返回 void 的方法

若是一个 Java 方法返回 void,那么从 Kotlin 调用时中返回 Unit。 万一有人使用其返回值,它将由 Kotlin 编译器在调用处赋值, 由于该值自己是预先知道的(是 Unit)。

将 Kotlin 中是关键字的 Java 标识符进行转义

一些 Kotlin 关键字在 Java 中是有效标识符:inobjectis 等等。 若是一个 Java 库使用了 Kotlin 关键字做为方法,你仍然能够经过反引号(`)字符转义它来调用该方法

foo.`is`(bar)
复制代码

Java 泛型

Kotlin 的泛型与 Java 有点不一样(参见泛型)。当将 Java 类型导入 Kotlin 时,咱们会执行一些转换:

  • Java 的通配符转换成类型投影
    • Foo<? extends Bar> 转换成 Foo<out Bar!>!
    • Foo<? super Bar> 转换成 Foo<in Bar!>!
  • Java的原始类型转换成星投影
    • List 转换成 List<*>!,即 List<out Any?>!

和 Java 同样,Kotlin 在运行时不保留泛型,即对象不携带传递到他们构造器中的那些类型参数的实际类型。 即 ArrayList<Integer>()ArrayList<Character>() 是不能区分的。 这使得执行 is-检测不可能照顾到泛型。 Kotlin 只容许 is-检测星投影的泛型类型:

if (a is List<Int>) // 错误:没法检查它是否真的是一个 Int 列表
// but
if (a is List<*>) // OK:不保证列表的内容
复制代码

Java 集合

java 集合类型映射的平台类型都是可变的,用法如kotlin 同样,并且 操做符约定一样有效

Java 数组

与 Java 不一样,Kotlin 中的数组是不型变的。这意味着 Kotlin 不容许咱们把一个 Array<String> 赋值给一个 Array<Any>, 从而避免了可能的运行时故障。Kotlin 也禁止咱们把一个子类的数组当作超类的数组传递给 Kotlin 的方法, 可是对于 Java 方法,这是容许的(经过 Array<(out) String>! 这种形式的平台类型)。

Java 平台上,数组会使用原生数据类型以免装箱/拆箱操做的开销。 因为 Kotlin 隐藏了这些实现细节,所以须要一个变通方法来与 Java 代码进行交互。 对于每种原生类型的数组都有一个特化的类(IntArrayDoubleArrayCharArray 等等)来处理这种状况。 它们与 Array 类无关,而且会编译成 Java 原生类型数组以得到最佳性能。

假设有一个接受 int 数组索引的 Java 方法:

public class JavaArrayExample {

    public void removeIndices(int[] indices) {
        // 在此编码……
    }
}
复制代码

在 Kotlin 中你能够这样传递一个原生类型的数组:

val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndices(array)  // 将 int[] 传给方法
复制代码

当编译为 JVM 字节代码时,编译器会优化对数组的访问,这样就不会引入任何开销:

val array = arrayOf(1, 2, 3, 4)
array[x] = array[x] * 2 // 不会实际生成对 get() 和 set() 的调用
for (x in array) { // 不会建立迭代器
    print(x)
}
复制代码

即便当咱们使用索引定位时,也不会引入任何开销

for (i in array.indices) {// 不会建立迭代器
    array[i] += 2
}
复制代码

最后,in-检测也没有额外开销

if (i in array.indices) { // 同 (i >= 0 && i < array.size)
    print(array[i])
}
复制代码

Java 可变参数

Java 类有时声明一个具备可变数量参数(varargs)的方法来使用索引。

public class JavaArrayExample {

    public void removeIndicesVarArg(int... indices) {
        // 在此编码……
    }
}
复制代码

在这种状况下,你须要使用展开运算符 * 来传递 IntArray

val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndicesVarArg(*array)
复制代码

目前没法传递 null 给一个声明为可变参数的方法。

操做符

因为 Java 没法标记用于运算符语法的方法,Kotlin 容许具备正确名称和签名的任何 Java 方法做为运算符重载和其余约定(invoke() 等)使用。 不容许使用中缀调用语法调用 Java 方法。

受检异常

在 Kotlin 中,全部异常都是非受检的,这意味着编译器不会强迫你捕获其中的任何一个。 所以,当你调用一个声明受检异常的 Java 方法时,Kotlin 不会强迫你作任何事情:

对象方法

当 Java 类型导入到 Kotlin 中时,类型 java.lang.Object 的全部引用都成了 Any。 而由于 Any 不是平台指定的,它只声明了 toString()hashCode()equals() 做为其成员, 因此为了能用到 java.lang.Object 的其余成员,Kotlin 要用到扩展函数

wait()/notify()

Effective Java 第 69 条善意地建议优先使用并发工具(concurrency utilities)而不是 wait()notify()。 所以,类型 Any 的引用不提供这两个方法。 若是你真的须要调用它们的话,你能够将其转换为 java.lang.Object

(foo as java.lang.Object).wait()
复制代码
getClass()

要取得对象的 Java 类,请在类引用上使用 java 扩展属性。

val fooClass = foo::class.java
复制代码

上面的代码使用了自 Kotlin 1.1 起支持的绑定的类引用。你也可使用 javaClass 扩展属性。

val fooClass = foo.javaClass
复制代码
clone()

要覆盖 clone(),须要继承 kotlin.Cloneable

class Example : Cloneable {
    override fun clone(): Any { …… }
}
复制代码

不要忘记 Effective Java 的第 11 条: 谨慎地改写clone

finalize()

要覆盖 finalize(),全部你须要作的就是简单地声明它,而不须要 override 关键字:

class C {
    protected fun finalize() {
        // 终止化逻辑
    }
}
复制代码

根据 Java 的规则,finalize() 不能是 private 的。

访问静态成员

Java 类的静态成员会造成该类的“伴生对象”。咱们没法将这样的“伴生对象”做为值来传递, 但能够显式访问其成员,例如:

val character = Character
if (Character.isLetter('A')) {
  // ……
}
复制代码

Java 反射

Java 反射适用于 Kotlin 类,反之亦然。如上所述,你可使用 instance::class.java,ClassName::class.java 或者 instance.javaClass 经过 java.lang.Class 来进入 Java 反射。

其余支持的状况包括为一个 Kotlin 属性获取一个 Java 的 getter/setter 方法或者幕后字段、为一个 Java 字段获取一个 KProperty、为一个 KFunction 获取一个 Java 方法或者构造函数,反之亦然。

SAM 转换

就像 Java 8 同样,Kotlin 支持 SAM 转换。这意味着 Kotlin 函数字面值能够被自动的转换成只有一个非默认方法的 Java 接口的实现,只要这个方法的参数类型可以与这个 Kotlin 函数的参数类型相匹配。

你能够这样建立 SAM 接口的实例:

val runnable = Runnable { println("This runs in a runnable") }

复制代码

……以及在方法调用中:

val executor = ThreadPoolExecutor()
// Java 签名:void execute(Runnable command)
executor.execute { println("This runs in a thread pool") }

复制代码

若是 Java 类有多个接受函数式接口的方法,那么能够经过使用将 lambda 表达式转换为特定的 SAM 类型的适配器函数来选择须要调用的方法。这些适配器函数也会按需由编译器生成。

executor.execute(Runnable { println("This runs in a thread pool") })

复制代码

请注意,SAM 转换只适用于接口,而不适用于抽象类,即便这些抽象类也只有一个抽象方法。

还要注意,此功能只适用于 Java 互操做;由于 Kotlin 具备合适的函数类型,因此不须要将函数自动转换为 Kotlin 接口的实现,所以不受支持。

Java中调用 Kotlin

Java 能够轻松调用 Kotlin 代码。

属性

Kotlin 属性会编译成如下 Java 元素:

  • 一个 getter 方法,名称经过加前缀 get 算出;
  • 一个 setter 方法,名称经过加前缀 set 算出(只适用于 var 属性);
  • 一个私有字段,与属性名称相同(仅适用于具备幕后字段的属性)。

例如,var firstName: String 编译成如下 Java 声明:

private String firstName;

public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
}

复制代码

若是属性的名称以 is 开头,则使用不一样的名称映射规则:getter 的名称与属性名称相同,而且 setter 的名称是经过将 is 替换为 set 得到。 例如,对于属性 isOpen,其 getter 会称作 isOpen(),而其 setter 会称作 setOpen()。 这一规则适用于任何类型的属性,并不只限于 Boolean

包级函数

org.foo.bar 包内的 example.kt 文件中声明的全部的函数和属性,包括扩展函数, 都编译成一个名为 org.foo.bar.ExampleKt 的 Java 类的静态方法。

// example.kt
package demo

class Foo

fun bar() {
}


复制代码
// Java
new demo.Foo();
demo.ExampleKt.bar();

复制代码

可使用 @JvmName 注解修改生成的 Java 类的类名:

@file:JvmName("DemoUtils")

package demo

class Foo

fun bar() {
}


复制代码
// Java
new demo.Foo();
demo.DemoUtils.bar();

复制代码

若是多个文件中生成了相同的 Java 类名(包名相同而且类名相同或者有相同的 @JvmName 注解)一般是错误的。然而,编译器可以生成一个单一的 Java 外观类,它具备指定的名称且包含来自全部文件中具备该名称的全部声明。 要启用生成这样的外观,请在全部相关文件中使用 @JvmMultifileClass 注解。

// oldutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass

package demo

fun foo() {
}

复制代码
// newutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass

package demo

fun bar() {
}

复制代码
// Java
demo.Utils.foo();
demo.Utils.bar();

复制代码

实例字段

若是须要在 Java 中将 Kotlin 属性做为字段暴露,那就须要使用 @JvmField 注解对其标注。 该字段将具备与底层属性相同的可见性。若是一个属性有幕后字段(backing field)、非私有、没有 open /override 或者 const修饰符而且不是被委托的属性,那么你能够用 @JvmField 注解该属性。

class C(id: String) {
    @JvmField val ID = id
}

复制代码
// Java
class JavaClient {
    public String getID(C c) {
        return c.ID;
    }
}

复制代码

延迟初始化的属性(在Java中)也会暴露为字段。 该字段的可见性与 lateinit 属性的 setter 相同。

静态字段

在命名对象或伴生对象中声明的 Kotlin 属性会在该命名对象或包含伴生对象的类中具备静态幕后字段。

一般这些字段是私有的,但能够经过如下方式之一暴露出来:

  • @JvmField 注解;
  • lateinit 修饰符;
  • const 修饰符。

使用 @JvmField 标注这样的属性使其成为与属性自己具备相同可见性的静态字段。

class Key(val value: Int) {
    companion object {
        @JvmField
        val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value }
    }
}

复制代码
// Java
Key.COMPARATOR.compare(key1, key2);
// Key 类中的 public static final 字段

复制代码

在命名对象或者伴生对象中的一个延迟初始化的属性具备与属性 setter 相同可见性的静态幕后字段。

object Singleton {
    lateinit var provider: Provider
}

复制代码
// Java
Singleton.provider = new Provider();
// 在 Singleton 类中的 public static 非-final 字段

复制代码

const 标注的(在类中以及在顶层的)属性在 Java 中会成为静态字段:

// 文件 example.kt

object Obj {
    const val CONST = 1
}

class C {
    companion object {
        const val VERSION = 9
    }
}

const val MAX = 239

复制代码

在 Java 中:

int c = Obj.CONST;
int d = ExampleKt.MAX;
int v = C.VERSION;

复制代码

静态方法

如上所述,Kotlin 将包级函数表示为静态方法。 Kotlin 还能够为命名对象或伴生对象中定义的函数生成静态方法,若是你将这些函数标注为 @JvmStatic 的话。 若是你使用该注解,编译器既会在相应对象的类中生成静态方法,也会在对象自身中生成实例方法。 例如:

class C {
    companion object {
        @JvmStatic fun foo() {}
        fun bar() {}
    }
}

复制代码

如今,foo() 在 Java 中是静态的,而 bar() 不是:

C.foo(); // 没问题
C.bar(); // 错误:不是一个静态方法
C.Companion.foo(); // 保留实例方法
C.Companion.bar(); // 惟一的工做方式

复制代码

对于命名对象也一样:

object Obj {
    @JvmStatic fun foo() {}
    fun bar() {}
}

复制代码

在 Java 中:

Obj.foo(); // 没问题
Obj.bar(); // 错误
Obj.INSTANCE.bar(); // 没问题,经过单例实例调用
Obj.INSTANCE.foo(); // 也没问题

复制代码

@JvmStatic 注解也能够应用于对象或伴生对象的属性, 使其 getter 和 setter 方法在该对象或包含该伴生对象的类中是静态成员。

可见性

Kotlin 的可见性如下列方式映射到 Java:

  • private 成员编译成 private 成员;
  • private 的顶层声明编译成包级局部声明;
  • protected 保持 protected(注意 Java 容许访问同一个包中其余类的受保护成员, 而 Kotlin 不能,因此 Java 类会访问更普遍的代码);
  • internal 声明会成为 Java 中的 publicinternal 类的成员会经过名字修饰,使其更难以在 Java 中意外使用到,而且根据 Kotlin 规则使其容许重载相同签名的成员而互不可见;
  • public 保持 public

KClass

有时你须要调用有 KClass 类型参数的 Kotlin 方法。 由于没有从 ClassKClass 的自动转换,因此你必须经过调用 Class<T>.kotlin 扩展属性的等价形式来手动进行转换:

kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)

复制代码

用 @JvmName 解决签名冲突

有时咱们想让一个 Kotlin 中的命名函数在字节码中有另一个 JVM 名称。 最突出的例子是因为类型擦除引起的:

fun List<String>.filterValid(): List<String>
fun List<Int>.filterValid(): List<Int>

复制代码

这两个函数不能同时定义,由于它们的 JVM 签名是同样的:filterValid(Ljava/util/List;)Ljava/util/List;。 若是咱们真的但愿它们在 Kotlin 中用相同名称,咱们须要用 @JvmName 去标注其中的一个(或两个),并指定不一样的名称做为参数:

fun List<String>.filterValid(): List<String>

@JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int>

复制代码

在 Kotlin 中它们能够用相同的名称 filterValid 来访问,而在 Java 中,它们分别是 filterValidfilterValidInt

一样的技巧也适用于属性 x 和函数 getX() 共存:

val x: Int
    @JvmName("getX_prop")
    get() = 15

fun getX() = 10

复制代码

生成重载

一般,若是你写一个有默认参数值的 Kotlin 函数,在 Java 中只会有一个全部参数都存在的完整参数签名的方法可见,若是但愿向 Java 调用者暴露多个重载,可使用 @JvmOverloads 注解。

该注解也适用于构造函数、静态方法等。它不能用于抽象方法,包括在接口中定义的方法。

class Foo @JvmOverloads constructor(x: Int, y: Double = 0.0) {
    @JvmOverloads fun f(a: String, b: Int = 0, c: String = "abc") {
        ……
    }
}

复制代码

对于每个有默认值的参数,都会生成一个额外的重载,这个重载会把这个参数和它右边的全部参数都移除掉。在上例中,会生成如下代码 :

// 构造函数:
Foo(int x, double y)
Foo(int x)

// 方法
void f(String a, int b, String c) { }
void f(String a, int b) { }
void f(String a) { }

复制代码

请注意,如次构造函数中所述,若是一个类的全部构造函数参数都有默认值,那么会为其生成一个公有的无参构造函数。这就算没有 @JvmOverloads 注解也有效。

受检异常

如上所述,Kotlin 没有受检异常。 因此,一般 Kotlin 函数的 Java 签名不会声明抛出异常。 因而若是咱们有一个这样的 Kotlin 函数:

// example.kt
package demo

fun foo() {
    throw IOException()
}

复制代码

而后咱们想要在 Java 中调用它并捕捉这个异常:

// Java
try {
  demo.Example.foo();
}
catch (IOException e) { // 错误:foo() 未在 throws 列表中声明 IOException
  // ……
}

复制代码

由于 foo() 没有声明 IOException,咱们从 Java 编译器获得了一个报错消息。 为了解决这个问题,要在 Kotlin 中使用 @Throws 注解。

@Throws(IOException::class)
fun foo() {
    throw IOException()
}

复制代码

空安全性

当从 Java 中调用 Kotlin 函数时,没人阻止咱们将 null 做为非空参数传递。 这就是为何 Kotlin 给全部指望非空参数的公有函数生成运行时检测。 这样咱们就能在 Java 代码里当即获得 NullPointerException

型变的泛型

当 Kotlin 的类使用了声明处型变,有两种选择能够从 Java 代码中看到它们的用法。让咱们假设咱们有如下类和两个使用它的函数:

class Box<out T>(val value: T)

interface Base
class Derived : Base

fun boxDerived(value: Derived): Box<Derived> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value

复制代码

一种看似理所固然地将这俩函数转换成 Java 代码的方式可能会是:

Box<Derived> boxDerived(Derived value) { …… }
Base unboxBase(Box<Base> box) { …… }

复制代码

问题是,在 Kotlin 中咱们能够这样写 unboxBase(boxDerived("s")),可是在 Java 中是行不通的,由于在 Java 中类 Box 在其泛型参数 T 上是不型变的,因而 Box<Derived> 并非 Box<Base> 的子类。 要使其在 Java 中工做,咱们按如下这样定义 unboxBase

Base unboxBase(Box<? extends Base> box) { …… }  

复制代码

这里咱们使用 Java 的通配符类型? extends Base)来经过使用处型变来模拟声明处型变,由于在 Java 中只能这样。

当它做为参数出现时,为了让 Kotlin 的 API 在 Java 中工做,对于协变定义的 Box 咱们生成 Box<Super> 做为 Box<? extends Super> (或者对于逆变定义的 Foo 生成 Foo<? super Bar>)。当它是一个返回值时, 咱们不生成通配符,由于不然 Java 客户端将必须处理它们(而且它违反经常使用 Java 编码风格)。所以,咱们的示例中的对应函数实际上翻译以下:

// 做为返回类型——没有通配符
Box<Derived> boxDerived(Derived value) { …… }
 
// 做为参数——有通配符
Base unboxBase(Box<? extends Base> box) { …… }

复制代码

注意:当参数类型是 final 时,生成通配符一般没有意义,因此不管在什么地方 Box<String> 始终转换为 Box<String>

若是咱们在默认不生成通配符的地方须要通配符,咱们可使用 @JvmWildcard 注解:

fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
// 将被转换成
// Box<? extends Derived> boxDerived(Derived value) { …… }

复制代码

另外一方面,若是咱们根本不须要默认的通配符转换,咱们可使用@JvmSuppressWildcards

fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
// 会翻译成
// Base unboxBase(Box<Base> box) { …… }

复制代码

注意:@JvmSuppressWildcards 不只可用于单个类型参数,还可用于整个声明(如函数或类),从而抑制其中的全部通配符。

Nothing 类型翻译

类型 Nothing 是特殊的,由于它在 Java 中没有天然的对应。确实,每一个 Java 引用类型,包括java.lang.Void 均可以接受 null 值,可是 Nothing 不行。所以,这种类型不能在 Java 世界中准确表示。这就是为何在使用 Nothing 参数的地方 Kotlin 生成一个原始类型:

fun emptyList(): List<Nothing> = listOf()
// 会翻译成
// List emptyList() { …… }
复制代码

在 Kotlin 中使用 JNI

要声明一个在本地(C 或 C++)代码中实现的函数,你须要使用 external 修饰符来标记它:

external fun foo(x: Int): Double
复制代码

其他的过程与 Java 中的工做方式彻底相同。

Kotlin 高级编程

领域特定语言 DSL

域特定语言(DSL)的基本思想是针对特定类型的问题的计算机语言,而不是面向任何类型的软件问题的通用语言。

类型安全的构建器

构建器(builder)的概念在 Groovy 社区中很是热门。 构建器容许以半声明(semi-declarative)的方式定义数据。构建器很适合用来生成 XML布局 UI 组件描述 3D 场景以及其余更多功能……

Kotlin 容许检查类型的构建器,比 Groovy 自身的动态类型实现更具吸引力。

HTML DSL kotlin 官方示例:

fun main(args: Array<String>) {
    val result =
            html {
                head {
                    title { +"XML encoding with Kotlin" }
                }
                body {
                    h1 { +"XML encoding with Kotlin" }
                    p { +"this format can be used as an alternative markup to XML" }

                    // an element with attributes and text content
                    a(href = "http://jetbrains.com/kotlin") { +"Kotlin" }

                    // mixed content
                    p {
                        +"This is some"
                        b { +"mixed" }
                        +"text. For more see the"
                        a(href = "http://jetbrains.com/kotlin") { +"Kotlin" }
                        +"project"
                    }
                    p { +"some text" }

                    // content generated from command-line arguments
                    p {
                        +"Command line arguments were:"
                        ul {
                            for (arg in args)
                                li { +arg }
                        }
                    }
                }
            }
    println(result)
}

interface Element {
    fun render(builder: StringBuilder, indent: String)
}

class TextElement(val text: String) : Element {
    override fun render(builder: StringBuilder, indent: String) {
        builder.append("$indent$text\n")
    }
}

@DslMarker
annotation class HtmlTagMarker

@HtmlTagMarker
abstract class Tag(val name: String) : Element {
    val children = arrayListOf<Element>()
    val attributes = hashMapOf<String, String>()

    protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T {
        tag.init()
        children.add(tag)
        return tag
    }

    override fun render(builder: StringBuilder, indent: String) {
        builder.append("$indent<$name${renderAttributes()}>\n")
        for (c in children) {
            c.render(builder, indent + " ")
        }
        builder.append("$indent</$name>\n")
    }

    private fun renderAttributes(): String? {
        val builder = StringBuilder()
        for (a in attributes.keys) {
            builder.append(" $a=\"${attributes[a]}\"")
        }
        return builder.toString()
    }


    override fun toString(): String {
        val builder = StringBuilder()
        render(builder, "")
        return builder.toString()
    }
}

abstract class TagWithText(name: String) : Tag(name) {
    operator fun String.unaryPlus() {
        children.add(TextElement(this))
    }
}

class HTML() : TagWithText("html") {
    fun head(init: Head.() -> Unit) = initTag(Head(), init)

    fun body(init: Body.() -> Unit) = initTag(Body(), init)
}

class Head() : TagWithText("head") {
    fun title(init: Title.() -> Unit) = initTag(Title(), init)
}

class Title() : TagWithText("title")

abstract class BodyTag(name: String) : TagWithText(name) {
    fun b(init: B.() -> Unit) = initTag(B(), init)
    fun p(init: P.() -> Unit) = initTag(P(), init)
    fun h1(init: H1.() -> Unit) = initTag(H1(), init)
    fun ul(init: UL.() -> Unit) = initTag(UL(), init)
    fun a(href: String, init: A.() -> Unit) {
        val a = initTag(A(), init)
        a.href = href
    }
}

class Body() : BodyTag("body")
class UL() : BodyTag("ul") {
    fun li(init: LI.() -> Unit) = initTag(LI(), init)
}

class B() : BodyTag("b")
class LI() : BodyTag("li")
class P() : BodyTag("p")
class H1() : BodyTag("h1")

class A() : BodyTag("a") {
    public var href: String
        get() = attributes["href"]!!
        set(value) {
            attributes["href"] = value
        }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()
    html.init()
    return html
}

复制代码

上面实现 HTML 标签,其实是调用一个 lambda函数,用一个标签接收者的函数类型zuo做为参数,使在函数内部调用该实例的成员。

几个厉害的 DSL 项目

  • Anko 用于 Android 的,用于描述 UI 。
  • Gensokyo 用于 Swing 的,用于描述 UI
  • KotlinTest Kotlin测试框架基于优秀的Scalatest

协程 Coroutine

在 Kotlin 1.1 中协程是实验性的。另外kotlin 为了减小程序体积,根据须要使用协程,你要加入kotlinx-coroutines-core 库.

一些 API 启动长时间运行的操做(例如网络 IO、文件 IO、CPU 或 GPU 密集型任务等),并要求调用者阻塞直到它们完成。协程提供了一种避免阻塞线程并用更廉价、更可控的操做替代线程阻塞的方法:协程挂起

协程经过将复杂性放入库来简化异步编程。程序的逻辑能够在协程中顺序地表达,而底层库会为咱们解决其异步性。该库能够将用户代码的相关部分包装为回调、订阅相关事件、在不一样线程(甚至不一样机器!)上调度执行,而代码则保持如同顺序执行同样简单。

许多在其余语言中可用的异步机制可使用 Kotlin 协程实现为库。这包括源于 C# 和 ECMAScript 的 async/await、源于 Go 的 管道select 以及源于 C# 和 Python 生成器/yield。关于提供这些结构的库请参见其下文描述。

阻塞 vs 挂起

基本上,协程计算能够被挂起而无需阻塞线程。线程阻塞的代价一般是昂贵的,尤为在高负载时,由于只有相对少许线程实际可用,所以阻塞其中一个会致使一些重要的任务被延迟。

另外一方面,协程挂起几乎是无代价的。不须要上下文切换或者 OS 的任何其余干预。最重要的是,挂起能够在很大程度上由用户库控制:做为库的做者,咱们能够决定挂起时发生什么并根据需求优化/记日志/截获。

另外一个区别是,协程不能在随机的指令中挂起,而只能在所谓的挂起点挂起,这会调用特别标记的函数。

挂起函数

当咱们调用标记有特殊修饰符 suspend 的函数时,会发生挂起:

suspend fun doSomething(foo: Foo): Bar {
    ……
}
复制代码

这样的函数称为挂起函数,由于调用它们可能挂起协程(若是相关调用的结果已经可用,库能够决定继续进行而不挂起)。挂起函数可以以与普通函数相同的方式获取参数和返回值,但它们只能从协程和其余挂起函数中调用。事实上,要启动协程,必须至少有一个挂起函数,它一般是匿名的(即它是一个挂起 lambda 表达式)。让咱们来看一个例子,一个简化的 async() 函数(源自 kotlinx.coroutines 库):

fun <T> async(block: suspend () -> T)
复制代码

这里的 async() 是一个普通函数(不是挂起函数),可是它的 block 参数具备一个带 suspend 修饰符的函数类型: suspend () -> T。因此,当咱们将一个 lambda 表达式传给 async() 时,它会是挂起 lambda 表达式,因而咱们能够从中调用挂起函数:

async {
    doSomething(foo)
    ……
}
复制代码

继续该类比,await() 能够是一个挂起函数(所以也能够在一个 async {} 块中调用),该函数挂起一个协程,直到一些计算完成并返回其结果:

async {
    ……
    val result = computation.await()
    ……
}
复制代码

更多关于 async/await 函数实际在 kotlinx.coroutines 中如何工做的信息能够在这里找到。

请注意,挂起函数 await()doSomething() 不能在像 main() 这样的普通函数中调用:

fun main(args: Array<String>) {
    doSomething() // 错误:挂起函数从非协程上下文调用
}
复制代码

还要注意的是,挂起函数能够是虚拟的,当覆盖它们时,必须指定 suspend 修饰符:

interface Base {
    suspend fun foo()
}

class Derived: Base {
    override suspend fun foo() { …… }
}
复制代码

@RestrictsSuspension 注解

扩展函数(和 lambda 表达式)也能够标记为 suspend,就像普通的同样。这容许建立 DSL 及其余用户可扩展的 API。在某些状况下,库做者须要阻止用户添加新方式来挂起协程。

为了实现这一点,可使用 @RestrictsSuspension 注解。当接收者类/接口 R 用它标注时,全部挂起扩展都须要委托给 R 的成员或其它委托给它的扩展。因为扩展不能无限相互委托(程序不会终止),这保证全部挂起都经过调用 R 的成员发生,库的做者就能够彻底控制了。

这在少数状况是须要的,当每次挂起在库中以特殊方式处理时。例如,当经过 buildSequence() 函数实现下文所述的生成器时,咱们须要确保在协程中的任何挂起调用最终调用 yield()yieldAll() 而不是任何其余函数。这就是为何 SequenceBuilder@RestrictsSuspension 注解:

@RestrictsSuspension
public abstract class SequenceBuilder<in T> {
    ……
}
复制代码

参见其 Github 上 的源代码。

协程的内部机制

咱们不是在这里给出一个关于协程如何工做的完整解释,然而粗略地认识发生了什么是至关重要的。

协程彻底经过编译技术实现(不须要来自 VM 或 OS 端的支持),挂起经过代码来生效。基本上,每一个挂起函数(优化可能适用,但咱们不在这里讨论)都转换为状态机,其中的状态对应于挂起调用。恰好在挂起前,下一状态与相关局部变量等一块儿存储在编译器生成的类的字段中。在恢复该协程时,恢复局部变量而且状态机从恰好挂起以后的状态进行。

挂起的协程能够做为保持其挂起状态与局部变量的对象来存储和传递。这种对象的类型是 Continuation,而这里描述的整个代码转换对应于经典的延续性传递风格(Continuation-passing style)。所以,挂起函数有一个 Continuation 类型的额外参数做为高级选项。

关于协程工做原理的更多细节能够在这个设计文档中找到。在其余语言(如 C# 或者 ECMAScript 2016)中的 async/await 的相似描述与此相关,虽然它们实现的语言功能可能不像 Kotlin 协程这样通用。

协程的实验性状态

协程的设计是实验性的,这意味着它可能在即将发布的版本中更改。当在 Kotlin 1.1 中编译协程时,默认状况下会报一个警告:“协程”功能是实验性的。要移出该警告,你须要指定 opt-in 标志

因为其实验性状态,标准库中协程相关的 API 放在 kotlin.coroutines.experimental 包下。当设计完成而且实验性状态解除时,最终的 API 会移动到 kotlin.coroutines,而且实验包会被保留(可能在一个单独的构件中)以实现向后兼容。

重要注意事项:咱们建议库做者遵循相同惯例:给暴露基于协程 API 的包添加“experimental”后缀(如 com.example.experimental),以使你的库保持二进制兼容。当最终 API 发布时,请按照下列步骤操做:

  • 将全部 API 复制到 com.example(没有 experimental 后缀),
  • 保持实验包的向后兼容性。

这将最小化你的用户的迁移问题。

标准 API

协程有三个主要组成部分:

  • 语言支持(即如上所述的挂起功能),
  • Kotlin 标准库中的底层核心 API,
  • 能够直接在用户代码中使用的高级 API。

底层 API:kotlin.coroutines

底层 API 相对较小,而且除了建立更高级的库以外,不该该使用它。 它由两个主要包组成:

关于这些 API 用法的更多细节能够在这里找到。

kotlin.coroutines 中的生成器 API

kotlin.coroutines.experimental 中仅有的“应用程序级”函数是

这些包含在 kotlin-stdlib 中由于他们与序列相关。这些函数(咱们能够仅限于这里的 buildSequence())实现了 生成器 ,即提供一种廉价构建惰性序列的方法:

val fibonacciSeq = buildSequence {
    var a = 0
    var b = 1

    yield(1)

    while (true) {
        yield(a + b)

        val tmp = a + b
        a = b
        b = tmp
    }
}
复制代码

这经过建立一个协程生成一个惰性的、潜在无限的斐波那契数列,该协程经过调用 yield() 函数来产生连续的斐波纳契数。当在这样的序列的迭代器上迭代每一步,都会执行生成下一个数的协程的另外一部分。所以,咱们能够从该序列中取出任何有限的数字列表,例如 fibonacciSeq.take(8).toList() 结果是 [1, 1, 2, 3, 5, 8, 13, 21]。协程足够廉价使这很实用。

为了演示这样一个序列的真正惰性,让咱们在调用 buildSequence() 内部输出一些调试信息:

val lazySeq = buildSequence {
    print("START ")
    for (i in 1..5) {
        yield(i)
        print("STEP ")
    }
    print("END")
}

// 输出序列的前三个元素
lazySeq.take(3).forEach { print("$it ") }
复制代码

运行上面的代码看,是否是咱们输出前三个元素的数字与生成循环的 STEP 有交叉。这意味着计算确实是惰性的。要输出 1,咱们只执行到第一个 yield(i),而且过程当中会输出 START。而后,输出 2,咱们须要继续下一个 yield(i),并会输出 STEP3 也同样。永远不会输出再下一个 STEP(以及END),由于咱们再也没有请求序列的后续元素。

为了一次产生值的集合(或序列),可使用 yieldAll() 函数:

val lazySeq = buildSequence {
    yield(0)
    yieldAll(1..10) 
}

lazySeq.forEach { print("$it ") }
复制代码

buildIterator() 的工做方式相似于 buildSequence(),但返回一个惰性迭代器。

能够经过为 SequenceBuilder 类写挂起扩展(带有上文描述的 @RestrictsSuspension 注解)来为 buildSequence() 添加自定义生产逻辑(custom yielding logic):

suspend fun SequenceBuilder<Int>.yieldIfOdd(x: Int) {
    if (x % 2 != 0) yield(x)
}

val lazySeq = buildSequence {
    for (i in 1..10) yieldIfOdd(i)
}
复制代码

其余高级 API:kotlinx.coroutines

只有与协程相关的核心 API 能够从 Kotlin 标准库得到。这主要包括全部基于协程的库可能使用的核心原语和接口。

大多数基于协程的应用程序级API都做为单独的库发布:kotlinx.coroutines。这个库覆盖了

  • 使用kotlinx-coroutines-core的平台无关异步编程

    • 此模块包括支持 select 和其余便利原语的相似 Go 的管道
    • 这个库的综合指南在这里
  • 基于 JDK 8 中的 CompletableFuture 的 API:kotlinx-coroutines-jdk8

  • 基于 JDK 7 及更高版本 API 的非阻塞 IO(NIO):kotlinx-coroutines-nio

  • 支持 Swing (kotlinx-coroutines-swing) 和 JavaFx (kotlinx-coroutines-javafx)

  • 支持 RxJava:kotlinx-coroutines-rx

这些库既做为使通用任务易用的便利的 API,也做为如何构建基于协程的库的端到端示例。

更多

集合

与大多数语言不一样,Kotlin 区分可变集合和不可变集合(lists、sets、maps 等)。精确控制何时集合可编辑有助于消除 bug 和设计良好的 API。

预先了解一个可变集合的只读 视图 和一个真正的不可变集合之间的区别是很重要的。它们都容易建立,但类型系统不能表达它们的差异,因此由你来跟踪(是否相关)。

Kotlin 的 List<out T> 类型是一个提供只读操做如 sizeget等的接口。和 Java 相似,它继承自 Collection<T> 进而继承自 Iterable<T>。改变 list 的方法是由 MutableList<T> 加入的。这一模式一样适用于 Set<out T>/MutableSet<T>Map<K, out V>/MutableMap<K, V>

咱们能够看下 list 及 set 类型的基本用法:

val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
val readOnlyView: List<Int> = numbers
println(numbers)        // 输出 "[1, 2, 3]"
numbers.add(4)
println(readOnlyView)   // 输出 "[1, 2, 3, 4]"
readOnlyView.clear()    // -> 不能编译

val strings = hashSetOf("a", "b", "c", "c")
assert(strings.size == 3)
复制代码

Kotlin 没有专门的语法结构建立 list 或 set。 要用标准库的方法,如 listOf()mutableListOf()setOf()mutableSetOf()。 在非性能关键代码中建立 map 能够用一个简单的惯用法来完成:mapOf(a to b, c to d)

注意上面的 readOnlyView 变量(译者注:与对应可变集合变量 numbers)指向相同的底层 list 并会随之改变。 若是一个 list 只存在只读引用,咱们能够考虑该集合彻底不可变。建立一个这样的集合的一个简单方式以下:

val items = listOf(1, 2, 3)
复制代码

目前 listOf 方法是使用 array list 实现的,可是将来能够利用它们知道本身不能变的事实,返回更节约内存的彻底不可变的集合类型。

注意这些类型是协变的。这意味着,你能够把一个 List<Rectangle> 赋值给 List<Shape> 假定 Rectangle 继承自 Shape。对于可变集合类型这是不容许的,由于这将致使运行时故障。

有时你想给调用者返回一个集合在某个特定时间的一个快照, 一个保证不会变的:

class Controller {
    private val _items = mutableListOf<String>()
    val items: List<String> get() = _items.toList()
}
复制代码

这个 toList 扩展方法只是复制列表项,所以返回的 list 保证永远不会改变。

List 和 set 有不少有用的扩展方法值得熟悉:

val items = listOf(1, 2, 3, 4)
items.first() == 1
items.last() == 4
items.filter { it % 2 == 0 }   // 返回 [2, 4]

val rwList = mutableListOf(1, 2, 3)
rwList.requireNoNulls()        // 返回 [1, 2, 3]
if (rwList.none { it > 6 }) println("No items above 6")  // 输出“No items above 6”
val item = rwList.firstOrNull()
复制代码

…… 以及全部你所指望的实用工具,例如 sort、zip、fold、reduce 等等。

Map 遵循一样模式。它们能够容易地实例化和访问,像这样:

val readWriteMap = hashMapOf("foo" to 1, "bar" to 2)
println(readWriteMap["foo"])  // 输出“1”
val snapshot: Map<String, Int> = HashMap(readWriteMap)
复制代码

类型安全和智能转换

空安全

Kotlin 的类型系统

  • 可空类型
  • 非空类型

它消除了不少编程语言(如: Java)来自于代码空引用,而致使的 NullPointerException 或简称 NPE

NOTE: Kotlin 发生 NPE 缘由可能以下:

  • 显式调用 throw NullPointerException()
  • 使用了下文描述的 !! 操做符
  • 外部 Java 代码致使的

在上面 变量 中, Kotlin 默认声明变量时是非空类型的,要使该变量接收 null 值,需使用 操做符 , 例子以下

var aNullNothing = null
var bNullUnable: Int = null //不能为空
var cNullUnable = 1 //不能为空
var cNullable: Int? = null //能为空
var dNullable: Any? = 1 //能为空 

fun fun0(): Unit {
    aNullNothing = 1 //Nothing error
    cNullUnable = null
    cNullable = 1
    dNullable = null //能够 null
}
复制代码

当声明可空类型变量时,它是不安全的,访问方法或属性时须要做处理:

  • 在条件中检查 null ,但仅适用于 val 且不可覆盖(即不能用 open 修饰)或者 get 的不可变的变量。
  • 安全的调用 ?. , 若为null 则跳过,不然接着调用
  • !! 操做符 ,会返回一个非空的值,不然抛出一个 NPE 异常

条件中检查 nul 例子

open class TestCheckNull {
    val cReadNullable: Int? = 1
    val cGetReadNullable: Int? get() = 1
    open val cOverrideReadNullable: Int? = 1

    fun fun0(): Unit {
        if (cReadNullable != null) {
            cReadNullable.dec() //tips replace safe access expression
        }
        if (cGetReadNullable != null) {
            cGetReadNullable.dec()
        }
        if (cOverrideReadNullable != null) {
            cOverrideReadNullable.dec()
        }
    }
}
复制代码

安全调用和!! 操做符对比

cNullUnable.dec() //保证不会致使 NPE
val hc = dNullable?.hashCode() //dNullable == null return null, hc is null
val dec = cNullable?.dec() // cNullable !=null return cNullable.dec(),dec is "0"
cNullable!!.dec() // cNullable !=null execute dec()
dNullable!!.toString() // dNullable == null throws NPE
var aNotNullObject = cNullable!!
复制代码

类型检测和安全的类型转换

  • is !is 运算符检测一个表达式是否某类型的一个实例。在许多状况下,不须要在 Kotlin 中使用显式转换操做符,由于编译器跟踪不可变值的 is-检查,并在须要时自动插入(安全的)转换:
val obj: Any = ""
if (obj is String) {
    print(obj.length)
}
if (obj !is String) { // 与 !(obj is String) 相同
    print("Not a String")
} else if (obj is String) {
    print(obj.length)
} else {
    print(obj.length)
}
when(obj){
    is String -> obj.length
}
复制代码
  • as as? 运算符能把对象转换为目标类型,常规类型转换可能会致使 ClassCastException。使用安全的类型转换符 as?,若是尝试转换不成功则返回 null
val father = Father()
val son = Son()
println(father is Son)
println(son is Father)

val fatherSon: Father = Son()
println(fatherSon is Son)
println(fatherSon is Father)

val sonFatherSon: Son = fatherSon as Son
println(sonFatherSon != null)

val newFather: Son? = father as? Son
val newFather1 = father as? Son  //newFather1 start define val newFather : Son?
val newFather2 = father as Son // newFather1 start define val newFather : Son
println(newFather == null)
复制代码

NOTE: Kotlin 类型检测十分智能, 想了解请更多参考 Type Checks and Casts

操做符重载

Kotlin 容许咱们为本身的类型提供预约义的一组操做符的实现。这些操做符具备固定的符号表示(如 +*)和固定的优先级。为实现这样的操做符,咱们为相应的类型(即二元操做符左侧的类型和一元操做符的参数类型)提供了一个固定名字的成员函数扩展函数。 重载操做符的函数须要用 operator 修饰符标记。

另外,咱们描述为不一样操做符规范操做符重载的约定。

一元前缀操做符

表达式 翻译为
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()

这个表是说,当编译器处理例如表达式 +a 时,它执行如下步骤:

  • 肯定 a 的类型,令其为 T
  • 为接收者 T 查找一个带有 operator 修饰符的无参函数 unaryPlus(),即成员函数或扩展函数。
  • 若是函数不存在或不明确,则致使编译错误。
  • 若是函数存在且其返回类型为 R,那就表达式 +a 具备类型 R

注意 这些操做以及全部其余操做都针对基本类型作了优化,不会为它们引入函数调用的开销。

如下是如何重载一元减运算符的示例:

data class Point(val x: Int, val y: Int)

operator fun Point.unaryMinus() = Point(-x, -y)

val point = Point(10, 20)
println(-point)  // 输出“(-10, -20)”
复制代码

递增与递减

表达式 翻译为
a++ a.inc() + 见下文
a-- a.dec() + 见下文

inc()dec() 函数必须返回一个值,它用于赋值给使用++-- 操做的变量。它们不该该改变在其上调用 inc()dec() 的对象。

编译器执行如下步骤来解析后缀形式的操做符,例如 a++

  • 肯定 a 的类型,令其为 T
  • 查找一个适用于类型为 T 的接收者的、带有 operator 修饰符的无参数函数 inc()
  • 检查函数的返回类型是 T 的子类型。

计算表达式的步骤是:

  • a 的初始值存储到临时存储 a0 中,
  • a.inc() 结果赋值给 a
  • a0 做为表达式的结果返回。

对于 a--,步骤是彻底相似的。

对于前缀形式 ++a--a 以相同方式解析,其步骤是:

  • a.inc() 结果赋值给 a
  • a 的新值做为表达式结果返回。

二元操做算术运算符

表达式 翻译为
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b)、 a.mod(b) (已弃用)
a..b a.rangeTo(b)

对于此表中的操做,编译器只是解析成翻译为列中的表达式。

请注意,自 Kotlin 1.1 起支持 rem 运算符。Kotlin 1.0 使用 mod 运算符,它在 Kotlin 1.1 中被弃用。

示例

下面是一个从给定值起始的 Counter 类的示例,它可使用重载的 + 运算符来增长计数。

data class Counter(val dayIndex: Int) {
    operator fun plus(increment: Int): Counter {
        return Counter(dayIndex + increment)
    }
}
复制代码

In操做符

表达式 翻译为
a in b b.contains(a)
a !in b !b.contains(a)

对于 in!in,过程是相同的,可是参数的顺序是相反的。

索引访问操做符

表达式 翻译为
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, ……, i_n] a.get(i_1, ……, i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, ……, i_n] = b a.set(i_1, ……, i_n, b)

方括号转换为调用带有适当数量参数的 getset

调用操做符

表达式 翻译为
a() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, ……, i_n) a.invoke(i_1, ……, i_n)

圆括号转换为调用带有适当数量参数的 invoke

广义赋值

表达式 翻译为
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b), a.modAssign(b)(已弃用)

对于赋值操做,例如 a += b,编译器执行如下步骤:

  • 若是右列的函数可用
    • 若是相应的二元函数(即 plusAssign() 对应于 plus())也可用,那么报告错误(模糊)。
    • 确保其返回类型是 Unit,不然报告错误。
    • 生成 a.plusAssign(b) 的代码
  • 不然试着生成 a = a + b 的代码(这里包含类型检查:a + b 的类型必须是 a 的子类型)。

注意:赋值在 Kotlin 中不是表达式。

相等与不等操做符

表达式 翻译为
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))

注意===!==(同一性检查)不可重载,所以不存在对他们的约定

这个 == 操做符有些特殊:它被翻译成一个复杂的表达式,用于筛选 null 值。 null == null 老是 true,对于非空的 xx == null 老是 false 而不会调用 x.equals()

比较操做符

表达式 翻译为
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

全部的比较都转换为对 compareTo 的调用,这个函数须要返回 Int

属性委托操做符

provideDelegategetValue 以及 setValue 操做符函数已在委托属性中描述。

命名函数的中缀调用

咱们能够经过中缀函数的调用 来模拟自定义中缀操做符。

类型相等性

Kotlin 中有两种类型的相等性:

  • 引用相等(两个引用指向同一对象)

  • 结构相等(用 equals() 检查)

引用相等

引用相等由 ===(以及其否认形式 !==)操做判断。a === b 当且仅当 a 和 b 指向同一个对象时求值为 true。

结构相等

结构相等由 ==(以及其否认形式 !=)操做判断。按照惯例,像 a == b 这样的表达式会翻译成

a?.equals(b) ?: (b === null)

也就是说若是 a 不是 null 则调用 equals(Any?) 函数,不然(即 anull)检查 b 是否与 null 引用相等。

请注意,当与 null 显式比较时彻底不必优化你的代码:a == null 会被自动转换为 a=== null。同类型才有可比性。

This表达式

为了表示当前的 接收者 咱们使用 this 表达式:

若是 this 没有限定符,它指的是最内层的包含它的做用域。要引用其余做用域中的 this,请使用 标签限定符

fun main(args: Array<String>) {
    val kotlinThisExpression = KotlinThisExpression()
    println(kotlinThisExpression.leftReference() === kotlinThisExpression)
    kotlinThisExpression.InnerKotlinThisExpression().test()
}

private class KotlinThisExpression {
    val thisClassObject get() = this

    inner class KotlinThisExpression {
        //val thisClassObject get() = this@KotlinThisExpression //不明确label
        val thisClassObject get() = this //内部类名相同,不能用限定的 this

    }


    inner class InnerKotlinThisExpression { // 隐式标签 @InnerKotlinThisExpression
        fun InnerKotlinThisExpression.fuck() { // 隐式标签 @fuck
            val a = this@KotlinThisExpression // KotlinThisExpression 的 this
            val b = this@InnerKotlinThisExpression // InnerKotlinThisExpression 的 this

            val c = this // fuck() 的接收者,一个 InnerKotlinThisExpression
            val d = this@fuck // fuck() 的接收者,一个 InnerKotlinThisExpression

            val label = label@ fun String.() {
                println(this)// label 的接收者
            }

            "label".label()
            val lambda = { ->
                // fuck() 的接收者,由于它包含的 lambda 表达式
                // 没有任何接收者
                println(this)
            }

            lambda()
        }

        fun test() {
            fuck()
        }
    }
}

private fun KotlinThisExpression.leftReference() = this.thisClassObject //this 表示在点左侧传递的 接收者 参数。

复制代码

Nothing 类型

若是用 null 来初始化一个要推断类型的值,而又没有其余信息可用于肯定更具体的类型时,编译器会推断出 Nothing? 类型:

val nothingInt/*: Nothing?*/ = null
val list:List<Nothing?> = listOf(null)
复制代码

另外Kotlin 中 throw 是表达式, 表达式的类型是特殊类型 Nothing。 该类型没有值,而是用于标记永远不能达到的代码位置。Nothing 能够用来标记一个永远不会返回的函数, 也能够做为 Elvis 表达式的一部分:

val nothingInt/*: Nothing?*/ = null
val list: List<Nothing?> = listOf(null)
fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}
fail("fail")
//做为 Elvis 表达式的一部分 
var exception = null ?: throw RuntimeException("throw")
复制代码

解构声明

解构声明是建立多个变量与对象componentN 函数对应起来。例如在上面的数据类中

val (name, age) = KotlinDataClass.User("Lisa", 18)
复制代码

NOTE: componentN() 函数须要用 operator 关键字标记,以容许在解构声明中使用它们。它能够用for-循环、

map-映射, 以及 lambda 表达式中。

fun main(args: Array<String>) {
    val (name, age) = KotlinDeconstruction.Person("jack", 32)
    println("$name $age")

    val request = KotlinDeconstruction.request()
    val (rs, code) = request
    println("result = $rs , code = $code")
    //下划线用于未使用的变量
    val (_, responseCode) = request
    println(responseCode)
    println(request.component1())
    println(request.component2())

    //解构声明和Map
    val map = mutableMapOf<String, String>()
    for (it in 1..10) {
        map.put(it.toString(), it.toString())
    }
    for ((k, v) in map) {
        println("map key = $k, value = $v")
    }
    map.mapValues { entry -> println("key = ${entry.key}, value = ${entry.value}!") }
    map.mapValues { (key, value) -> println("key = $key, value = $value!") }
}


private class KotlinDeconstruction {
    class Person(val name: String, val age: Int) {
        operator fun component1(): Any = name
        operator fun component2(): Any = age
    }

    data class Response(val result: String, val code: Int)

    companion object {
        fun request(): Response {
            //request network
            return Response("ok", 200)
        }
    }
}
复制代码

解构声明的好处, 如request 函数时要返回两个东西时,用它爽爆了。由于编译器始终会建立多个变量接收,效率并不比以前用对象的高。但实际上并不须要解析一个对象里的大量变量,不然经过对象 .属性获取值。

val (name, age) = person //编译器会生成以下两句代码
val name = person.component1()
val age = person.component2()
复制代码

相等性

Kotlin 中有两种类型的相等性:

  • 引用相等(两个引用指向同一对象)
  • 结构相等(用 equals() 检查)

引用相等

引用相等由 ===(以及其否认形式 !==)操做判断。a === b 当且仅当 ab 指向同一个对象时求值为 true。

结构相等

结构相等由 ==(以及其否认形式 !=)操做判断。按照惯例,像 a == b 这样的表达式会翻译成

a?.equals(b) ?: (b === null)
复制代码

也就是说若是 a 不是 null 则调用 equals(Any?) 函数,不然(即 anull)检查 b 是否与 null 引用相等。

请注意,当与 null 显式比较时彻底不必优化你的代码:a == null 会被自动转换为 a=== null

浮点数相等性

当相等性检测的两个操做数都是静态已知的(可空或非空的)FloatDouble 类型时,该检测遵循 IEEE 754 浮点数运算标准。

不然会使用不符合该标准的结构相等性检测,这会致使 NaN 等于其自身,而 -0.0 不等于 0.0

异常

异常类

Kotlin 中全部异常类都是 Throwable 类的子孙类。 每一个异常都有消息、堆栈回溯信息和可选的缘由。

使用 throw-表达式来抛出异常:

throw MyException("Hi There!")
复制代码

使用 try-表达式来捕获异常:

try {
    // 一些代码
}
catch (e: SomeException) {
    // 处理程序
}
finally {
    // 可选的 finally 块
}
复制代码

能够有零到多个 catch 块。finally 块能够省略。 可是 catchfinally 块至少应该存在一个。

Try 是一个表达式

try 是一个表达式,即它能够有一个返回值:

val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
复制代码

try-表达式的返回值是 try 块中的最后一个表达式或者是(全部)catch 块中的最后一个表达式。 finally 块中的内容不会影响表达式的结果。

受检的异常

Kotlin 没有受检的异常。这其中有不少缘由,但咱们会提供一个简单的例子。

如下是 JDK 中 StringBuilder 类实现的一个示例接口:

Appendable append(CharSequence csq) throws IOException;
复制代码

这个签名是什么意思? 它是说,每次我追加一个字符串到一些东西(一个 StringBuilder、某种日志、一个控制台等)上时我就必须捕获那些 IOException。 为何?由于它可能正在执行 IO 操做(Writer 也实现了 Appendable)…… 因此它致使这种代码随处可见的出现:

try {
    log.append(message)
}
catch (IOException e) {
    // 必需要安全
}
复制代码

这并很差,参见《Effective Java》 第 65 条:不要忽略异常

Bruce Eckel 在《Java 是否须要受检的异常?》(Does Java need Checked Exceptions?) 中指出:

经过一些小程序测试得出的结论是异常规范会同时提升开发者的生产力和代码质量,可是大型软件项目的经验代表一个不一样的结论——生产力下降、代码质量不多或没有提升。

其余相关引证:

注意:throw 表达式的类型是特殊类型 Nothing。参见Nothing类型

反射

反射是这样的一组语言和库功能,它容许在运行时自省你的程序的结构。 Kotlin 让语言中的函数和属性作为一等公民、并对其自省(即在运行时获悉一个名称或者一个属性或函数的类型)与简单地使用函数式或响应式风格紧密相关。

在 Java 平台上,使用反射功能所需的运行时组件做为单独的 JAR 文件(kotlin-reflect.jar)分发。这样作是为了减小不使用反射功能的应用程序所需的运行时库的大小。若是你须要使用反射,请确保该 .jar文件添加到项目的 classpath 中。

类引用

最基本的反射功能是获取 Kotlin 类的运行时引用。要获取对静态已知的 Kotlin 类的引用,可使用 类字面值 语法:

val c = MyClass::class
复制代码

该引用是 KClass 类型的值。

请注意,Kotlin 类引用与 Java 类引用不一样。要得到 Java 类引用, 请在 KClass 实例上使用 .java 属性。

绑定的类引用(自 1.1 起)

经过使用对象做为接收者,能够用相同的 ::class 语法获取指定对象的类的引用:

val widget: Widget = ……
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }
复制代码

你能够获取对象的精确类的引用,例如 GoodWidgetBadWidget,尽管接收者表达式的类型是 Widget

函数引用

当咱们有一个命名函数声明以下:

fun isOdd(x: Int) = x % 2 != 0
复制代码

咱们能够很容易地直接调用它(isOdd(5)),可是咱们也能够把它做为一个值传递。例如传给另外一个函数。 为此,咱们使用 :: 操做符:

val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // 输出 [1, 3]
复制代码

这里 ::isOdd 是函数类型 (Int) -> Boolean 的一个值。

当上下文中已知函数指望的类型时,:: 能够用于重载函数。 例如:

fun isOdd(x: Int) = x % 2 != 0
fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove"

val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // 引用到 isOdd(x: Int)
复制代码

或者,你能够经过将方法引用存储在具备显式指定类型的变量中来提供必要的上下文:

val predicate: (String) -> Boolean = ::isOdd   // 引用到 isOdd(x: String)
复制代码

若是咱们须要使用类的成员函数或扩展函数,它须要是限定的。 例如 String::toCharArray 为类型 String提供了一个扩展函数:String.() -> CharArray

示例:函数组合

考虑如下函数:

fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
    return { x -> f(g(x)) }
}
复制代码

它返回一个传给它的两个函数的组合:compose(f, g) = f(g(*))。 如今,你能够将其应用于可调用引用:

fun length(s: String) = s.length

val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")

println(strings.filter(oddLength)) // 输出 "[a, abc]"
复制代码

属性引用

要把属性做为 Kotlin中 的一等对象来访问,咱们也可使用 :: 运算符:

var x = 1

fun main(args: Array<String>) {
    println(::x.get()) // 输出 "1"
    ::x.set(2)
    println(x)         // 输出 "2"
}
复制代码

表达式 ::x 求值为 KProperty<Int> 类型的属性对象,它容许咱们使用 get() 读取它的值,或者使用 name 属性来获取属性名。更多信息请参见关于 KProperty 类的文档

对于可变属性,例如 var y = 1::y 返回 KMutableProperty 类型的一个值, 该类型有一个 set() 方法。

属性引用能够用在不须要参数的函数处:

val strs = listOf("a", "bc", "def")
println(strs.map(String::length)) // 输出 [1, 2, 3]
复制代码

要访问属于类的成员的属性,咱们这样限定它:

class A(val p: Int)

fun main(args: Array<String>) {
    val prop = A::p
    println(prop.get(A(1))) // 输出 "1"
}
复制代码

对于扩展属性:

val String.lastChar: Char
    get() = this[length - 1]

fun main(args: Array<String>) {
    println(String::lastChar.get("abc")) // 输出 "c"
}
复制代码

与 Java 反射的互操做性

在Java平台上,标准库包含反射类的扩展,它提供了与 Java 反射对象之间映射(参见 kotlin.reflect.jvm包)。 例如,要查找一个用做 Kotlin 属性 getter 的 幕后字段或 Java方法,能够这样写:

import kotlin.reflect.jvm.*

class A(val p: Int)

fun main(args: Array<String>) {
    println(A::p.javaGetter) // 输出 "public final int A.getP()"
    println(A::p.javaField)  // 输出 "private final int A.p"
}
复制代码

要得到对应于 Java 类的 Kotlin 类,请使用 .kotlin 扩展属性:

fun getKClass(o: Any): KClass<Any> = o.javaClass.kotlin
复制代码

构造函数引用

构造函数能够像方法和属性那样引用。他们能够用于期待这样的函数类型对象的任何地方:它与该构造函数接受相同参数而且返回相应类型的对象。 经过使用 :: 操做符并添加类名来引用构造函数。考虑下面的函数, 它期待一个无参并返回 Foo 类型的函数参数:

class Foo

fun function(factory: () -> Foo) {
    val x: Foo = factory()
}
复制代码

使用 ::Foo,类 Foo 的零参数构造函数,咱们能够这样简单地调用它:

function(::Foo)
复制代码

绑定的函数与属性引用(自 1.1 起)

你能够引用特定对象的实例方法。

val numberRegex = "\\d+".toRegex()
println(numberRegex.matches("29")) // 输出“true”
 
val isNumber = numberRegex::matches
println(isNumber("29")) // 输出“true”
复制代码

取代直接调用方法 matches 的是咱们存储其引用。 这样的引用会绑定到其接收者上。 它能够直接调用(如上例所示)或者用于任何期待一个函数类型表达式的时候:

val strings = listOf("abc", "124", "a70")
println(strings.filter(numberRegex::matches)) // 输出“[124]”
复制代码

比较绑定的类型和相应的未绑定类型的引用。 绑定的可调用引用有其接收者“附加”到其上,所以接收者的类型再也不是参数:

val isNumber: (CharSequence) -> Boolean = numberRegex::matches

val matches: (Regex, CharSequence) -> Boolean = Regex::matches
复制代码

属性引用也能够绑定:

val prop = "abc"::length
println(prop.get())   // 输出“3”
复制代码

类型别名

类型别名为现有类型提供替代名称。 若是类型名称太长,你能够另外引入较短的名称,并使用新的名称替代原类型名。能够为函数类型提供另外的别名,也能够为内部类和嵌套类建立新名称

类型别名不会引入新类型。 它们等效于相应的底层类型。 当你在代码中添加 typealias Predicate<T> 并使用 Predicate<Int> 时,Kotlin 编译器老是把它扩展为 (Int) -> Boolean

fun main(args: Array<String>) {
    val net: Net = Network()

    val enable = enable(net) {
        netStatus() == 0
    }
    println(enable)

    val p: Predicate<Int> = { it > 0 }
    println(listOf(1, -2).filter(p)) // 输出 "[1]"
}


typealias Net = Network
typealias Node = Network.Node

typealias NodeSet = Set<Network.Node>
typealias FileTable<N> = MutableMap<N, MutableList<File>>

typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean

fun netStatus(): Int = 0

class Network {
    inner class Node
}

fun <T> enable(t: T, p: Predicate<T>): Boolean {
    return p(t)
}
复制代码
相关文章
相关标签/搜索