Kotlin 快速入门
这篇文章是给有过 Java 或其他面向对象编程语言开发经验的朋友看的。省略了对很多面向对象编程的解释,希望大家能用最少的时间了解 Kotlin 的基础知识。你可以用 Intellij 、 Android Studio 或 Try Kotlin 运行文中的代码。
本文内容:
- 变量与常量
- 基本类型
- 值域
- 循环
- 异常处理
- 引用等价与结构等价
- 控制流
- when 表达式
- 类型层级
变量与常量
Kotlin 有两个声明变量和常量的数据类型,分别是 val 和 var。
var 变量能直接赋值,也能先声明后赋值:
// 无类型声明
var age = 12
// 显式声明变量类型
var name1: String = "老王"
// 重新赋值
var name2 = "kotlin"
val 常量相当于是 Java 中的 final 变量,必须在创建时初始化,且不能重新赋值:
val name = "kotlin"
// 编译时异常
name = "changed kotlin"
类型推断
在 Kotlin 中,声明变量时不需要声明具体类型,比如上面代码中的 age 变量。这种机制称为类型推断(type inference)。
基本类型
Kotlin 没有 int 和 Integer 等 Java 中的基本类型和包装类。但在 Kotlin 中有和 Java 包装类相似的装箱类(boxed type)。
数值类型
下面是 Kotlin 中内置的数值装箱类
类型 | 字节 |
---|---|
Long | 8 |
Int | 4 |
Short | 2 |
Byte | 1 |
Double | 8 |
Float | 4 |
创建数值常量:
val int = 123
val long = 123456L // 长整型结尾为 L
val double = 12.34
val float = 12.34F // 浮点型结尾为 F
val hexadecimal = 0xAB // 十六进制开头为 0x
val binary = 0b010101 // 二进制开头为 0b
Kotlin 中数值类型的相互转换可以通过类型转换函数:
val int = 123
val long = int.toLong()
val float = 3.0F
val double = float.toDouble()
Kotlin 的类型转换函数:
- toByte()
- toShort()
- toInt()
- toLong()
- toFloat()
- toDoble()
- toChar()
下面是 Kotlin 的按位运算操作,按位取反在 Kotlin 是一元运算符,因此用 inv() 函数来进行运算:
// 左移
val leftShift = 1 shl 2
// 右移
val rightShift = 1 shr 2
// 无符号右移
val unsignedRightShift = 1 ushr 2
// 按位与
val and = 1 and 0x00001111
// 按位或
val or = 1 or 0x00001111
// 按位异或
val xor = 1 xor 0x00001111
// 按位取反
val inv1 = 1.inv()
// 编译时异常,要用 inv()
val inv2 = ~1
字符类型
Kotlin 中的字符型变量不能用作数值,因为 Kotlin 中对该运算符的定义:
// 编译时异常
var a = 'a' == 10
// 编译通过
var b = 'a'.toInt() == 10
字符串类型
在 Kotlin 中可以用三个双引号 “”“ 将字符串括起来,这种字符串也叫原始字符串(raw string)。当字符串要换行的时候,不需要再用加号:
val rawString = """
strings that span many lines
strings that span many lines
"""
println(rawString)
数组类型
在 Kotlin 中可以通过 arrayOf() 函数来创建数组,还可以通过数组长度和字面函数来创建数组,函数计算值将会赋值到每一个元素中:
val array = arrayOf(1, 2, 3)
// 0、1、4、9、16、...、81
val perfectSquares = Array(10, {k -> k * k})
Kotlin 中的数组不是特殊数据类型,因此它有 get 、set 方法,但是一般不推荐使用:
val array = arrayOf(1, 2, 3)
var element1 = array[0]
var element2 = array.get(1)
array[2] = 5
因为装箱类是对象,开销比基本类型要大。当你对性能要求高,可以使用 Kotlin 提供的几种数组类型,它们会被转换成基本类型 :
- ByteArray
- CharArray
- ShortArray
- IntArray
- LongArray
- BooleanArray
- FloatArray
- DoubleArray
导包重命名
当导入的类同名时,可以用 as 运算符对导入的包进行重命名:
import com.packt.myproject.Foo
import com.packt.otherproject.Foo as Foo2
fun doubleFoo() {
val foo1 = Foo()
val foo2 = Foo2()
}
字符串模板
Koltin 的字符串模板(template)允许你在字符串中插入多个变量,而不需要用加号拼接它们,使用字符串模板只需要在变量前面加美元符号($):
val name1 = "Sam"
val name2 = "Alex"
val str = "$name1, $name2"
// Sam, Alex
println(str)
如果插入的变量需要进行运算,比如访问某个对象的属性,则需要用花括号 { } 将它括起来:
val name = "Sam"
val str = "${name.length}"
// 3
println(str)
值域
Kotlin 中的值域(range)是由区间定义的。任何可比较的数据类型(如 Int、Long、Char)都能用于创建值域,创建值域可以用两点 .. 或 until 运算符:
val aToZ = "a".."z"
val oneToNine = 1..9
// until 适用于遍历数组和列表等集合时
// 10...19
val zeroToNine = 0 until 10
for(i in zeroToNine) println(i)
用 in 运算符判断某个值是否在值域内:
val aToZ = "a".."z"
val isTrue = "c" in aToZ
val oneToNine = 1..9
val isFalse = 11 in oneToNine
值域从该值往下和从该值往上分别是 downTo() 和 rangeTo() :
// 0...100
val countingDown = 100.downTo(0)
// 10...20
val rangeTo = 10.rangeTo(20)
step() 函数可以创建不同步长的值域:
val oneToTwenty = 1..20
// 1、3、5、...、19
val oddNumbers = oneToTwenty.step(2)
用 reversed() 函数将值域的顺序颠倒:
// 100、98、96、...、2
val down2 = (2..100).step(2).reversed()
循环
Kotlin 的 for 循环能够遍历任何实现了 Iterator 接口的类中的元素,如数组、List、Set、Map :
var array = listOf(1, 2, 3)
for (i in array) println(i)
val list = listOf(2, 3, 4)
for (k in list) println(k)
var map = mapOf("a" to 1, "b" to 2)
// key: a, value: 1
// key: b, value: 2
for((key, value) in map)
println("key: $key, value: $value")
在 Kotlin 的 String 类中,Kotlin 也为它提供了一个 Iterator 扩展函数,因此 String 也能被遍历:
val string = "print my characters"
for (char in string) println(char)
Kotlin 的数组有一个 indices 扩展函数,它可以用于遍历整个数组的下标:
val array = arrayOf("a", "b", "c")
// 0 1 2
for (i in array.indices)
println(i)
异常处理
在 Kotlin 中异常都是非强制处理的(unchecked):
fun error() {
throw FileNotFoundException()
}
error()
类的实例化
在 Kotlin 中实例化一个类不需要 new :
val date = BigDecimal(100)
引用等价与结构等价
引用等价指的是两个对象是否为同一个引用,也就是两个对象是否在同一块内存中,结构等价指的是对象结构是否相同,比如 List 的长度以及每个元素是否相同:
var a = listOf(1, 2)
var b = listOf(1, 2)
// false, 非引用等价
println(a === b)
// true,结构等价
println(a == b)
this 表达式
当 Kotlin 的 this 语句在扩展函数或字面函数(Function Literals)中时,this 指的是该扩展函数或字面函数。可以用 @ 加标签来修饰 this 指向的对象:
class A {
inner class B {
var foo = 100.foo()
fun Int.foo() {
// 用标签指向类 A 的实例
var a = this@A
// 用标签指向类 B 的实例
var b = this@B
// 当前扩展函数实例
var c = this
println(a)
println(b)
println(c)
}
}
}
可见度修饰符
private
使用 private 修饰的顶层函数、顶层类或接口只能在同一个文件中被访问。
在一个类、接口或对象,任何 private 函数或属性只对其他该类的成员变量、接口或对象可见:
class A {
private var a = 10
private fun b() { }
fun c() {
var c = a
var d = b()
}
}
class B {
// 编译时异常,a 为私有变量
var e = A().a
// 编译时异常,a 为私有函数
var f = A().b()
}
Protected
顶层函数、类、接口或对象不能声明为 protected 。任何在类或接口中声明为 protected 的函数或属性只对该类或接口的成员变量以及子类可见。
open class A {
protected var a = 10
}
class B {
// 编译时异常,a 为 protected
var a = A().a
}
class C: A() {
var c = a
}
Internal
同模块的类可见。这里的模块指的是 Intellij 中定义的 Maven 或 Gradle 模块。
控制流语句
在 Kotlin 中,if...else 和 try...catch 代码块是表达式,这些代码块的运算结果可以赋值给变量、用作函数的返回值或当做参数传给一个函数:
var a = 10
val b = if (a == 10) true else false
val success = try {
true
} catch (e: IOException) {
false
}
if...else 作为返回值和参数:
fun isZero(x: Int): Boolean {
return if (x == 0) true else false
}
isZero(if (a == 10) 0 else 10)
Kotlin 中没有定义三元运算符:
// 编译时异常,不支持三元运算符
var b = a == 10 ? 5 : 0
当使用 if 作为表达式时,要包含 else 语句 :
// 编译时异常
var a = if (1 == 2) true
// 编译通过
var b = if (1 == 2) true else false
空值处理
一个能赋为空值变量需要在变量类型后加上问号:
// 编译时异常
var str1: String = null
// 编译通过
var str2: String? = null
类型检查与转换
Kotlin 中可以用 is 运算符替换 Java 的 instnaceof 运算符,在经过 is 运算符类型检查通过后编译器都会帮我们自动转换类型:
fun isString(any: Any) {
var str = ""
// 经过 is 检查后编译器会帮我们将 Any 转成 String
str = if (any is String) any else "b"
}
如果要进行转换的是布尔值那么 if...else 都可以省了:
fun isEmptyString(any: Any) : Boolean {
return any is String && any.length == 0
}
在这个例子中,函数测试我们没有一个字符串,或者我们有,然后必须为空
显式转换
若要显式地进行类型转换,可以用 as 运算符,转换不成功时会抛出一个类型转换异常:
val str1 = "123" as String
// elvis 安全转换,转换不成功会返回 null 字符串
val str2 = 123 as? String
// 类型转换异常
val str3 = 123 as String
when 表达式
Kotlin 的 when 表达式有两种形式。第一种类似于 Java 的 switch 表达式。第二种用一系列条件判断语句条件进行语句切换。
when(value)
类似于 switch ,需要一个常量:
fun foo(x: Int) {
when (x) {
0 -> println("x is zero")
1 -> println("x is 1")
else -> println("x is neither 0 or 1")
}
}
Kotlin 中的 when 表达式类似于 if...else 代码块可以赋值给变量、作为参数或返回值:
fun isMinOrMax(x: Int): Boolean {
val isZero = when (x) {
Int.MIN_VALUE -> true
Int.MAX_VALUE -> false
else -> false
}
}
当两个分支的代码是一样时,可以用逗号将它们分隔开以执行同一个分支:
fun isZeroOrOne(x: Int) : Boolean {
return when (x) {
0, 1 -> true
else -> false
}
}
没有参数的 when
没有参数的 when 代码块,用条件语句进行切换:
fun foo (x: Int, y: Int) {
when {
x < y -> println("x 小于 y")
x > y -> println("x 大于 y")
else -> println("x 等于 y0")
}
}
函数返回值
在 Kotlin 中,当函数返回值不为空时,需要声明函数返回值的类型,当允许返回空值时,要加问号表明:
fun foo1() {
// 编译时异常,没声明返回值类型
return ""
}
fun foo2(): String {
// 编译时异常,没有加问号
return null
}
fun foo3(a: Int, b: Int) : Int {
// 编译成功
return a + b
}
如果从闭包中返回一个值,需要用标签修饰返回类型,否则返回的是外部函数,标签可以是显式也可以是隐式的:
fun printUtilStop1() {
val list = listOf("a", "b")
list.forEach stop@ {
// 显式标签
if (it == "stop") return@stop
else println(it)
}
}
fun printUtilStop2() {
val list = listOf("a", "b")
list.forEach {
// 隐式标签
if (it == "stop") return@forEach
else println(it)
}
}
类型层级
在 Kotlin 的 Any 类型相当于 Java 的 Object,它是所有对象的父类。Any 中定义了 toString,hashCode 以及 equal 等函数。它也定义了apply 、let 和 to 等扩展方法。
Kotlin 的 Unit 类型相当于 Java 中的 void,Unit 与 void 的区别在于 Unit 是一个类而且是单例对象。
Kotlin 中的 Nothing 类型是所有类型的子类。同时它也是 elvis 运算符和 emptyList() 函数的实现。当函数没有返回值时可以返回
Nothing 类型:
fun reportError(): Nothing = throw RuntimeException()
fun a (x: Int): String{
if (x > 10)
return "OK"
// 编译时异常,没有返回值
}
fun b (x: Int): String {
if (x > 10)
return "OK"
// 自动返回 Nothing
reportError()
}
参考资料
Programming Kotlin