函数定义
函数定义使用关键字 fun,参数格式为:参数 : 类型
1 | fun sum(a: Int, b: Int): Int { // Int 参数,返回值 Int |
可变长参数函数
用 vararg
关键字进行标识
定义常量与变量
可变变量定义:var
关键字
1 | var <标识符> : <类型> = <初始化值> |
不可变变量定义:val
关键字,只能赋值一次的变量(类似Java中final修饰的变量)
1 | val <标识符> : <类型> = <初始化值> |
编译器支持自动类型判断,即声明时可以不指定类型,由编译器判断。
1 | val a: Int = 1 |
字符串
$ 表示一个变量名或者变量值
$varName 表示变量值
${varName.fun()} 表示变量的方法返回值:
1 | fun strFun(){ |
Kotlin 支持三个引号 “”” 扩起来的字符串,支持多行字符串
1 | fun multilineText() { |
NULL机制检查
Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式,
- 字段后加
!!
像Java一样抛出空异常, - 字段后加
?
可不做处理返回值为 null或配合?:
做空判断处理
1 | //类型后面加?表示可为空 |
类型检测及自动类型转换
用is
运算符检测一个表达式是否某类型的一个实例(类似于Java中的instanceof
关键字)。
1 | fun getStringLength(obj: Any): Int? { |
或者
1 | fun getStringLength(obj: Any): Int? { |
甚至还可以
1 | fun getStringLength(obj: Any): Int? { |
区间
区间表达式由具有操作符形式 ..
的 rangeTo 函数辅以 in
和!in
形成。
区间是为任何可比较类型定义的,但对于整型原生类型,它有一个优化的实现。以下是使用区间的一些示例:
1 | for (i in 1..4) print(i) // 输出“1234” |
基本数据类型
类型 | 位宽度 |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
字面常量
下面是所有类型的字面常量:
- 十进制:123
- 长整型以大写的 L 结尾:123L
- 16 进制以 0x 开头:0x0F
- 2 进制以 0b 开头:0b00001011
- 注意:8进制不支持
Kotlin 同时也支持传统符号表示的浮点数值:
- Doubles 默认写法:
123.5
,123.5e10
- Floats 使用 f 或者 F 后缀:
123.5f
你可以使用下划线使数字常量更易读:
1 | val oneMillion = 1_000_000 |
比较两个数字
Kotlin 中没有基础数据类型,只有封装的数字类型
三个等号 === 表示比较对象地址,两个 == 表示比较两个值大小
位操作符
Kotlin的位操作符只可用在Int和Long类型
1 | shl(bits) – 左移位 (Java’s <<) |
补充说明:负数的右移操作
1 | fun testByteOperation() { |
负数在计算机中是以补码的形式存储的,即:负数的绝对值取反码后加1。
在Kotlin中int
类型位4位,即32字节,所以|b|
即1的反码为
加1后为
即-1
在计算机中的存储方式(补码)为如下
无符号右移(ushr)
即该32位都为数值(最高位不是符号位),右移后高位补0。
所以无符号右移1位:变成如下图所示:
其值为:2^31 - 1=2147483647
带符号右移(shr)
即最高位(第31位,位数从0开始)为符号位,不参与位移运算。
右移后高位补与符号位相同的值,即负数补1,正数补0。
所以-1带符号右移1位可以拆分位两步:
- 除符号位右移一位
- 空缺的高位(第30位)补1
结果值还是-1。
位操作符应用
- 判断Int型变量a是奇数还是偶数
1 | a1 and 1 = 0 // 偶数 |
- 获取Int型变量的第K位(注:K从0开始依次由右往左,以下揭同)
1 | a1 shr k and 1 |
- 将Int型变量的第K位清0
1 | a1 and ((1 shl k).inv()) |
- 将Int型变量的第K位置1
1 | a1 or (1 shl k) |
- 平均值(整数)
1 | (a1 and b1)+((a1 xor b1) shr 1) |
- 不用temp交换两个整数
1 | a1 = a1 xor b1 |
- 获取绝对值
1 | val temp = c1 shr 31 |
- 获取相反数(正>负,负>正)
1 | c1.inv()+1 |
- Int转byte数组
1 | val bytes = ByteArray(4) |
数组
数组的创建两种方式:一种是使用函数arrayOf();另外一种是使用工厂函数。
1 | fun main(args: Array<String>) { |
第一类
- arrayOf(vararg elements: T)
- doubleArrayOf(vararg elements: Double)
- floatArrayOf(vararg elements: Float)
- longArrayOf(vararg elements: Long)
- intArrayOf(vararg elements: Int)
- charArrayOf(vararg elements: Char)
- shortArrayOf(vararg elements: Short)
- byteArrayOf(vararg elements: Byte)
- booleanArrayOf(vararg elements: Boolean)
第二类
Array
ByteArray
CharArray
ShortArray
IntArray
LongArray
FloatArray
DoubleArray
BooleanArray
条件控制
If-else表达式
作为表达式
1 | val max = if (a > b) a else b |
把 IF 表达式的结果赋值给一个变量
1 | val max = if (a > b) { |
使用 in 运算符来检测某个数字是否在指定区间内,区间格式为 x..y :
1 | fun testIfIn(x: Int) { |
When 表达式
等同于Java中的switch
其中的else
相当于switch
中的default
1 | fun testIfWhen(x: Int) { |
多分支相同的方式处理
- 把多个分支条件放在一起,用逗号分隔
1 | fun testIfWhenSame(x: Int) { |
- 在(in)或者不在(!in)一个区间或者集合中
注:不同分支的区间如果有重叠部分,按照分支的先后顺序进入分支后就结束,不会继续后续的符合的分支。
如下x=4
时,只会进入1..4
分支。
1 | fun testIfWhenIn(x: Int) { |
配合is字段判断特定类型
检测一个值是(is)或者不是(!is)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法和属性而无需 任何额外的检测。
1 | /** |
不提供参数
不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支。等同于 if-else if
链。
1 | fun testIfWhenNone(x: Int) { |
循环控制
For 循环
or 循环可以对任何提供迭代器(iterator)的对象进行遍历,语法如下:
1 | for (item in collection) print(item) |
获取索引
1 | val arr = arrayOf(1, 2, 3, 4) |
while 与 do…while 循环
与Java中的一致
1 | while( 布尔表达式 ) { |
返回和跳转
Kotlin 有三种结构化跳转表达式:
- return。默认从最直接包围它的函数或者匿名函数返回。
- break。终止最直接包围它的循环。
- continue。继续下一次最直接包围它的循环。
Break 、Continue标签
在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@、fooBar@都是有效的标签。 要为一个表达式加标签,我们只要在其前加标签即可。一般用于多层循环中。
1 | outter@ for (i in 1..4) { |
标签处返回
Kotlin 有函数字面量、局部函数和对象表达式。因此 Kotlin 的函数可以被嵌套。 标签限制的 return 允许我们从外层函数返回。 最重要的一个用途就是从 lambda 表达式中返回
1 | //从最直接包围它的函数即 foo 中返回,15 |
类和对象
类可以有一个 主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后
1 | class Person constructor(firstName: String) {} |
如果主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字可以省略。
1 | class Person(firstName: String) { |
getter 和 setter
属性声明的完整语法:
1 | var <propertyName>[: <PropertyType>] [= <property_initializer>] |
getter 和 setter 都是可选
如果属性类型可以从初始化语句或者类的成员函数中推断出来,那就可以省去类型,val不允许设置setter函数,因为它是只读的。
1 | var allByDefault: Int? // 错误: 需要一个初始化语句, 默认实现了 getter 和 setter 方法 |
示例
1 |
|
非空属性必须在定义的时候初始化,kotlin提供了一种可以延迟初始化的方案,使用 lateinit 关键字描述属性
主构造器
主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀。
1 | class Person constructor(firstName: String) { |
注意:主构造器的参数可以在初始化代码段中使用,也可以在类主体n定义的属性初始化代码中使用。 一种简洁语法,可以通过主构造器来定义属性并初始化属性值(可以是var或val):
1 | class People(val firstName: String, val lastName: String) { |
如果构造器有注解,或者有可见度修饰符,这时constructor关键字是必须的,注解和修饰符要放在它之前。
1 | class Person(val name: String, age: Int, weight: Int) { |
次构造函数
类也可以有二级构造函数,需要加前缀 constructor:
如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this 关键字
如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数是 public 。如果你不想你的类有公共的构造函数,你就得声明一个空的主构造函数:
1 | class DontCreateMe private constructor () { |
注意:在 JVM 虚拟机中,如果主构造函数的所有参数都有默认值,编译器会生成一个附加的无参的构造函数,这个构造函数会直接使用默认值。这使得 Kotlin 可以更简单的使用像 Jackson 或者 JPA 这样使用无参构造函数来创建类实例的库。
1
2 > class Customer(val customerName: String = "")
>
1 | class Person(val name: String, age: Int, weight: Int) { |
嵌套类
我们可以把类嵌套在其他类中,看以下实例:
1 | class Outer { // 外部类 |
内部类
内部类使用 inner 关键字来表示。
内部类会带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。
1 | class Outer { |
匿名内部类
使用对象表达式来创建匿名内部类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class Test {
var v = "成员属性"
fun setInterFace(test: TestInterFace) {
test.test()
}
}
/**
* 定义接口
*/
interface TestInterFace {
fun test()
}
fun main(args: Array<String>) {
var test = Test()
/**
* 采用对象表达式来创建接口对象,即匿名内部类的实例。
* 这里的参数名称必须为'object'
*/
test.setInterFace(object : TestInterFace {
override fun test() {
println("对象表达式创建匿名内部类的实例")
}
})
}
类的修饰符
类的修饰符包括 classModifier 和accessModifier:
classModifier: 类属性修饰符,标示类本身特性。
1
2
3
4
5abstract // 抽象类
final // 类不可继承,默认属性
enum // 枚举类
open // 类可继承,类默认是final的
annotation // 注解类accessModifier: 访问权限修饰符
1
2
3
4private // 仅在同一个文件中可见
protected // 同一个文件中或子类可见
public // 所有调用的地方都可见
internal // 同一个模块中可见
示例
1 | // 文件名:example.kt |
继承
Kotlin 中所有类都继承该 Any 类,它是所有类的超类,对于没有超类型声明的类是默认超类:
1 | class Example // 从 Any 隐式继承 |
Any 默认提供了三个函数:
1 | equals() |
注意:Any 不是 java.lang.Object。
如果一个类要被继承,可以使用 open 关键字进行修饰。
1 | open class Base(p: Int) // 定义基类 |
构造函数
子类有主构造函数
如果子类有主构造函数, 则基类必须在主构造函数中立即初始化。
1 | open class Person(var name : String, var age : Int){// 基类 |
子类没有主构造函数
如果子类没有主构造函数,则必须在每一个二级构造函数中用 super 关键字初始化基类,或者在代理另一个构造函数。初始化基类时,可以调用基类的不同构造方法。
1 | open class Person(var name: String, var age: Int) { |
重写
在基类中,使用fun声明函数时,此函数默认为final修饰,不能被子类重写。如果允许子类重写该函数,那么就要手动添加 open 修饰它, 子类重写方法使用 override 关键词
如果有多个相同的方法(继承或者实现自其他类,如A、B类),则必须要重写该方法,使用super范型去选择性地调用父类的实现。
1 | open class A { |
属性重写
属性重写使用 override 关键字,属性必须具有兼容类型,每一个声明的属性都可以通过初始化程序或者getter方法被重写
1 | open class Foo { |
你可以用一个var属性重写一个val属性,但是反过来不行(属性扩大)。因为val属性本身定义了getter方法,重写为var属性会在衍生类中额外声明一个setter方法
你可以在主构造函数中使用 override 关键字作为属性声明的一部分:
1 | interface Foo { |
接口
Kotlin 接口与 Java 8 类似,使用 interface 关键字定义接口,允许方法有默认实现:
1 | interface MyInterface { |
实现接口时,必须重写的几种类别:
抽象方法、抽象成员变量
多个接口时,遇到同一方法继承多个实现
实现接口
一个类或者对象可以实现一个或多个接口,多个接口时用,
分隔。
1 | class Child : MyInterface, OtherInterface { |
接口中的属性
接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值,实现接口时,必须重写属性:
1 | interface MyInterface{ |
扩展
Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用 Decorator 模式。
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。
扩展函数
扩展函数可以在已有类(系统类、自己写的类)中添加新的方法,不会对原类做修改,扩展函数定义形式:
1 | fun receiverType.functionName(params){ |
- receiverType:表示函数的接收者,也就是函数扩展的对象
- functionName:扩展函数的名称
- params:扩展函数的参数,可以为NULL
示例:
1 | class User(var name:String) |
扩展函数是静态解析的
扩展函数是静态解析的,并不是接收者类型的虚拟成员,在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的:
1 | open class J |
若扩展函数和成员函数一致,则使用该函数时,只会使用成员函数。
1 | class P { |
扩展一个空对象
在扩展函数内, 可以通过 this 来判断接收者是否为 NULL,这样,即使接收者为 NULL,也可以调用扩展函数。例如:
1 | fun Any?.toString(): String { |
扩展的作用域
通常扩展函数或属性定义在顶级包下:
1 | package foo.bar |
要使用所定义包之外的一个扩展, 通过import导入被扩展的类和扩展的函数名进行使用:
1 | package com.example.usage |
数据类
Kotlin 可以创建一个只包含数据的类,关键字为 data:
1 | data class User(val name: String, val age: Int) |
编译器会自动的从主构造函数中根据所有声明的属性提取以下函数:
equals()
/hashCode()
toString()
格式如"User(name=John, age=42)"
componentN() functions
对应于属性,按声明顺序排列copy()
函数
如果这些函数在类中已经被明确定义了,或者从超类中继承而来,就不再会生成。
为了保证生成代码的一致性以及有意义,数据类需要满足以下条件:
- 主构造函数至少包含一个参数。
- 所有的主构造函数的参数必须标识为
val
或者var
; - 数据类不可以声明为
abstract
,open
,sealed
或者inner
; - 数据类不能继承其他类 (但是可以实现接口)。
复制
复制使用 copy() 函数,我们可以使用该函数复制对象并修改部分属性, 对于上文的 User 类,其实现会类似下面这样:
1 | fun copy(name: String = this.name, age: Int = this.age) = User(name, age) |
数据类以及解构声明
组件函数允许数据类在解构声明中使用:
1 | data class Chinese(val name: String, val age: Int) |
密封类
密封类用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合 也是受限的,但每个枚举常量只存在一个实例,而密封类 的一个子类可以有可包含状态的多个实例。
声明一个密封类,使用 sealed 修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中。
sealed 不能修饰 interface ,abstract class(会报 warning,但是不会出现编译错误)