Kotlin
参考
简介
Kotlin是一门把Java平台作为目标的新的编程语言。它简洁、安全、优雅而且专注于和Java代码间的互操作性。它几乎可以用于如今Java遍及的所有地方.
- Kotlin是静态类型的,支持类型推断的,在保持代码精简的同时维持准确性和性能。
- kotlin同时支持面向对象和函数式编程风格,通过把函数放在一等公民的位置实现更高层次的抽象,通过支持不可变值简化了测试和多线程开发。
- Kotlin在服务器端应用运行良好。它能全面支持现有的Java框架并为公共任务提供了新的工具,例如生成HTML和保持一致性。
- Kotlin是免费和开源的。它为主流IDE和构建系统提供了全面的支持。
- Kotlin是优雅的、安全的、精简的以及互操作性强的(语言)。这意味着它专注于使用已经被证明的方案来解决常见任务,阻止一般的错误,例如:NullPointerException,支持紧凑和易读的代码,松散的Java集成功能。
kotlin构建流程:
函数和变量
Hello,world!
fun main(args: Array<String>) {
println("Hello, world!")
}
- 用fun来定义一个函数;
- 函数可以不依赖类而存在;
- 参数类型写在参数名后面;
- 数组只是一个类, 没有声明数组的特殊语法;
- 可以省略行末的分号.
函数
函数声明以fun关键字为开始,接着是函数名:max。接着是圆括号中的参数列表。返回类型跟在参数列表后面,以冒号分隔。
在kotlin中if是一个表达式; if的分支可以是代码块, 代码块中最后的表达式就是这个代码块的值; 如果if被用作表达式(比如返回值或者给变量赋值), 则需要有else分支.
如果函数的内容部分只有一个表达式,你可以移除大括号和return声明,使用表达式作为整个函数的主体;对于表达式主体函数来说,编译器能够分析用做主函数主体的表达式,并使用表达式的类型作为函数返回类型.只有表达式函数才允许忽略返回值。
fun max(a: Int, b: Int) = if (a > b) a else b
命名参数和默认参数
定义参数时,默认参数值可以减少函数的重载:
fun <T> joinToString(
collection: Collection<T>,
separator: String = ", ",
prefix: String = "",
postfix: String = ""
): String
调用Kotlin中定义的函数时, 可以指定参数的名称, 使代码更易读, 但调用Java中的方法并不行:
// 有默认参数值的参数可省略
joinToString(collection = collection, postfix = " ")
变量
有两个关键词来声明一个变量:
- val 不可变的引用, 对应于final变量。
- var 可变的引用, 对应于非final变量。
如果变量有初始化器, 可以忽略变量的类型声明:
val message = "Success"
final String message = "Success";
一个val变量必须在定义块执行时被初始化而且只能一次。如果编译器能够确保唯一的初始化声明能够其中一个被执行, 你可以根据情况用不同的值初始化变量:
val message: String
if ( canPerformOperation()) {
message = "Success"
} else {
message = "Failed"
}
字符串模版
字符串可以包含模板表达式, 即一些小段代码, 会求值并把结果合并到字符串中.
val s = "abc"
val str = "$s.length is ${s.length}" // "abc.length is 3"
val price = """
${'$'}9.99
"""
类和属性
类
声明一个有name属性的类:
class Person(val name: String)
在Java中:
public class Person{
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
在Kotlin中的一个类可以有一个主构造函数和一个或多个次构造函数.主构造函数是类头的一部分: 它跟在类名(和可选的类型参数)后
class Person(firstName: String) {
}
如果主构造函数没有任何注解或者可见性修饰符, 可以省略这个 constructor
关键字.
class Person(firstName: String) {
}
主构造函数不能包含任何的代码. 初始化的代码可以放到以 init 关键字作为前缀的初始化块中:
class Customer(name: String) {
val customerKey = name.toUpperCase()
init {
logger.info("Customer initialized with value ${name}")
}
}
class Person(val firstName: String, val lastName: String, var age: Int) {
// ……
}
声明次构造函数
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果类有一个主构造函数, 每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
属性
声明属性的完整语法:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
kotlin中,类没有字段。
class Person(
val name: String, // 只读属性,生成一个幕后字段和一个简单的getter
var isMarried: Boolean // 可写属性,生成一个幕后字段和简单的getter和setter
)
如果属性的名称以"is"开头,那么它的getter不会增加任何前缀,并且它的setter会把is替换成set。
// java
Person person = new Person("fjh", false);
System.out.println(person.getName());
System.out.println(person.isMarried());
// kotlin
val person = Person("fjh", false)
println(person.name)
println(person.isMarried)
自定义访问器:
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}
class Rectangle(val height: Int, val width: Int) {
fun isSquare(): Boolean = height == width
}
继承
Kotlin 需要显式标注可覆盖的成员和覆盖后的成员, 除了用abstract标记的抽象类和抽象成员.
在Kotlin中所有类都有一个共同的超类Any, 这对于没有超类型声明的类是默认超类; Any 不是 java.lang.Object, 它除了equals(),hashCode()和toString() 外没
有任何成员.
如果该类有一个主构造函数,其基类型可以用主构造函数参数就
地初始化.
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
final override fun v() {} // 通过final禁止再次继承
}
如果类没有主构造函数, 那么每个次构造函数必须使用super关键字初始化其基类型, 或委托给另一个构造函数做到这一点.
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
接口
Kotlin 的接口与 Java 8 类似,既包含抽象方法的声明,也包含实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。
interface MyInterface {
fun bar()
fun foo() {
// 可选的方法体
}
}
带有属性的接口(如果接口定义了getter那么它的的getter不能引用属性):
interface MyInterface {
val prop: Int // 抽象的
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
}
内部类和嵌套类
像在Java中一样, 类可以嵌套在其他类中. 但在Kotlin中, 嵌套类默认和Java中的静态的内部类类似.
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
类A在另一个类B中的声明 | Java | Kotlin |
---|---|---|
嵌套类 | static class A | class A |
内部类 | class A | inner class A |
密封类
密封类用来表示受限的类继承结构:当一个值为有限集中的类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
- 密封类用sealed声明;
- 一个密封类是自身抽象的,它不能直接实例化并可以有抽象成员;密封类不允许有非- private构造函数(其构造函数默认为 private );
- 扩展密封类子类的类(间接继承者)可以放在任何位置, 而无需在同一个文件中;
- 密封类的所有子类都必须在与密封类自身相同的文件中声明(在1.1之前, 必须在密封类内部).
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
数据类
我们经常创建一些只保存数据的类。在这些类中,一些标准函数往往是从数据机械推导而来的。在 Kotlin 中,这叫做 数据类 并标记为 data :
data class User(val name: String, val age: Int)
编译器自动从主构造函数中声明的所有属性导出以下成员:
- equals() / hashCode() 对;
- toString() 格式是 "User(name=John, age=42)" ;
- componentN() 函数按声明顺序所有属性(用于解构声明);
- copy() 函数。
在很多情况下, 我们需要复制一个对象改变它的一些属性, 但其余部分保持不变. copy()函数就是为此而生成.
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
val fjh = User(name = "fjh", age = 22)
val olderFjh = fjh.copy(age = 23)
解构声明
一个解构声明同时创建多个变量:
class Person(val name: String, var age: Int) {
operator fun component1() = name
operator fun component2() = age
}
fun main(args: Array<String>) {
val (name, age) = Person("fjh", 22)
println(name)
println(age)
}
会被编译成一下代码:
val name = person.component1()
val age = person.component2()
componentN() 函数需要用 operator 关键字标记,以允许在解构声明中使用它们.
委托
类委托
类可以继承一个接口,并将其所有共有的方法委托给一个指定的对象:
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print() // 输出 10
}
可以覆盖你想要修改的方法.
属性委托
有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够把他们只实现一次并放入一个库会更好。例如包括:
- 延迟属性(lazy properties): 其值只在首次访问时计算;
- 可观察属性(observable properties):监听器会收到有关此属性变更的通知;
- 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
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 Example {
var p: String by Delegate()
}
object关键字
object关键字不同于class, 它会声明一个类并创建一个实例.
使用场景:
- 对象声明, 实现单例;
- 伴生对象, 实现类似Java中的静态成员;
- 对象表达式, 代替Java匿名内部类.
伴生对象
Kotlin中的类不能有静态成员, 而用包级别函数和对象声明替代.
class MyClass {
// 可以省略伴生对象的名称
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员.
val instance = MyClass.Factory.create()
对象表达式
fun countClicks(window: Window) {
var clickCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
})
}
扩展
Kotlin 同 C# 和 Gosu类似,能够扩展一个类的新功能而无需继承该类或使用像装饰者这样的任何类型的设计模式。这通过叫做扩展的特殊声明完成。Kotlin 支持 扩展函数 和 扩展属性。
扩展函数
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
扩展不能真正的修改他们所扩展的类; 调用的扩展函数是由函数调用所在的表达式的类型来决定的,而不是由表达式运行时求值结果决定的。
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())
// 输出c
扩展属性
扩展属性不能有初始化器
val <T> List<T>.lastIndex: Int
get() = size - 1
伴生对象扩展
class MyClass {
companion object { } // 将被称为 "Companion"
}
fun MyClass.Companion.foo() {
// ……
}
目录和包
kotlin中目录和包不需要对应;没有import static。
源文件通常以包声明开头, 如果没有指明包, 该文件的内容属于无名字的默认包:
package utry
// ......
有多个包会默认导入到每个Kotlin文件中:
- 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.
如果出现名字冲突, 可以使用 as 关键字在本地重命名冲突项来消歧义:
import foo.Bar // Bar 可访问
import bar.Bar as bBar // bBar 代表“bar.Bar”
import 也可以用来导入:
- 顶层函数及属性;
- 在对象声明中声明的函数和属性;
- 枚举常量.
可见性修饰符
在 Kotlin 中有这四个可见性修饰符: private 、protected 、 internal 和 public。如果没有显式指定修饰符的话,默认可见性是public 。
Kotlin中没有Java的包私有, 而提供了internal(模块内部可见).
可见性修饰符 internal意味着该成员只在相同模块内可见。更具体地说,一个模块是编译在一起的一套 Kotlin 文件:
- 一个 IntelliJ IDEA 模块;
- 一个 Maven 项目;
- 一个 Gradle 源集;
- 一次 <kotlinc> Ant 任务执行所编译的一套文件。
修饰符 | 类成员 | 顶层声明 |
---|---|---|
public | 所有地方可见 | 所有地方可见 |
internal | 模块中可见 | 模块中可见 |
protected | 子类中可见 | --- |
private | 类中可见 | 文件中可见 |
枚举和when
声明枚举类型
enum class Color(val r: Int, val g: Int, val b: Int){
RED(255, 0, 0), GREEN(0, 255, 0), BLUE(0, 0, 255)
fun rgb() = (r * 256 + g) * 256 + b
}
when表达式
用when处理枚举类型:
fun getName(color: Color) =
when (color) {
Color.RED -> "Apple"
Color.GREEN -> "Hat"
Color.BLUE -> "Sky"
}
显式导入枚举常量后:
import Color.*
fun getName(color: Color) =
when (color) {
RED -> "Apple"
GREEN -> "Hat"
BLUE -> "Sky"
}
能使用任何表达式作为分支条件:
fun mix(c1: Color, c2: Color) =
when (setOf(c1, c2)) {
setOf(RED, GREEN) -> "Apple"
setOf(RED, BLUE) -> "Hat"
setOf(GREEN, BLUE) -> "Sky"
else -> throw Exception("Dirty color")
}
如果没有给when表达式提供参数, 那么分支条件就是任意的布尔表达式。
智能转换
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int =
if (e is Num) {
// 这个块内e被智能转换成Num 因此不需要下面这行代码
// val n = e as Num
e.value
} else if (e is Sum) {
// 这个块内e被只能转换成Sum
eval(e.right) + eval(e.left)
} else {
throw IllegalArgumentException("Unknown expression")
}
fun main(args: Array<String>) {
println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
}
// 输出 7
代码块中最后的表达式就是结果, 在所有使用代码块并期望得到结果的地方成立(除了常规函数).
when表达式代替if
fun eval(e: Expr): Int =
when (e) {
is Num ->
e.value
is Sum ->
eval(e.right) + eval(e.left)
else ->
throw IllegalArgumentException("Unknown expression")
}
while循环和for循环
while循环
while循环语法与java中相同:
区间和数列
- 区间是两个值之间的间隔, 用 .. 运算符来表示区间(.. 运算符也可以用作创建字符区间);
- Kotlin的区间是包含的或者闭合的,第二个值始终是区间的一部分;
- 如果能迭代一个区间中的所有值,这样的区间就叫数列。
// 1到100
for (i in 1..100) {
println(i)
}
// 100到1 步长为2
for (i in 100 downTo 1 step 2) {
println(i)
}
迭代map
val binaryReps = TreeMap<Char, String>()
for (c in 'A'..'F') {
binaryReps[c] = Integer.toBinaryString(c.toInt())
}
for ((letter, binary) in binaryReps) {
println("$letter = $binary")
}
用 in 检查集合和区间的成员
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun recognize(c: Char) = when(c) {
in '0'..'9' -> "It's a digit!"
in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
else -> "I don't know..."
}
异常
Kotlin的异常处理基本和Java类似,不同的是Kotlin的try和throw能作为表达式使用:
val percentage =
if (number in 0..100)
number
else
throw IllegalArgumentException("$number is not between 0 and 100")
throw表达式的类型为Nothing。
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
null
}
println(number)
}
try表达式的值语句主题的最后一个表达式的值, 如果捕获到了异常,那catch块的最后一个表达式就是结果。
空安全
Kotlin 的类型系统旨在从我们的代码中消除 NullPointerException 。NPE 的唯一可能的原因
可能是:
- 显式调用 throw NullPointerException() ;
- 使用了下文描述的 !! 操作符;
- 外部 Java 代码导致的;
- 对于初始化,有一些数据不一致(如一个未初始化的this用于构造函数的某个地方)。
可空类型与非空类型
Kotlin对可空类型显式支持, 这是一种指出程序中哪些变量和属性允许为null的方式.
所有常见类型默认都是非空的, 除非用?标记为可空.
var a: String = "abc"
a = null // 编译错误
var a: String? = "abc"
a = null // OK
可空类型不能访问类型的成员:
var a: String? = "abc"
println(a.length) // 编译错误
但是在检查非空后可以访问成员:
var a: String? = "abc"
if (a != null)
println(a.length)
// 以及...
println(when {
a != null -> a.length
else -> return
})
安全调用
安全调用操作符 ?. 是处理可空类型最安全有效的一种工具, 他把一次null检查和一次方法调用合并成一个操作.
var a: String? = "abc"
println(a?.toUpperCase())
println(a?.length)
如果调用的是非空值的方法, 方法会正常执行; 如果值是null, 方法不会执行, 表达式的值为null; 表达式的类型为可空类型.
null合并运算符(Elvis运算符)
Elvis运算符 ?: 第一个运算参数不为null, 结果就是第一个参数, 否则结果是第二个参数.
var a: String? = null
println(a ?: "abc")
println(a ?: throw IllegalArgumentException())
println(a ?: return)
非空断言
你可以用 !! 来告诉编译器这个值不会为null, 并准备好了接收NPE异常.
var a: String? = null
var b: String = a!! //会在这里抛出NPE
println(b.length)
安全转换
as? 运算符尝试把值转换成指定类型, 如果值不是合适的类型就返回null.
class Person(val firstName: String, val lastName: String) {
override fun equals(o: Any?): Boolean {
val otherPerson = o as? Person ?: return false
return otherPerson.firstName == firstName &&
otherPerson.lastName == lastName
}
override fun hashCode(): Int =
firstName.hashCode() * 37 + lastName.hashCode()
}
可空类型的扩展
fun verifyUserInput(input: String?) {
if (input.isNullOrBlank()) {
println("Please fill in the required fields")
}
}
fun main(args: Array<String>) {
verifyUserInput(" ")
verifyUserInput(null)
}
fun String?.isNullOrBlank(): Boolean = this == null || this.isBlank()
基本数据类型和其他基本类型
在 Kotlin 中, 所有东西都是对象, 但一些类型可以有特殊的内部表示——例如数字, 字符和布尔值可以在运行时表示为原生类型值.
数字
Kotlin 处理数字在某种程度上接近 Java, 但是并不完全相同. 例如对于数字没有隐式拓宽转换, 另外有些情况的字面值略有不
同.在kotlin中字符不是数字.
123L // 十进制Long
0x0F // 十六进制
0b00001011 // 二进制
123.5 // Double
123.5e10 // Double
123.5f // Float
不支持8进制
从1.1起, kotlin的数字字面值中可以加入下划线来使数字更易读:
val oneMillion = 1_000_000
表示方式
在Java平台下数字会物理存储为JVM的原生类型; 在需要可空引用或泛型时, 会将数字装箱.
val a: Int = 10000
print(a === a) // true
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA) // true
print(boxedA === anotherBoxedA) // false
Kotlin的相等性
- 引用相等, 由===检查;
- 结构相等, 由equals检查.
a == b
可被翻译为a?.equals(b) ?: (b === null)
== 可被重载而 === 不行
显式转换
每个数字类型支持如下的转换:
- toByte(): Byte
- toShort(): Short
- toInt(): Int
- toLong(): Long
- toFloat(): Float
- toDouble(): Double
- toChar(): Char
运算
Kotlin支持数字运算的标准集, 运算被定义为相应的类成员(但编译器会将函数调用优化为相
应的指令).操作符可被重载.
没有特殊字符来表示位运算,只能中缀方式调用命名函数:
val x = (1 shl 2) and 0x000FF000
- shl(bits) – 有符号左移 ( << )
- shr(bits) – 有符号右移 ( >> )
- ushr(bits) – 无符号右移 ( >>> )
- and(bits) – 位与
- or(bits) – 位或
- xor(bits) – 位异或
- inv() – 位非
字符
字符用Char类表示;字符不能被当作数字, 但是可用toInt()
转换为Int; 需要可空引用时也会被装箱.
布尔
布尔用Boolean类表示, 若需要可空引用布尔会被装箱.
数组
数组使用Array类来表示, 定义了get和set函数(在操作符重载中对应[]), size以及成员函数.
arrayOf(1, 2, 3) // array [1, 2, 3]。
arrayOfNulls(3) // array [null, null, null]
Array(5, { i -> (i * i).toString() }) // ["0", "1", "4", "9", "16"]
字符串
字符串使用String 表示; 字符串中的字符可用索引访问: s[i]
; 可用for遍历字符串:
for (c in str) {
println(c)
}
原生字符串 使用"""
分界符括起来,内部没有转义并且可以包含换行和任何其他字符:
val text = """
for (c in "foo")
print(c)
"""
println(text)
val text2 = """
|for (c in "foo")
|print(c)
""".trimMargin() // 用`.trimMargin()`去除前导空格
println(text2)
val text3 = """
>for (c in "foo")
>print(c)
""".trimMargin(">") // 用`.trimMargin()`去除前导空格
println(text3)
/* output:
for (c in "foo")
print(c)
for (c in "foo")
print(c)
for (c in "foo")
print(c)
*/
Any和Any?
类似Object, Any是所有非空类型的超类型. 它除了equals(),hashCode()和toString() 外没有任何成员. 如果需要调用Object的方法, 可以把值转换成java.lang.Object来调用.
Unit类型
与Java中的void功能一样, 但Unit是一个完备的类型, 可以作为类型参数.
interface Processor<T> {
fun process(): T
}
class NoResultProcessor : Processor<Unit> {
override fun process() {
// ...
// 不需要return
}
}
Nothing类型
Nothing类型没有值, 它用于标记永远不可能到达的地方. 可以用Nothing来标记一个永远不可能返回的函数.
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
Nothing? 有一个可能的值 null. 果用 null 来初始化一个要推断类型的值, 而又没有其他信息可用于确定
更具体的类型时, 编译器会推断出 Nothing? 类型.
val x = null // “x”具有类型 `Nothing?`
val l = listOf(null) // “l”具有类型 `List<Nothing?>
集合和数组
只读集合与可变集合
Kotlin中并没有自己的集合类, 它的集合类和Java中完全相同; Kotlin把访问集合数据的接口和修改集合数据的接口分开了.
Kotlin 的 List 类型是一个提供只读操作如 size 、get 等的接口。和 Java 类似,它继承自 Collection 进而继承自 Iterable 。改变 list 的方法是由 MutableList 加入的。这一模式同样适用于 Set/MutableSet 及 Map/MutableMap 。
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泛型定义与Java类似. Kotlin中没有通配符,它有两个其他的东西:声明处型变(declaration-site variance)和类型投影(type projections).
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // Java中禁止这样
声明处型变
协变: out操作符标注类型参数只用做在方法中返回, 而不会在方法的参数中出现:
abstract class Source<out T> { //Source 被声明为在T上协变
abstract fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs
// ……
}
逆变: in操作符标注类型参数只能用在方法参数中, 而不能出现在返回值中:
abstract class Comparable<in T> {
abstract fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
val y: Comparable<Double> = x // OK!
}
消费者 in, 生产者 out!
类型投影
使用处的型变.
例如Array 这样的类, 不能在参数类型的声明处限制只能返回T, 可以在使用的时候限制它:
fun copy(from: Array<out Any>, to: Array<Any>) {
// ……
}
fun fill(dest: Array<in String>, value: String) {
// ……
}
星投影
当你不知道泛型的信息时, 可以使用星投影:
fun printFirst(list: List<*>) {
if (list.isNotEmpty()) {
println(list.first())
}
}
注解
声明注解,要将 annotation 修饰符放在类的前面
annotation class Fancy
注解的附加属性可以通过用元注解标注注解类来指定:
- @Target 指定可以用该注解标注的元素的可能的类型(类、函数、属性、表达式等);
- @Retention 指定该注解是否存储在编译后的 class 文件中,以及它在运行时能否通过反射可见 (默认都是 true);
- @Repeatable 允许在单个元素上多次使用相同的该注解;
- @MustBeDocumented 指定该注解是公有 API 的一部分,并且应该包含在生成的 API 文档中显示的类或方法的签名中。
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy
注解可以有接受参数的构造函数:
annotation class Special(val why: String)
@Special("example") class Foo {}
允许的参数:
- 对应于 Java 原生类型的类型(Int、 Long等);
- 字符串;
- 类( Foo::class );
- 枚举;
- 其他注解;
- 上面已列类型的数组。
注解参数不能有可空类型,因为 JVM 不支持将 null 作为注解属性的值存储。
如果注解用作另一个注解的参数,则其名称不以 @ 字符为前缀:
annotation class ReplaceWith(val expression: String)
annotation class Deprecated(
val message: String,
val replaceWith: ReplaceWith = ReplaceWith(""))
@Deprecated("This function is deprecated, use === instead", ReplaceWith("this === othe
r"))
如果需要将一个类指定为注解的参数,请使用 Kotlin 类 (KClass)。Kotlin 编译器会自动将
其转换为 Java 类,以便 Java 代码能够正常看到该注解和参数:
import kotlin.reflect.KClass
annotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any?>)
@Ann(String::class, Int::class) class MyClass
反射
Kotlin可以使用两种反射API, Java的反射和Kotlin的反射.
Kotlin的反射API在kotlin.reflect下, Kotlin反射能提供Java中没有的信息(属性和可空类型); Kotlin反射API没有仅限于Kotlin类.
使用Kotlin的反射API需要添加kotlin-reflect的jar
类引用
KClass代表了一个类, 对应 java.lang.Class .
可以使用MyClass::class的写法来获得KClass实例; 用MyClass::class.java来获得Java中的class对象; 在运行时, 可以使用javaClass来获得Java类, 再访问.kotlin扩展属性:
import kotlin.reflect.memberProperties
class Person(val name: String, val age: Int)
fun main(args: Array<String>) {
val person = Person("Alice", 29)
val kClass = person.javaClass.kotlin // 或者person::class
println(kClass.simpleName)
kClass.memberProperties.forEach { println(it.name) }
}
函数引用
使用 :: 来获得一个命名函数的引用:
fun isOdd(x: Int) = x % 2 != 0
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // ::isOdd 是函数类型(Int) -> Boolean的一个值
// 输出 [1, 3]
当上下文中已知函数期望的类型时, :: 可以用于重载函数。
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: (Int) -> Boolean = ::isOdd
引用类的成员函数或者成员属性,或者extension函数,需要加上类名,如String::toCharArray
.
属性引用
val x = 1
fun main(args: Array<String>) {
//
println(::x.get()) // 输出 "1"
println(::x.name) // 输出 "x"
}
::x 类型为KProperty; 如果x是可变属性, 则他的类型为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"
}
构造函数引用
构造函数可以像方法和属性那样引用。他们可以用于期待这样的函数类型对象的任何地方:
它与该构造函数接受相同参数并且返回相应类型的对象。
class Foo
fun function(factory : () -> Foo) {
val x : Foo = factory()
}
function(::Foo)
Lambda
简化Lambda表达式
args.forEach({
element -> println(element)
})
args.forEach{
println(it)
}
args.forEach(::println)
- 最后一个Lambda可以移参数列表的括号
- 只有一个Lambda,小括号可省略
- Lambda 只有一个参数可默认为 it
- 入参、返回值与形参一致的函数可以用函数引用的方式作为实参传入
返回和跳转
Kotlin 有三种结构化跳转表达式, 这些表达式的类型是Nothing:
- return 。默认从最直接包围它的函数或者匿名函数返回。
- break 。终止最直接包围它的循环。
- continue 。继续下一次最直接包围它的循环。
在 Kotlin 中任何表达式都可以用标签(表示符后加@, 比如[email protected]
)来标记。加上标签后, 就可以用标签来限制break和continue.
[email protected] for (i in 1..100) {
for (j in 1..100) {
if (……) [email protected]
}
}
break 跳转到刚好位于该标签指定的循环后面的执行点。 continue 继续标签指定的循环的下一次迭代。
如果我们需要从Lambda中返回, 必须加标签来限制return:
args.forEach [email protected]{
if(it == "q") [email protected]
println(it)
}
Gradle + Spring Boot
可以去start.spring.io生成一个示例项目
- 新建一个gradle项目:
- 添加所需插件和依赖, 最终gradle.build如下:
group 'utry'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.1.4-2'
ext.springboot_version = '1.5.2.RELEASE'
repositories {
// 仓库的阿里云镜像
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
//jcenter()
//mavenCentral()
}
dependencies {
// Kotlin Gradle插件
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// Kotlin整合SpringBoot的默认无参构造函数,默认把所有的类设置open类插件
classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlin_version")
classpath("org.jetbrains.kotlin:kotlin-noarg:$kotlin_version")
// SpringBoot Gradle插件
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springboot_version")
}
}
apply plugin: 'kotlin'
apply plugin: 'war'
//Kotlin-spring 编译器插件,它根据 Spring 的要求自动配置全开放插件。
apply plugin: 'kotlin-spring'
apply plugin: 'org.springframework.boot'
repositories {
// 仓库的阿里云镜像
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
//jcenter()
//mavenCentral()
}
dependencies {
// https://mvnrepository.com/artifact/io.springfox/springfox-swagger2
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.7.0'
// https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.7.0'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
compile group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '1.3.1'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
testCompile group: 'junit', name: 'junit', version: '4.11'
compile('mysql:mysql-connector-java:5.1.13')
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
- 创建Spring Boot配置类和main函数:
@SpringBootApplication
@EnableSwagger2
class PostApplication {
@Bean
fun createRestApi(): Docket {
return Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("cn.utry"))
.paths(PathSelectors.any())
.build()
}
private fun apiInfo(): ApiInfo {
return ApiInfoBuilder()
.title("简单的Demo")
.build()
}
}
fun main(args: Array<String>) {
SpringApplication.run(PostApplication::class.java, *args)
}
- Controller示例:
@RestController
@RequestMapping("/post")
class PostController {
@Autowired
lateinit var postService: PostService
@RequestMapping("/", method = arrayOf(RequestMethod.POST))
fun submit(content: String) = postService.submit(content)
@RequestMapping("/", method = arrayOf(RequestMethod.GET))
fun query() = postService.query()
}
kotlin的生态
测试
- KotlinTest should风格.
- Spek 属于Kotlin的BDD风格的测试框架. JetBrains发起, 现由社区维护.
// KotlinTest示例
s should startWith("kot")
JSON序列化
- Jackson;
- Kotson GSON的包装器.
Web应用
- Ktor JetBrains的研究项目.
- Kara 最初的Kotlin Web框架.
数据库
Exposed, sql生成框架.
//声明一张表
object Country : Table() {
val id = integer("id").autoIncrement().primaryKey() //Column类型
val name = varchar("name", 50)
}
SchemaUtils.create(Country)