Kotlin基础
1.基本要素:函数和变量
函数
语句和表达式:
在Kotlin中,if是表达式而不是语句。
- 表达式:有值,能作为另一个表达式的一部分使用
- 语句:无值,总是包围着它的代码块中的顶层元素
在Java中,所有控制结构都是语句。
在Kotlin中,除了循环以外的大多数控制结构都是表达式。
在Java中,赋值是表达式,在Kotlin中则是语句。这有助于避免比较和赋值之间的混淆。
表达式函数体:
函数体由单个表达式构成的函数。
用表达式作为完整的函数体,并去掉花括号和return语句。
fun max(a: Int,b: Int) = if(a > b) a else b
变量
声明变量关键字由两个:
val
:不可变引用,来自value。不能在初始化之后再次赋值,对应final
变量var
:可变引用,来自variable。对应普通变量
默认情况下尽可能使用val
关键字来声明变量。
使用不可变引用,不可变对象,无副作用的函数让你的代码更接近函数式编程风格。
字符串模板
val name = "world"
println("hello,$name")
和许多脚本语言一样,Kotlin可以在字符串字面值中引用局部变量,在变量前加上$
即可。
这等价于Java中的字符串连接,编译后的代码创建了一个StringBuilder
对象,并把常量和变量附加上去。
对象表达式和声明的区别
他俩之间只有一个特别重要的区别:
-
对象表达式在我们使用的地方立即初始化并执行的
-
对象声明是懒加载的,是在我们第一次访问时初始化的。
2.类和属性
属性
值对象:只有数据没有其他代码的类。
在Java中,字段和其访问器的组合被叫做属性
在Kotlin中,属性是头等的语言特性,在类中声明一个属性和声明一个变量一样:使用val
和var
关键字。
自定义访问器
class Rectangle(val h: Int, val w: Int){
val isSquare: Boolean
get(){
return h == w
}
}
属性isSquare
不需要字段来保存其值,它的值是每次访问属性的时候被计算出来的。
也可以写成表达式体get() = h == w
Kotlin源码布局:目录和包
Java把所有类组织成包,Kotlin也有类似的概念。
Kotlin不区分导入的是类还是函数,允许使用import
关键字导入任何种类的声明。可以直接导入顶层函数的名称。
在Java中,要把类放到和包结构相匹配的文件与目录结构中。
在Kotlin中则不需要,可以把多个类放在同一个文件中,文件的名字还可以随意选择。也没有对磁盘上源文件的布局强加任何限制。包的层级结构不需要遵循目录的层级结构。
3.表示处理和选择:枚举和“when”
声明枚举类
枚举并不是值的列表,可以给枚举类声明属性和方法。
enum class Color(
val r: Int, val g: Int, val b: Int
) {
RED(255, 0, 0), ORANGE(255, 165, 0),
YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0, 0, 255),
INDIGO(75, 0, 130), VIOLET(238, 130, 238);
fun rgb() = (r * 256 + g) * 256 + b
}
fun main(args: Array<String>) {
println(Color.BLUE.rgb())
}
当你声明了枚举类的属性,那么声明枚举常量的时候,必须提供此枚举常量的属性值。
**Kotlin中唯一必须使用分号的地方:**在枚举类中,定义方法之前,用分号把常量和方法隔开。
使用“when”处理枚举类
Java中switch
语句,在Kotlin中对应的结构是when
。
when
是一个有返回值的表达式,因此可以直接写一个返回when
表达式的表达式体函数。
enum class Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
fun getMnemonic(color: Color) =
when (color) {
Color.RED -> "Richard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Gave"
Color.BLUE -> "Battle"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}
fun getWarmth(color: Color) = when(color) {
Color.RED, Color.ORANGE, Color.YELLOW -> "warm"
Color.GREEN -> "neutral"
Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold"
}
fun main(args: Array<String>) {
println(getMnemonic(Color.BLUE))
}
和Java不一样你不需要写break
语句,并且可以把多个值合并到同一个分支。
在“when”结构中使用任意对象
when
允许使用任何对象作为分支条件。
fun mix(c1: Color, c2: Color) =
when (setOf(c1, c2)) {
setOf(RED, YELLOW) -> ORANGE
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Dirty color")
}
fun main(args: Array<String>) {
println(mix(BLUE, YELLOW))
}
使用不带参数的“when”
上面一个例子中的代码效率有点低,因为每次调用它都会创建一些Set
实例。
可以换一种方式重写:
fun mixOptimized(c1: Color, c2: Color) =
when {
(c1 == RED && c2 == YELLOW) ||
(c1 == YELLOW && c2 == RED) ->
ORANGE
(c1 == YELLOW && c2 == BLUE) ||
(c1 == BLUE && c2 == YELLOW) ->
GREEN
(c1 == BLUE && c2 == VIOLET) ||
(c1 == VIOLET && c2 == BLUE) ->
INDIGO
else -> throw Exception("Dirty color")
}
若没有给when
表达式提供参数,则分支条件就是任意的布尔表达式。
智能转换:合并类型检查和转换
接下来用一个函数做例子,此函数要求完成 (1+2)+4 的算术表达式的求值。
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int {
if (e is Num) {
val n = e as Num
return n.value
}
if (e is Sum) {
return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException("Unknown expression")
}
fun main(args: Array<String>) {
println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
}
上面的代码展示了一种简单的类结构来表达这种表达式编码方式:Expr
接口和其实现类Num
和Sum
注意Expr
接口没有声明任何方法,他只是一个标记接口,用来给不同种类的表达式提供一个公共的类型。
在Kotlin中,使用 is
检查来判断一个变量是否为某种类型。
is
检查和Java中的 instanceOf
相似。但是Kotlin中若你检查过某变量的类型,后面再使用它就不需要转换了。
编译器为你执行了类型转换,这种行为称为智能转换。
智能转换只在变量经过is
检查且之后不再发生变化的情况下有效。当你对一个类的属性进行智能转换的时候,这个属性必须是val
属性,且不能有自定义的访问器。否则每次对属性的访问是否都能返回同样的值将无从验证。
使用as
关键字来表示到特定类型的显示转换。
重构:用“when”代替“if”
Kotlin没有三元运算符,因为if
表达式有返回值。
这意味着可以用表达式体语法重写eval
函数。
fun eval(e: Expr): Int =
if (e is Num) {
e.value
} else if (e is Sum) {
eval(e.right) + eval(e.left)
} else {
throw IllegalArgumentException("Unknown expression")
}
若if分支只有一个表达式,花括号可以省略。
若if分支是代码块,代码块中最后一个表达式会被作为结果返回。
接下来使用when
来重写:
fun eval(e: Expr): Int =
when (e) {
is Num ->
e.value
is Sum ->
eval(e.right) + eval(e.left)
else ->
throw IllegalArgumentException("Unknown expression")
}
when
表达式并不仅限于检查值是否相等,它还允许你检查实参值的类型。
代码块作为“if”和“when”的分支
分支体是代码块,代码块中最后一个表达式会被作为结果返回。
fun evalWithLogging(e: Expr): Int =
when (e) {
is Num -> {
println("num: ${e.value}")
e.value
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
println("sum: $left + $right")
left + right
}
else -> throw IllegalArgumentException("Unknown expression")
}
规则:代码块中最后的表达式就是结果
此规则对try
主体和catch
子句也有效。
此规则对常规函数不成立。一个常规函数要么具有不是代码块的表达式函数体,要么具有包含显示return
语句的代码块函数体。
4.迭代事物:“while”循环和“for”循环
while
循环和Java中的没有区别。
for
循环和Java的for-each
循环一致,其写法和for<item> in <elements>
和C#一样。
迭代数字:区间和数列
区间:两个值之间的间隔,使用..
来表示区间。
val oneToTen = 1..10
Kotlin的区间是闭合的,结束值是区间的一部分。
若你能迭代区间中所有的值,这样的区间被称作数列。
fun fizzBuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz "
i % 3 == 0 -> "Fizz "
i % 5 == 0 -> "Buzz "
else -> "$i "
}
fun main(args: Array<String>) {
for (i in 100 downTo 1 step 2) {
print(fizzBuzz(i))
}
}
上述代码迭代了一个步长为2的递减数列。
迭代map
下面的程序将字符转换成二进制打印出来:
fun main(args: Array<String>) {
val binaryReps = TreeMap<Char, String>()
for (c in 'A'..'F') {
val binary = Integer.toBinaryString(c.toInt())
binaryReps[c] = binary
}
for ((letter, binary) in binaryReps) {
println("$letter = $binary")
}
}
..
也可以创建字符区间。这里从A迭代到F,包括F。
for
循环允许展开迭代中的集合的元素,并把展开的结果存储到了两个独立变量letter
和binary
中。
可以用这样的展开语法在迭代集合的同时跟踪当前项的下标。
下面的代码遍历的list并打印出下标:
val list = arrayListOf("10", "11", "1101")
for ((index, element) in list.withIndex()) {
println("$index : $element")
}
使用“in”检查集合和区间的成员
使用in
运算符来检查一个值是否在区间中,或者其逆运算,!n
,检查不在区间中。
下面的代码检查一个char是数字还是字母:
fun recognize(c: Char) = when (c) {
in '0'..'9' -> "It's a digit!"
in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
else -> "I don't know…"
}
fun main(args: Array<String>) {
println(recognize('8'))
}
区间不限于数字和字符,只要支持实例比较操作(实现了java.lang.Comparable
接口),就能创建这一种类型的对象区间。
如果是这样的区间,并不能列举出这个区间中的所有对象。
比如我们不能列举出“Java”和“Kotlin”之间所有的字符串。
但是我们任然可以用in运算符检查一个对象是否属于这个区间。
5.Kotlin中的异常
Kotlin的异常处理大体上和Java一致。Kotlin中的throw
结构是一个表达式,能作为另一个表达式的一部分使用。
“tyr”“catch”和“finally”
下面的代码从给定文件中读取一行,解析成数字并返回值;若不是有效数字则返回null
。
fun readNumber(reader: BufferedReader): Int? {
try {
val line = reader.readLine()
return Integer.parseInt(line)
}
catch (e: NumberFormatException) {
return null
}
finally {
reader.close()
}
}
fun main(args: Array<String>) {
val reader = BufferedReader(StringReader("239"))
println(readNumber(reader))
}
和Java最大的区别是throws子句没有出现在代码中。
和其他许多现代JVM语言一样,Kotlin并不区分受检异常和未受检异常。不用指定函数抛出的异常,可以处理也可以不处理异常。
“try”作为表达式
修改上面的例子如下:
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
return
}
println(number)
}
fun main(args: Array<String>) {
val reader = BufferedReader(StringReader("not a number"))
readNumber(reader)
}
try
关键字引入了一个表达式,可以把它的值赋给一个变量。
return
语句放在catch
代码块中,因此该函数的执行在catch
代码块之后不会再继续。
如果你需要返回值,可以将return
替换为null
,最后会打印出null
6.小结
fun
关键字用来声明函数,val
和var
分别声明只读变量和可变变量- 字符串模板:在变量前加上
$
或者用${}
包围一个表达式,来把值注入字符串 if
,when
,try catch
都是带返回值的表达式- 检查变量类型之后不必再显示的转换,编译器用智能转换自动帮你完成
..
会创建一个区间,还可以使用in
和!n
运算符来检查值是否属于区间- Kotlin不要求你声明函数可以抛出的异常