基本语法
1.1 使用字符串模板
fun main(args: Array<String>) {
var a = 1
// 使用变量名作为模板:
val s1 = "a is $a"
a = 2
// 使用表达式作为模板:
val s2 = "${s1.replace("is", "was")}, but now is $a"
println(s2)
}
1.2 使用可空变量以及空值检查
下面的函数时当str中不包含整数时返回空:
fun parseInt(str : String): Int?{
//...
}
1.3 对一个对象实例调用多个方法
class Turtle {
fun penDown()
fun penUp()
fun turn(degrees: Double)
fun forward(pixels: Double)
}
val myTurtle = Turtle()
with(myTurtle) { // 画一个 100 像素的正方形
penDown()
for(i in 1..4) {
forward(100.0)
turn(90.0)
}
penUp()
}
1.4 When表达式
//dwhen 取代了类 C 语言的 switch 操作符。其最简单的形式如下:
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 注意这个块
print("x is neither 1 nor 2")
}
}
when 是可以取代if-else链的
1.5 For循环
for (item: Int in ints) {
// ……
}
1.6 返回与跳转
在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@、fooBar@都是有效的标签(参见语法)。 要为一个表达式加标签,我们只要在其前加标签即可。
fun foo() {
ints.forEach {
if (it == 0) return@forEach
print(it)
}
}
fun foo() {
ints.forEach(fun(value: Int) {
if (value == 0) return // local return to the caller of the anonymous fun, i.e. the forEach loop
print(value)
})
}
类与对象
类与继承
构造函数
主构造函数是类头的一部分:它跟在类名后
class Person constructor(firstName: String) {
}
//主构造函数中没有任何注解或者可见性修饰符,可以省略
class Person(firstName: String) {
}
初始化可以放到以init关键字为前缀的初始化块中
class Customer(name: String) {
init {
logger.info("Customer initialized with value ${name}")
}
}
主构造函数中声明的属性可以是可变的
(var)
或只读的(val)
如果这个构造函数有注解或可进行修饰符,这个constructor关键字是必须的,并且这些修饰符在他前面。
class Customer public @Inject constructor(name: String) { …… }
次构造函数
每个次构造函数需要委托给主构造函数,可以用this
来委托
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
拥有同一个私有的构造函数,需要声明体格带有非默认可见性的主构造函数
class DontCreateMe private constructor () {
}
继承
如果类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
覆盖方法
如果没写,编译器将会报错。 如果函数没有标注 open 如 Base.nv(),则子类中不允许定义相同签名的函数, 不论加不加 override。在一个 final 类中(没有用 open 标注的类),开放成员是禁止的。
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}
//如果想在此禁止覆盖,可以这样写
open class AnotherDerived() : Base() {
final override fun v() {}
}
覆盖属性
在超类中声明然后在派生类中重新声明的属性必须以 override 开头,并且它们必须具有兼容的类型。
open class Foo {
open val x: Int get() { …… }
}
class Bar1 : Foo() {
override val x: Int = ……
}
super@Outer
:在一个类中访问外部类的超类
super<Base>
为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super
抽象类
类和其中的某些成员可以声明为 abstract。 抽象成员在本类中可以不用实现。 需要注意的是,我们并不需要用 open 标注一个抽象类或者函数
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
属性与字段
class Address {
var name: String = ……
var street: String = ……
var city: String = ……
var state: String? = ……
var zip: String = ……
}
fun copyAddress(address: Address): Address {
val result = Address() // Kotlin 中没有“new”关键字
result.name = address.name // 将调用访问器
result.street = address.street
// ……
return result
}
一个只读属性的语法和一个可变的属性的语法有两方面的不同:1、只读属性的用 val开始代替var 2、只读属性不允许 setter
//自定义setter的例子
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 解析字符串并赋值给其他属性
}
幕后字段
使用
field
标识符只能用在属性的访问其内
var counter = 0 // 此初始器值直接写入到幕后字段
set(value) {
if (value >= 0)
field = value
}
接口
使用关键字 interface 来定义接口,可以实现一个或多个接口
interface MyInterface {
fun bar()
fun foo() {
// 可选的方法体
}
}
class Child : MyInterface {
override fun bar() {
// 方法体
}
}
接口中的属性
在接口中声明的属性要么是抽象的,要么提供访问器的实现。在接口中声明的属性不能有幕后字段(backing field)
,因此接口中声明的访问器不能引用它们。
interface MyInterface {
val prop: Int // 抽象的
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
}
解决覆盖属性使用super<A>
可见性修饰符
- 如果你不指定任何可见性修饰符,默认为
public
,这意味着你的声明将随处可见; - 如果你声明为
private
,它只会在声明它的文件内可见; - 如果你声明为
internal
,它会在相同模块内随处可见; protected
不适用于顶层声明。
扩展
声明一个扩展函数,需要用一个接收者类型也就是扩展的类型来作为他的前缀
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
如果一个类定义有一个成员函数和一个扩展函数,而这两个函数又有相同的接收者类型、相同的名字并且都适用给定的参数,这种情况总是取成员函数。
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
//它将输出“member”
扩展函数重载同样名字但不同签名成员函数也完全可以
class C {
fun foo() { println("member") }
}
fun C.foo(i: Int) { println("extension") }
//将输出 "extension"
可空接收者
fun Any?.toString(): String {
if (this == null) return "null"
// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
// 解析为 Any 类的成员函数
return toString()
}
扩展属性不能有初始化器
扩展是静态解析的
我们想强调的是扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
//输出“c”
扩展函数使用
open class D {
}
class D1 : D() {
}
open class C {
open fun D.foo() {
println("D.foo in C")
}
open fun D1.foo() {
println("D1.foo in C")
}
fun caller(d: D) {
d.foo() // 调用扩展函数
}
}
class C1 : C() {
override fun D.foo() {
println("D.foo in C1")
}
override fun D1.foo() {
println("D1.foo in C1")
}
}
//C().caller(D()) // 输出 "D.foo in C"
//C1().caller(D()) // 输出 "D.foo in C1" —— 分发接收者虚拟解析
//C().caller(D1()) // 输出 "D.foo in C" —— 扩展接收者静态解析
数据类
属性导出一下成员:
- 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 jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
密封类
- 一个密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员。
- 密封类不允许有非-private 构造函数(其构造函数默认为 private)。
- 请注意,扩展密封类子类的类(间接继承者)可以放在任何位置,而无需在同一个文件中。
要声明一个密封类,需要在类名前面添加 sealed 修饰符。虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明。
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}
泛型
型变
kotlin中没有通配符类型,只有两个东西:声明处型变于类型投影
声明出类型
在Kotlin中,有一种方法想编译器解释这种情况。这称为声明处型变;我们可以标注 Source 的类型参数 T 来确保它仅从 Source 成员中返回(生产),并从不被消费。 为此,我们提供 out 修饰符:
abstract class Source<out T> {
abstract fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // 这个没问题,因为 T 是一个 out-参数
// ……
}
Kotlin 又补充了一个型变注释: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!
}
类型投影:我们说from不仅仅是一个数组,而是一个受限制的(投影的)数组:我们只可以调用返回类型为类型参数 T 的方法,如上,这意味着我们只能调用 get()。
嵌套类与内部类
类可以标记位inner
以便可以访问外部类的成员。内部类会带有一个对外部类的对象的引用:
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
匿名内部类
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)
}
伴生对象
类内部的对象声明可以用 companion
关键字标记:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口:
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
委托
类 Derived
可以继承一个接口 Base,并将其所有共有的方法委托给一个指定的对象:
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
}
Derived的超类型列表中的by-子句表示b将会在Derived中内部存储。并且编译器将转发给b的所有Base方法