Kotlin 快捷入门

前言

人生苦多,快来 Kotlin ,飞快学习Kotlin!

什么是Kotlin?

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

参考: Kotlin 官网
/ Kotlin
语言普通话站

Example

Github
KotlinDemo
Hexo Site

源文件与包

Kotlin 源文件以 kt 结尾.
源文件所有内容(无论是类依然函数)都带有在注脚的包内.
NOTE: 源文件一般以包声明发轫,
没有指明包,则该公文的内容属于无名字的默许包(属于root package)。

package demo 

NOTE: 若注脚的包路径与公事路径不均等,亦可以健康编译. 然而会有如Java
一样的警告 Package directive doesn’t match file location

默许导入

Kotlin 就如Java 一样 默许源文件会导入以下包:

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

导入语法

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 中落实了常用的整型区间(IntRange
LongRangeCharRange),而在 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 操作符格局利用。

整型数列(IntProgressionLongProgression
CharProgression)表示等差数列。 数列由 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
中有这三个可知性修饰符:privateprotectedinternal
public

修饰符 是否支持顶级声明 当前文件 同一模块
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<Base> :

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 方法的 ReadOnlyProperty
ReadWriteProperty 接口之一。 那俩接口是在 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
的语法。

拉姆da 表明式与匿名函数

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

拉姆da 表明式语法

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。能够在其他模块中调用它,并且也足以在调用处内联那样的调用。

这带来了有的由模块做那样变更时造成的二进制包容的风险——声多美滋(Dumex)个内联函数但调用它的模块在它修改后并从未再一次编译。

为了破除那种由国有 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<T> Iterator<T> MutableIterator<T> (Mutable)Iterator<T>!
Iterable<T> Iterable<T> MutableIterable<T> (Mutable)Iterable<T>!
Collection<T> Collection<T> MutableCollection<T> (Mutable)Collection<T>!
Set<T> Set<T> MutableSet<T> (Mutable)Set<T>!
List<T> List<T> MutableList<T> (Mutable)List<T>!
ListIterator<T> ListIterator<T> MutableListIterator<T> (Mutable)ListIterator<T>!
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 代码举行互动。
对于每种原生类型的数组都有一个特化的类(IntArrayDoubleArray
CharArray 等等)来处理那种意况。 它们与 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 类有时声多美滋(Beingmate)个独具可变多少参数(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 方法。 因为没有从 Class
KClass 的机关转换,所以你必须经过调用 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

要表明一(Meadjohnson)个在本土(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?) 函数,否则(即 a
null)检查 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
可以用来标记一个永恒不会回去的函数, 也得以看成 埃尔维斯 表达式的一有的:

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 当且仅当 a
b 指向同一个指标时求值为 true。

布局分外

协会卓绝由 ==(以及其否定格局 !=)操作判断。根据常规,像 a == b
这样的表明式会翻译成

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

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

请注意,当与 null 显式比较时完全没须要优化你的代码:a == null
会被活动转换为 a=== null

浮点数相等性

当相等性检测的多少个操作数都是静态已知的(可空或非空的)Float
Double 类型时,该检测遵守 IEEE 754 浮点数运算标准。

否则会动用不符合该规范的构造相等性检测,那会招致 NaN 等于其自身,而
-0.0 不等于 0.0

异常

异常类

Kotlin 中拥有尤其类都是 Throwable 类的子孙类。
每个十分都有消息、堆栈回溯音讯和可选的缘由。

使用 throw-表达式来抛出越发:

throw MyException("Hi There!")

使用 try-说明式来捕获极度:

try {
    // 一些代码
}
catch (e: SomeException) {
    // 处理程序
}
finally {
    // 可选的 finally 块
}

可以有零到八个 catch 块。finally 块可以大致。 不过 catch
finally 块至少应当留存一个。

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}" }

您可以收获对象的精确类的引用,例如 GoodWidget
BadWidget,即使接收者表达式的品种是 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)
}