Kotlin lang 学习笔记
Contents
基本语法
定义 package
不要求目录与包相匹配~: 源文件可以放在文件系统的任意位置
源文件中的所有内容, 都是通过包声明来包含的. 即: 名包.方法名
等
特别地, 如果没指定包, 则该文件属于
default
包.(没有显式的名称的包)
默认导入的包
所有 kotlin 文件默认情况下, 都会导入以下包
kotlin.*
kotlin.annotation.*
kotlin.collections.*
kotlin.comparisons.* (since 1.1)
kotlin.io.*
kotlin.ranges.*
kotlin.sequences.*
kotlin.text.*
根据不同的目标平台, 还会导入一些额外的包:
JVM 平台
java.lang.* kotlin.jvm.*
JS 平台
kotlin.js.*
import, 导入包
import 指令不必限制于 classes 的, 你可以用它来导入其他的声明:
- 顶层的 方法和属性
- 声明在对象声明里的 方法和属性
- 枚举常量
不同于Java, Kotlin 没有单独的
import static
语法. 所有的导入, 都是通过import
来声明的
例子:
import foo.Bar
import foo.*
import bar.Bar as bBar
定义函数
语法
函数是通过 fun
关键字来定义的
参数定义
它是使用 Pascal
风格来定义的
name: type
即, 名称: 类型
默认参数
即在 type
后使用 = defaultValue
来定义. 如:
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {
...
}
override method
override method
总是与 base method
使用相同的默认值的!(注意, 总是
, always)~
当 override method
时, 要注意在方法签名里, 不能带有默认值! 否则会报如下错:
An overriding function is not allowed to specify default values for its parameters
如果一个方法中, 前面带有默认参数, 后面参数没有时, 这时如果想利用默认参数值的话, 则只能通过命名参数的方式来调用方法:
fun foo(bar: Int = 0, baz: Int) { /* ... */ }
foo(baz = 1) // The default value bar = 0 is used
但, 如果最后一个参数是 lambda
时, 则允许只直接传递最后一个没有默认值的 lambda
参数:
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /* ... */ }
foo(1) { println("hello") } // Uses the default value baz = 1
foo { println("hello") } // Uses both default values bar = 0 and baz = 1
命名参数
当调用方法时, 允许通过命名参数的方式来调用.
命名参数与位置参数混合调用时
规则为: 所有的位置参数必须在第一个命名参数的前面.即:
允许:
f(1, y = 2)
但不允许:
f(x = 1, 2)
可变参数
vararg
来声明
fun foo(vararg strings: String) { /* ... */ }
foo(strings = *arrayOf("a", "b", "c"))
Unit 返回类型
如果一个方法没有返回值, 则它的返回类型为 Unit
, 它是一种仅有唯一值 Unit
的类型.(可以忽略显式写法)
fun printHello(name: String?): Unit {
if (name != null)
println("Hello ${name}")
else
println("Hi there!")
// `return Unit` or `return` is optional
}
或
fun printHello(name: String?) {
...
}
单一表达式 函数 (Single-Expression function)
fun double(x: Int): Int = x * 2
infix 符号
通过 infix
关键字的 function
也被称为使用 infix
符号记法(即, 允许忽略点和括号来调用). 它必须满足以下条件:
- 它们必须是成员函数(member function)或扩展函数(extension function)
- 它们必须有一个参数
- 参数一定不能为可变参数, 也不能有默认值
如
infix fun Int.shl(x: Int): Int {
// ...
}
// calling the function using the infix notation
1 shl 2
// is the same as
1.shl(2)
注意, infix 函数 比 算术操作符, 类型转换换以入 rangeTo 操作符的优先级更低!
注意, infix 函数总是要求指定 receiver
和 parameter
的!
- 当你在一个方法里调用 infix 函数时, 你需要显式使用
this
作为receiver
, 其他的方法允许忽略它, 但 infix 方法不能忽略this
.
这种要求用来确保解释时没有歧义!
函数作用域
local function
本地函数. 即一个函数在另一个函数里面.
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: Set<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
local function
可以访问外部函数的 local variable
. 如:
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
member function
class Sample() {
fun foo() { print("Foo") }
}
调用
Sample().foo() // creates instance of class Sample and calls foo
generic function
fun <T> singletonList(item: T): List<T> {
// ...
}
inline function
extension function
higher-order function 和 lambda
tail recursive function
例子
fun sum(a: Int, b: Int): Int {
return a + b
}
fun sum(a: Int, b: Int) = a + b
fun printSum(a: Int, b: Int): Unit {
println("sum of $a and $b is ${a + b}")
}
fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}
定义变量
一次性声明 local variable(read only):
val a: Int = 1 // immediate assignment
val b = 2 // `Int` type is inferred
val c: Int // Type required when no initializer is provided
c = 3 // deferred assignment
可变变量
var x = 5 // `Int` type is inferred
x += 1
顶层变量
val PI = 3.14
var x = 0
fun incrementX() {
x += 1
}
定义属性
在 kotlin 中, classes 可以有属性. 它们可通过使用 var
关键字来声明为可变的(mutable), 或 通过 val
关键字来声明为不可变的.
完整的语法
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
当声明为 var
(可变) 的属性时, 它默认会有 getter
和 setter
方法.
当声明为 val
(不可变) 的属性时, 它只有 getter
而没有 setter
方法
自定义访问方法: (1.1及之后版本, 可以忽略 属性的类型, 由 getter 方法来推断)
val isEmpty: Boolean
get() = this.size == 0
自定义setter 方法:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}
如果你想修改 getter 和 setter
的访问权限或想注入它, 但又不想修改默认实现, 则可以这样子:
var setterVisibility: String = "abc"
private set // the setter is private and has the default implementation
var setterWithAnnotation: Any? = null
@Inject set // annotate the setter with Inject
compile time constant
如果属性的值可以在编译时就可以确定的话, 则它可以标记为编译时常量. 通过 const
修饰符来设置.这样子的属性, 要完全满足以下要求:
- 顶层或者是一个 object 的成员.
- 初始值的类型为
String
或为 基本数据类型 (primitive type) - 没有自定义的
setter
方法
例如
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
late initialized 属性和变量(延迟初始化)
通过 lateinit
修饰符来修饰.
条件为 - var 声明的变量 - 必须是 non null - 没有自定义的 getter 和 setter - 1.2及之后版本, 则为 顶层的属性 以及 local variable
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
检测 lateinit
修改的变量是否初始化(1.2版本开始)
if (foo::bar.isInitialized) {
println(foo.bar)
}
注释
行注释
//
块注释
/*
*/
不同于Java, 在 kotlin 中, 块注释可以嵌套的~
使用字符串模板
字符串可以包含 template expression
, 它是以一个 $
开头以及一个简单的变量或表达式组成的.
它在在 raw string
中也支持.
val price = """
${'$'}9.99
"""
例子
var a = 1
// simple name in template:
val s1 = "a is $a"
a = 2
// arbitrary expression in template:
val s2 = "${s1.replace("is", "was")}, but now is $a"
Null 安全的
Kotlin 的类型系统被设计于消除 null 引用的危险性的. 它可以区分是否一个引用能否为 null
的:
允许为 null:
var b: String? = "abc"
b = null // ok
不允许为 null:
var a: String = "abc"
a = null // compilation error
调用
检查 null 条件
if (b != null) {
//do something
}
安全调用
b?.length
这种也可用于调用链中~
elvis 操作符
?:
val l = b?.length ?: -1
等于同下面
val l: Int = if (b != null) b.length else -1
!! 操作符
如果引用为 null , 它会自动抛出 NPE 异常.
val l = b!!.length
安全转换
val aInt: Int? = a as? Int
当目标对象不能正确转换时, 它会返回 null
nullable 类型的集合
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
使用类型检查和自动转换
is
操作符检查一个表达式是否是一种类型的实例. 如果一个 immutable 的 local variable 或 属性 是已经检测为特定类型的话, 则不需要显式转换它. 例如:
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// `obj` is automatically cast to `String` in this branch
return obj.length
}
// `obj` is still of type `Any` outside of the type-checked branch
return null
}
使用 for 循环
for (item in collection) print(item)
for (item: Int in ints) {
// ...
}
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
使用 while 循环
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // y is visible here!
when 表达式
用于替换C系的 switch
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // Note the block
print("x is neither 1 nor 2")
}
}
或
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
或
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
使用 range
范围操作符: ..
例如:
if (i in 1..10) { // equivalent of 1 <= i && i <= 10
println(i)
}
反序:
for (i in 4 downTo 1) print(i) // prints "4321"
步进
for (i in 1..4 step 2) print(i) // prints "13"
until:
for (i in 1 until 10) { // i in [1, 10), 10 is excluded
println(i)
}
使用集合
for (item in items) {
println(item)
}
when {
"orange" in items -> println("juicy")
"apple" in items -> println("apple is fine too")
}
或
fruits
.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.toUpperCase() }
.forEach { println(it) }
创建基本类和它们的实例
val rectangle = Rectangle(5.0, 2.0) //no 'new' keyword required
val triangle = Triangle(3.0, 4.0, 5.0)
源码组织
目录结构
在一个混合语言的项目里, Kotlin 的源码应该与 Java 的源码的根目录相同, 并且跟随相同的目录结构(每个文件应该保存到 package
语句中的相应的目录)
在一个纯 Kotlin 的项目里, 建议目录的结构跟随在一个忽略了公共的根 package 下面(例如, 如果项目里所有的代码都是在 org.example.kotlin
包及子包下面, 则这些文件应该直接放在源码的根目录下, 并且文件在 org.example.kotlin.foo.bar
的则应该在源码的子目录 foo/bar
下面)
源码文件名
如果一个 kotlin 文件包含一个单独的 class , 则文件名应该与 class 的名字相同, 并带有 .kt
后缀.
如果包含多个 class, 或仅有顶层的声明的话, 则相应选择一个适当的描述的文件名. 使用 CamelHumps
(驼峰命名), 例如 ProcessDeclarations.kt
文件名应该选择适当描述文件内容的名称的. 因此, 应该避免使用无意义的单词, 例如 Util
这种文件名.
源文件的组织
我们鼓励将多个声明(class, 层顶的函数或属性)放在同一个源文件里, 只要这些声明相关性是比较话的话, 并且文件的大小也比较适合(不超出几百行代码)
特别地, 当为某个 class 定义 extension
函数时, 则建议将它们放在同一个文件中.
类层次
通常, class 的内容是按以下顺序来保存的: - 属性声明和初始化块 - 构造器 - 方法声明 - 引用的对象
不要将方法声明按字母或可见性来排序!
也不要分隔普通的方法和 extension 方法
相应的, 应该将相关性的放在一起, 以便人从上到下阅读源码时可以根据逻辑顺序来阅读.
将嵌套的类放在紧接着使用这些类的下面. 如果这些嵌套类是用于外部调用并且没有在内部使用的, 则放在类的最后.
接口实现层
如果实现一个接口的话, 则将实现的成员按接口中的相同顺序来保存.
如果有需要的话, 则为这些实现插入额外的私有方法.
重载层
总是将重载的方法放在一起!
命名规则
跟随Java的命名规则. 特别地:
名包总是小写的, 并且不要使用下划线(org.example.myproject
). 通常不建议使用多单词的名字, 但如果你确实需要, 你也可以简单地将它们拼接在一起, 或者使用驼峰式( com.example.myProject
)
函数名
函数名, 属性名, 以及 local variable 应该以小写字母开头, 并且使用驼峰的形式(没有下划线).
例外:
工厂方法(用于创建类实例)的, 方法名可以跟类名一样(大写字母开头的驼峰)
测试方法的命名
仅在测试方法中
它可以接受用反单引号里带有空格的方法名.(注意: 这种方法暂不被 Android Runtime 支持)
带有下划线的命名也允许在测试代码中:
class MyTestCase {
@Test fun `ensure everything works`() {
}
@Test fun ensureEverythingWorks_onAndroid() {
}
}
属性名
常量的命名, 应该以大写和以下划线分隔的名字:
const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"
其他的使用普通的驼峰命名(小写开头)
修饰符的顺序
public / protected / private / internal
expect / actual
final / open / abstract / sealed / const
external
override
lateinit
tailrec
vararg
suspend
inner
enum / annotation
companion
inline
infix
operator
data
Lable
在 Kotlin 中任意的语句都可以标记有一个 label
.
语法:
labelName@
即名称最后加个 @
使用 label 时, 则将 @
放在名称开头即可:
@labelName
return 中使用 label
fun foo() {
listOf(1, 2, 3, 4, 5).forEach lit@{
if (it == 3) return@lit // local return to the caller of the lambda, i.e. the forEach loop
print(it)
}
print(" done with explicit label")
}
return @a 1
表示在label @a
中进行 return 1
class 和 继承
class
通过关键字 class
来定义
组成:
- class name
- class header(指定它的参数, 主构造器, primary constructor 等)
- class body(用大括号包着)
header 和 body 都是可选的. 如果没有 body 的话:
class Empty
即可.
主构造器
class Person constructor(firstName: String) {
}
如果主构造器没有任何的 annotation 或 可见性修饰符, 则可以省略 constructor
关键字:
class Person(firstName: String) {
}
注意, 主构造器不能有任何代码!初始化代码可以被放在 initializer blocks
(初始化块中). 它是以 init
关键字为开头的代码块.它的初始化顺序, 就是代码出现的顺序. 例如:
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
println("First initializer block that prints ${name}")
}
val secondProperty = "Second property: ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
如果一个非抽象的 class 没有声明任何的构造器(主或次), 它会自动生成一个没有参数的 public
的主构造器. 如果你需要修改这访问权限的话, 则要显式地这样子:
class DontCreateMe private constructor () {
}
在JVM平台上, 如果主构造器的所有参数都有默认值的话, 编译器会生成一个额外的无参构造器, 它将使用这些默认值.这使 Kotlin 使用一些类似 Jackson 或 JPA 这些库更方便, 它们需要通过无参构造器来创建实例.
次构造器
可以有多个
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果一个 class 有一个 主构造器, 则每一个次构造器都需要委派给(delegate to)主构造器: 通过 this
关键字来做:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
初始化块
注意, 初始化块相当于主构造器的一部分. 委派给主构造器会相当于在次构造器的第一条语句中执行.
因此, 所有在初始化块的代码, 都会在次构器代码之前执行! 即使 class 没有主构造器也一样!
创建实例
val invoice = Invoice()
val customer = Customer("Joe Smith")
注意, 不需要 new
关键字.
class 成员
它可以包含
- 构造器来初始化块
- 函数
- 属性
- 嵌套以及内部类
- 对象声明
继承
所有 Kotlin 类都有一个公共的超类 Any
.
注意, Any
不是 java.lang.Object
. 特别地, 它没有任何的成员, 除了 equals()
, hashCode()
, toString()
之外.
显式指定继承的类, 可以在 class header 之后加个冒号. 如
class Derived(p: Int) : Base(p)
注意, 默认情况下, 所有的 Kotlin 类都是 final
的(基于 Effective Java 的第17条规则).
可以通过 open
来改变:
open class Base(p: Int)
这样子, 就允许该类被继承了.
如果子类有一个主构造器, 则父类可以且必需被初始化在正确的地方.
如果子类没有主构造器, 则每个子类中的次构造器必须通过 super
关键字来进行委派另一个构造器来初始化.
重写方法
不同于Java, Kotlin 中要求显式使用 override
来指明这是一个重写的方法
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}
一个标记为 override
的成员, 本身是 open
的. 如果你想禁止它重写, 则可使用 final
关键字:
open class AnotherDerived() : Base() {
final override fun v() {}
}
重写属性
重写属性也必须显式使用 override
, 并且它们必须有一个兼容的类型.
如:
open class Foo {
open val x: Int get() { ... }
}
class Bar1 : Foo() {
override val x: Int = ...
}
注意, 也可以在主构造器中使用 override
关键字作为属性声明的一部分.:
interface Foo {
val count: Int
}
class Bar1(override val count: Int) : Foo
class Bar2 : Foo {
override var count: Int = 0
}
子类初始化顺序
父类先初始化完, 再到子类.
这意味着, 在父类构造器执行期间, 子类中的声明的或重写的属性还没有初始化完毕的. 如果这些属性用在父类的初始化逻辑(不管是直接还是间接, 还是其他通过重写 open
成员来实现), 它可能会导致不正确的行为, 或者一个 runtime 失败.
设计一个父类时, 因此你应该避免在构造器, 属性初始化以及init
代码块中使用 open
的成员.
调用父类的实现
通过 super
关键字来调用
open class Foo {
open fun f() { println("Foo.f()") }
open val x: Int get() = 1
}
class Bar : Foo() {
override fun f() {
super.f()
println("Bar.f()")
}
override val x: Int get() = super.x + 1
}
在一个内部类中, 访问外部类的父类时, 可以通过 super
并带有外部类的名来实现: super@Outer
class Bar : Foo() {
override fun f() { /* ... */ }
override val x: Int get() = 0
inner class Baz {
fun g() {
super@Bar.f() // Calls Foo's implementation of f()
println(super@Bar.x) // Uses Foo's implementation of x's getter
}
}
}
重写规则
如果一个类从它的直接父类中继承了多个相同的成员, 则它必须重写它自己的成员, 并且提供它自己的实现.
为了指明使用哪个父类的实现, 可以通过 super
关键字, 并在带有 类括号
里写上父类类型. 例如:
open class A {
open fun f() { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } // interface members are 'open' by default
fun b() { print("b") }
}
class C() : A(), B {
// The compiler requires f() to be overridden:
override fun f() {
super<A>.f() // call to A.f()
super<B>.f() // call to B.f()
}
}
abstract class
注意, 我们不需要标记一个抽象类或函数为 open
的, 默认就是 open
的.
我们可以将一个非抽象的 open
成员, 重写为 abstract
的:
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
Companion Objects
在 Kotlin 中, 不像 Java 或 C# , 它没有静态方法的. 绝大部分情况下, 建议简单地使用 包级别的方法来代码~
如果你需要写一个函数可以不需要通过类实例就可以调用的话. 你可以将它写为一个 object declaration
的成员即可.
更具体地, 你可以声明一个 companion object
在你的类里. 这样, 你就可以像Java调用静态方法那样来调用它了.
Object Declaration
单例某几种情况下是非常有用的, 在 Kotlin 中, 你可以非常方便地声明单例:
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider>
get() = // ...
}
这被称为 object declaration
, 并且它总是一个 object
关键字后接一个名字的.
object declaration
的初始化是线程安全的.
companion object
在一个类内部进行 object declaration
的话, 它可以用 companion
关键字来标记.
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
然后可以这样子简化调用:
val instance = MyClass.create()
声明时, companion object
的名称可以省略:
class MyClass {
companion object {
}
}
val x = MyClass.Companion
属性和字段
声明属性
var
的表示是可变的(mutable)
val
的表示是只读的(read-only)
setter 和 getter
完整的语法为
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}
接口 interface
与 Java 8 非常相似.
它可以包含抽象方法, 也可以有方法实现. 使用 interface
关键字来定义:
interface MyInterface {
fun bar()
fun foo() {
// optional body
}
}
实现接口
class Child : MyInterface {
override fun bar() {
// body
}
}
接口中的属性
可以为抽象的, 也可以提供访问器实现.
interface MyInterface {
val prop: Int // abstract
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
}
重写时的冲突解决
参考上面说的.
访问修饰符
getter
总是与属性的访问性是一致的!一共有4种:
private
: 仅声明的地方可以访问.protected
: 在顶层的声明中不可用.internal
: 在相同的module
中可见.public
(默认就是public
) : 所有代码都可以访问.
module
一个 module
表示是一个 kotlin 文件编译在一起的集合:
- 一个 intellij 的 moduel
- 一个 maven 项目
- 一个 gradle 文件源的集合
- 一个 ant task 编译的集合
extension 扩展
extension function
为了声明一个扩展函数, 我们需要有一个接收者类型的前缀, 例如
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
this
关键字对应于接收者的对象. 现在, 我们可以调用在任意的MutableList<Int>
中调用这个方法了:
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l'
当然, 也可以是泛型版本:
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
extension 是静态解析的
扩展函数实际上并不修改它们扩展的 class 的. 通过定义一个 extension, 你并没有插入新的成员到该 class 中, 但仅是在该类型的变量中通过 .
来调用这个方法而已.
我们想强调的是, extension function 是通过静态分发的.
注意, 如果 class 中有一个成员函数, 以及一个 extension 函数, 它们的方法签名一样的话,则 member always wins!
例如
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
它总是会输出 member
.
然而, 可以通过 extension 来重载 (overload) 成员函数(即相同的方法名, 但签名不一样)
nullable 接收者
你可以在方法内部检查是否为 null
例如:
fun Any?.toString(): String {
if (this == null) return "null"
// after the null check, 'this' is autocast to a non-null type, so the toString() below
// resolves to the member function of the Any class
return toString()
}
extension 属性
类似函数, kotlin 支持扩展属性:
val <T> List<T>.lastIndex: Int
get() = size - 1
Companion Object Extensions
如果一个 class 有 companion object
, 你也可以为它定义一个扩展函数和扩展属性:
class MyClass {
companion object { } // will be called "Companion"
}
fun MyClass.Companion.foo() {
// ...
}
调用
MyClass.foo()
定义 extension 作为成员
class D {
fun bar() { ... }
}
class C {
fun baz() { ... }
fun D.foo() {
bar() // calls D.bar
baz() // calls C.baz
}
fun caller(d: D) {
d.foo() // call the extension function
}
}
Data classes
我们经常会创建一些主要用来持有数据的 class . 这在 Kotlin 中称为 data
data class User(val name: String, val age: Int)
编译器会自动为声明在主构造器(primary constructor) 中的属性创建以下
equals() / hashCode()
方法对toString()
, 形式为User(name=John, age=42)
componentN()
函数copy()
函数
前提条件(要全满足)
- 主构造器至少要有一个参数
- 所以主构造器的参数需要标记为
val
或var
- data class 不能为
abstract
,open
,sealed
或inner
的. - (1.1之前), data class 仅实现接口
生成代码的规则:
- 如果显式实现了 equals()
, hashCode()
或 toString()
, 或父类的这些实现是 final
的话, 则不会生成这些代码了, 它会用现成的代码
- 如果父类有 componentN()
函数, 并且为 open
以及返回兼容的类型, 则会生成的代码会重写父类的. 如果不能重写, 则会报错.
- 从一个有 copy()
函数的类中派生的话, 在 1.2 中是标记为 deprecated
的, 并且 在 1.3 中是禁止的.
- 不允许显式实现 componentN()
和 copy()
函数
在 body 里声明属性
注意: 只有在主构造器的参数的, 才会在 toString()
, equals()
和 hashCode()
, copy()
中使用到. 如果是在 body 中声明的, 则不会使用.
data class Person(val name: String) {
var age: Int = 0
}
copying
有时, 我们经常需要 copy 一个对象, 但仅修改某些属性, 又不想影响原有对象. copy()
方法就是为它使用的.
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
Sealed Classes
sealed classes
是用来表示有限的 class 继承的, 它的值仅可以为一些有限集合之中的一种类型, 并且不能为其他类型.
在某种意义上说, 它是一个扩展的枚举类, 值的集合是限定为枚举类, 但每个枚举常量仅存在一个单例, 而 sealed class
的子类则可以有多个实例状态.
声明一个 sealed class
sealed class Expr
sealed class
可以有子类, 但所有这些子类, 必须在声明 sealed class
的文件内!
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
sealed class
不允许有非 private 的构造器(默认就是 private
的)
sealed class
主要用在 when
表达式~
generic 泛型
class Box<T>(t: T) {
var value = t
}
如果参数是可以被解析的话, 则可以忽略类型:
val box = Box(1) // 1 has type Int, so the compiler figures out that we are talking about Box<Int>
嵌套类和内部类
嵌套类:
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
内部类
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
匿名内部类
window.addMouseListener(object: MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
}
})
枚举类
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
}
object expression 和 declaration
有时, 我们需要创建一个对象来小小修改一些 class 的, 不用为它显式声明一个新的子类. 在Java中, 处理这种情况的, 是用匿名内部类. Kotlin 则是用 object expression
和 object declaration
为现有的类型创建一个匿名类型, 可以这样子:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
}
})
如果父类是多种类型的话, 则用逗号隔开:
open class A(x: Int) {
public open val y: Int = x
}
interface B {...}
val ab: A = object : A(1), B {
override val y = 15
}
但我们仅需要一个对象的话, 则我们可以简单地:
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
object declaration , 参考前面.
Delegation 委托
delegation
模式提供了一个比较好的修改实现的方式. Kotlin
则原生支持它. 一个 Derived
类, 可以继承一个接口 Base
并且委托它所有的 public
方法到一个指定的对象:
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() // prints 10
}
by
子句, 在 Derived
class 中, 指示 b
将被内部保存在 Derived
中, 并且编译器将生成所有 Base
类的方法到 b
注意, override
: 编译器会使用你的 override
的实现来委托.
Delegated Properties
有一些公共的属性, 尽管我们可以手工多次实现它, 但更好的是实现它一次, 然后放到一个库中:
class Example {
var p: String by Delegate()
}
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.")
}
}
标准的 delegates
Lazy
val lazyValue: String by lazy { println("computed!") "Hello" } fun main(args: Array<String>) { println(lazyValue) println(lazyValue) }
Observable
import kotlin.properties.Delegates class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } fun main(args: Array<String>) { val user = User() user.name = "first" user.name = "second" }
Storing Properties in a Map
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
higher-order function 和 lambda
higher-order function
是这样一种函数: 它可以作为参数或函数返回值. 例如
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
it
: 隐式的单一的参数名字
如果 lambda 只有一个参数时, 可以这样子简写(并且不需要 ->
):
ints.map { it * 2 }
_
: 忽略参数:(1.1 版本开始)
map.forEach { _, value -> println("$value!") }
inline function
它一般可增强性能.
lambda expression 和 匿名函数
它们都是 function iteral
max(strings, { a, b -> a.length < b.length })
匿名函数
fun(x: Int, y: Int): Int = x + y
fun(x: Int, y: Int): Int {
return x + y
}
ints.filter(fun(item) = item > 0)
closures
一个 lambda expression
或 anonymous function
可以访问它的 closure
(例如, 一个声明在外部的变量) 不同于Java, 捕获的变量可以被修改(Java中, 只能访问 final
的变量)
function literal 与 receiver
Kotlin 允许为它提供一个接收者:
sum : Int.(other: Int) -> Int
然后可以这样子调用
1.sum(2)
inline function
使用 higher-order function
都会加重运行时的负担: 每一个函数都是一个 object
, 并且它会捕获一个 closure
, 例如, 可访问的函数体的变量. 内存分配以及虚拟调用都会增加 runtime 的负载.
但多数情况下, 可以通过 inline lambda expression
来减少这种负载.
例如:
lock(l) { foo() }
开启 inline
inline fun <T> lock(lock: Lock, body: () -> T): T {
// ...
}
noinline
如果你只想指定的某些参数不进行 inline:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ...
}
inline properties
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ...
inline set(v) { ... }
destructuring declaration
val (name, age) = person
以上就是析构的声明语法. 它由编译器自动生成以下代码
val name = person.component1()
val age = person.component2()
也可以在 for
循环里使用
for ((a, b) in collection) { ... }
从一个函数中返回2个值
data class Result(val result: Int, val status: Status)
fun function(...): Result {
// computations
return Result(result, status)
}
// Now, to use this function:
val (result, status) = function(...)
在 map 中使用
for ((key, value) in map) {
// do something with the key and the value
}
在 lambda 中使用
map.mapValues { entry -> "${entry.value}!" }
map.mapValues { (key, value) -> "$value!" }
Collections
Kotlin 是会区分 mutable
和 immutable
的集合的.
区分一个 mutable 的 read-only
视图, 和一个真正 immutable
的集合是非常重要的.
val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
val readOnlyView: List<Int> = numbers
println(numbers) // prints "[1, 2, 3]"
numbers.add(4)
println(readOnlyView) // prints "[1, 2, 3, 4]"
readOnlyView.clear() // -> does not compile
val strings = hashSetOf("a", "b", "c", "c")
assert(strings.size == 3)
this expression
- 在一个 class 成员内,
this
引用的是当前class 的 object 对象 - 在一个
expression function
或function literal with receiver
中,this
表示receiver
参数(即.
左边类型的对象)
如果 this
没有限定符, 则它引用的是 内部最近的作用域
. 为了使 this
引用其他作用域的, 可以使用 label qualifiers
例如
class A { // implicit label @A
inner class B { // implicit label @B
fun Int.foo() { // implicit label @foo
val a = this@A // A's this
val b = this@B // B's this
val c = this // foo()'s receiver, an Int
val c1 = this@foo // foo()'s receiver, an Int
val funLit = lambda@ fun String.() {
val d = this // funLit's receiver
}
val funLit2 = { s: String ->
// foo()'s receiver, since enclosing lambda expression
// doesn't have any receiver
val d1 = this
}
}
}
}
equality 相等
equals()
: structural equality (a == b, 它会被翻译为a?.equals(b) ?: (b === null)
)- 引用相等: 使用
===
或!==
来判断
操作符重载
参考 doc
annotation
声明注解
annotation class Fancy
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy
使用
@Fancy class Foo {
@Fancy fun baz(@Fancy foo: Int): Int {
return (@Fancy 1)
}
}
Type aliases 类型别名
typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>