基本数据类型
数字
| Type | Bit Width |
|---|---|
| Double | 64 |
| Float | 32 |
| Long | 32 |
| Int | 32 |
| Short | 16 |
| Byte | 8 |
字面常量
- 十进制:123
- Long类型用大写L 标记:123L
- 十六进制:0x0F
- 二进制:0b0001011
- 默认Double:123.5、123.5e10
- Float 用f或者F标记:123.5f
数字字面值中的下划线(自1.1起)
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
注意:
- 对于数字没有隐式拓展转换(如
Java中的int可以隐式转换为long) kotlin不支持八进制Kotlin中字符不是数字
字符
字符用 Char 类型表示。它们不能直接当作数字
fun check(c: Char) {
if (c == 1) { // 错误:类型不兼容
// ……
}
}
每种数字类型都支持如下转化
toByte(): BytetoShort(): ShorttoInt(): InttoLong(): LongtoFloat(): FloattoDouble(): DoubletoChar(): Char
布尔
布尔用 Boolean类型表示,它有两个值:true 与false。
若需要可空引用布尔会被装箱。
内置的布尔运算有:
- || – 短路逻辑或
- && – 短路逻辑与
- ! - 逻辑非
数组
控制流
if表达式
在 Kotlin 中,if是一个表达式,即它会返回一个值。 因此就不需要三元运算符(条件 ? 然后 : 否则),因为普通的 if 就能胜任这个角色。
// 传统用法
var max = a
if (a < b) max = b
// With else
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// 作为表达式
val max = if (a > b) a else b
if的分支可以是代码块,最后的表达式作为该块的值:
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
如果你使用 if 作为表达式而不是语句(例如:返回它的值或者把它赋给变量),该表达式需要有else 分支。
when
when取代了java switch 操作符。其最简单的形式如下
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 注意这个块
print("x is neither 1 nor 2")
}
}
与if作为表达式一样, when既可以被当做表达式使用也可以被当做语句使用,如果when 作为一个表达式使用,必须有else分支, 除非编译器能够检测出所有的可能情况都已经覆盖了。
while 循环
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // y 在此处可见
循环中的 Break与 continue
return默认从最直接包围它的函数或者匿名函数返回。break终止最直接包围它的循环。continue继续下一次最直接包围它的循环。
函数
函数的声明
Kotlin 中的函数使用 fun 关键字声明:
fun double(x: Int): Int {
return 2 * x
}
定义函数
- 带有两个 Int 参数、返回 Int 的函数:
fun sum(a: Int, b: Int): Int {
return a + b
}
- 将表达式作为函数体、返回值类型自动推断的函数:
fun sum(a: Int, b: Int) = a + b
- 函数返回无意义的值,
Unit是一种只有一个值——Unit的类型。这个值不需要显式返回:
fun printSum(a: Int, b: Int): Unit {
println("sum of $a and $b is ${a + b}")
}
注意:
- 当一个函数有大量的参数或默认参数时,可以通过命名参数来调用函数;
- 当一个函数调用混用位置参数与命名参数时,所有位置参数都要放在第一个命名参数之前
- 在调用
Java函数时不能使用命名参数语法,因为Java字节码并不总是保留函数参数的名称;
函数的作用域
在 Kotlin 中函数可以在文件顶层声明,不需要像Java一样创建一个类来保存一个函数。此外除了顶层函数,Kotlin 中函数也可以声明在局部作用域、作为成员函数以及扩展函数。
局部函数
Kotlin 支持局部函数,即一个函数在另一个函数内部:
fun count(){
var count =0;
fun innerCount(){
print(count)
}
}
局部函数可以访问外部函数(即闭包)的局部变量。
成员函数
成员函数是在类或对象内部定义的函数:
class Sample() {
fun foo() { print("Foo") }
}
成员函数以点表示法调用:
Sample().foo() // 创建类 Sample 实例并调用 foo
泛型函数
函数可以有泛型参数,通过在函数名前使用尖括号指定:
fun <T> singletonList(item: T): List<T> { …… }
内联函数
扩展函数
扩展一个类的新功能而无需继承该类或使用像装饰者这样的任何类型的设计模式。
声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。
高阶函数与Lambda表达式
高阶函数是将函数用作参数或返回值的函数。
函数类型
- 所有函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型:(A, B) -> C 表示接受类型分别为 A 与 B 两个参数并返回一个 C 类型值的函数类型。 参数类型列表可以为空,如 () -> A。Unit 返回类型不可省略。
- 函数类型可以有一个额外的接收者类型,它在表示法中的点之前指定: 类型 A.(B) -> C 表示可以在 A 的接收者对象上以一个 B 类型参数来调用并返回一个 C 类型值的函数。 带有接收者的函数字面值通常与这些类型一起使用。
- 挂起函数属于特殊种类的函数类型,它的表示法中有一个
suspend修饰符 ,例如suspend () -> Unit或者suspend A.(B) -> C(协程相关)。
函数类型表示法可以选择性地包含函数的参数名:
(x: Int, y: Int) -> Point。这些名称可用于表明参数的含义。
函数类型实例化
- 使用函数字面值的代码块,采用以下形式之一:
- lambda 表达式:
{ a, b -> a + b }, - 匿名函数:
fun(s: String): Int { return s.toIntOrNull() ?: 0 }
- lambda 表达式:
带有接收者的函数字面值可用作带有接收者的函数类型的值
- 使用已有声明的可调用引用:
- 顶层、局部、成员、扩展函数:
::isOdd、 String::toInt, - 顶层、成员、扩展属性:
List<Int>::size, - 构造函数:
::Regex
- 顶层、局部、成员、扩展函数:
这包括指向特定实例成员的绑定的可调用引用:foo::toString
- 使用实现函数类型接口的自定义类的实例:
class IntTransformer: (Int) -> Int {
override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()
如果有足够信息,编译器可以推断变量的函数类型:
val a = { i: Int -> i + 1 }
带与不带接收者的函数类型非字面值可以互换,其中接收者可以替代第一个参数,反之亦然。例如,(A, B) -> C 类型的值可以传给或赋值给期待 A.(B) -> C 的地方,反之亦然:
val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
val twoParameters: (String, Int) -> String = repeatFun // OK
fun runTransformation(f: (String, Int) -> String): String {
return f("hello", 3)
}
val result = runTransformation(repeatFun) // OK
请注意,默认情况下推断出的是没有接收者的函数类型,即使变量是通过扩展函数引用来初始化的。 如需改变这点,请显式指定变量类型。
Lambda 表达式
lambda 表达式与匿名函数是“函数字面值”,即未声明的函数, 但立即做为表达式传递,考虑下面的例子:
max(strings, { a, b -> a.length < b.length })
函数 max 是一个高阶函数,它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,即函数字面值,它等价于以下命名函数:
fun compare(a: String, b: String): Boolean = a.length < b.length
Lambda 表达式语法
Lambda 表达式的完整语法形式如下:
val sum = { x: Int, y: Int -> x + y }
lambda表达式总是括在花括号中, 完整语法形式的参数声明放在花括号内,并有可选的类型标注, 函数体跟在一个->符号之后。如果推断出的该lambda的返回类型不是 Unit,那么该lambda 主体中的最后一个(或可能是单个)表达式会视为返回值。
如果我们把所有可选标注都留下,看起来如下:
val sum: (Int, Int) -> Int = { x, y -> x + y }
将 lambda 表达式传给最后一个参数:
在 Kotlin 中有一个约定:如果函数的最后一个参数接受函数,那么作为相应参数传入的 lambda 表达式可以放在圆括号之外:
val product = items.fold(1) { acc, e -> acc * e }
如果该lambda表达式是调用时唯一的参数,那么圆括号可以完全省略:
run { println("...") }
尾递归函数
函数可以有泛型参数,通过在函数名前使用尖括号指定:
fun <T> singletonList(item: T): List<T> { …… }
类与对象
类的成员
- 构造函数与初始化块
- 函数
- 属性
- 嵌套类与内部类
- 对象声明
构造函数
Kotlin 中使用关键字 class 声明类
class Invoice { ... }
在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数
属性与字段
- 属性可以用关键字
var声明为可变的,否则使用只读关键字val,- 只读属性的用
val开始代替var - 只读属性不允许
setter。要使用一个属性,只要用名称引用它即可
- 只读属性的用
- 声明一个属性的完整语法:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
getter总是与属性有着相同的可见性,setter可见权限不能高于属性。private类属性默认不生成getter / setter,对它的访问都是直接访问,一旦拥有自定义的getter / setter,访问时就要通过getter / setter了。(可以在生成的Java文件中查看)
幕后字段
什么是幕后字段?
在JVM类属性中存在一个与之相对应的字段。可以通过Android Studio的tools->kotlin-> Show Kotlin Bytecode -> Decompile 查看在JVM中生成的文件。
下面这种情况就不存在幕后字段
val isEmpty: Boolean
get() = this.size == 0
这种情况会被直接编译成,而不存在isEmpty这个字段:
public final boolean isEmpty() {
return this.size == 0;
}
存在幕后字段的情况:
-
如果属性至少一个访问器使用默认实现,或者自定义访问器通过 field 引用幕后字段,将会为该属性生成一个幕后字段。对于
private属性比较特殊,它不存在默认的getter/setter却有幕后字段。 -
在自定义
getter / setter中使用了field的属性,一定有幕后字段。这个field就是我们访问幕后字段的「关键字」,它与Lambda表达式中的it类似,并不是一个真正的关键字,只在特定的语句内有特殊的意思,在其他语句内都不是关键字。
幕后属性
幕后属性的用处
很多时候,我们希望定义这样的属性:
-
对外表现为
val属性,只能读不能写; -
在类内部表现为
var属性,也就是说只能在类内部改变它的值。
val size get() = _size
private var _size:Int = 0
对应的java代码
private int _size;
public final int getSize() {
return this._size;
}
这个_size属性就是幕后属性
编译器常量
已知值的属性可以使用 const 修饰符标记为 编译期常量。这些属性需要满足以下条件
- 位于顶层或者是
object 声明或companion object的一个成员 - 以
String或原生类型值初始化 - 没有自定义
getter
继承
在 Kotlin 中所有类都有一个共同的超类 Any,这对于没有超类型声明的类是默认超类:
内联类
嵌套类
密封类
数据类
对象表达式与对象声明
对象表达式
我们需要创建一个对某个类做了轻微改动的类的对象,而不用为之显式声明新的子类, Java 用匿名内部类 处理这种情况。
view.setOnClickListener(object :View.OnClickListener{
override fun onClick(v: View?) {
}
})
多个超类型可以由跟在冒号后面的逗号分隔的列表指定,如果我们只需要“一个对象而已”,并不需要特殊超类型,那么我们可以简单地写:
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是Any。在匿名对象中添加的成员将无法访问
class C {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 公有函数,所以其返回类型是 Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 没问题
val x2 = publicFoo().x // 错误:未能解析的引用“x”
}
}
就像 Java 匿名内部类一样,对象表达式中的代码可以访问来自包含它的作用域的变量。 (与 Java 不同的是,这不仅限于 final 变量。)
fun countClicks(window: JComponent) {
var clickCount = 0
var enterCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
override fun mouseEntered(e: MouseEvent) {
enterCount++
}
})
// ……
}
对象声明
单例模式在一些场景中很有用, 而 Kotlin(继 Scala 之后)使单例声明变得很容易:
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}
对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中
这称为对象声明。并且它总是在 object关键字后跟一个名称。
就像变量声明一样,对象声明不是一个表达式,不能用在赋值语句的右边。
对象声明的初始化过程是线程安全的。
如需引用该对象,我们直接使用其名称即可:
DataProviderManager.registerDataProvider(……)
这些对象可以有超类型:
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { …… }
override fun mouseEntered(e: MouseEvent) { …… }
}
伴生对象
类内部的对象声明可以用 companion 关键字标记:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
该伴生对象的成员可通过只使用类名作为限定符来调用:
val instance = MyClass.create()
可以省略伴生对象的名称,在这种情况下将使用名称 Companion:
class MyClass {
companion object { }
}
val x = MyClass.Companion
其自身所用的类的名称(不是另一个名称的限定符)可用作对该类的伴生对象 (无论是否命名)的引用:
class MyClass1 {
companion object Named { }
}
val x = MyClass1
class MyClass2 {
companion object { }
}
val y = MyClass2
请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口:
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
val f: Factory<MyClass> = MyClass
对象表达式与对象声明的语义差别
- 对象表达式是在使用他们的地方立即执行(及初始化)的;
- 对象声明是在第一次被访问到时延迟初始化的;
- 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。
接口
Kotlin 的接口与 Java 8类似,既包含抽象方法的声明,也包含实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。
interface MyInterface {
fun bar()
fun foo() {
// 可选的方法体
}
}
属性
在接口中声明的属性要么是抽象的,要么提供访问器的实现。在接口中声明的属性不能有幕后字段(backing field),因此接口中声明的访问器不能引用它们。通过我们上述对幕后字段的解释,对于var属性,必须自定义getter和setter 对于val属性自定义getter。
interface MyInterface {
val prop: Int
val propertyWithImplementation: String
get() = "foo"
var prop2:Int
var propWithGetAndSet:String
get() = "prop2"
set(value) {
print(value)
}
fun foo() {
print(prop)
}
}
接口的继承
与java类似 一个类或者对象可以实现一个或多个接口,与Java 不同的是 只需定义所缺少的实现。
interface MyInterface2:MyInterface{
fun pritlnProps(){
println("propertyWithImplementation = "+propertyWithImplementation)
println("propWithGetAndSet = "+propWithGetAndSet)
println("prop = "+prop)
println("prop2 = "+prop2)
}
}
class A :MyInterface,MyInterface2{
override val prop: Int = 1
override var prop2: Int = 2
}
解决覆盖冲突
与类继承的覆盖规则是一致的,即需要通过super关键字来指定从哪个超类型继承的实现。