1 函数和变量
函数构成:函数名称,参数列表,返回类型,函数体;函数的声明以关键字fun开始,函数名称 紧随其后,接下来是括号括起来的 参数列表,参数列表的后面跟着
返回类型,返回类型和参数列表之间用冒号隔开,最后是函数体。如下:
fun min(a:Int,b:Int):Int {
return if(a>b) b else a
}
2 表达式和语句
上面的栗子 if是 表达式,而不是语句,区别: 表达式 有值,并且能作为另一个表达式的一部分使用。语句 总是包含着它的代码块中的顶层元素,并且没有自己的值。
在Java中,所有的控制结构都是语句,而在Kotlin中,除了for、do和do/while以外大多数控制结构都是表达式。当函数体是由单个表达式构成时,可以用这个表达式作为完整的函数体,并且去掉花括号和return语句,上面的例子就是这种情况,因此可以改写为:
fun min(a:Int,b:Int) :Int = if(a > b) b else a
如果函数体写在花括号中,我们说这个函数有 代码块体, 如果它直接返回了一个表达式,它就有 表达式体
3 省略返回类型
对于 表达式体函数,可以省略返回类型,因为编译器会分析作为函数体的表达式,并把它的类型作为函数的返回类型,这种分析称为 类型推导。但是对于有返回值的 代码块体函数,必须显示地写出返回类型和return语句。上面栗子可以简化:
fun min(a:Int,b:Int)= if(a > b) a else b
4 变量
在Kotlin中,变量的声明以关键字val/var开始,然后是变量名称,最后可以加上类型(不加也可以),这里分为两种情况:
如果指定了初始化器,那么在不指定类型的情况下,编译器会分析初始化器表达式的值,并把它的类型作为变量的类型,例如下面两个就分别为Int和Double类型:
fun valFunc() {
val dValue = 1e2
val iValue = 6
println("dValue = $dValue,iValue=$iValue")
}
如果没有指定初始化器,需要显示指定它的类型,因为此时编译器无法推断出它的类型。
5 可变变量和不可变变量
5.1 不可变引用 val 使用val声明的变量不能在初始化之后再次赋值,它对应的是Java的final变量。默认情况下,应该尽可能地使用val关键字来声明所有的Kotlin变量。在定义了val变量的代码块执行期间,val变量只能进行唯一一次初始化,但是,如果编译器能确保唯一一条初始化语句会被执行,可以根据条件使用不同的值来初始化它。
5.2 可变引用 var 这种变量的值可以改变,但是它的类型却是改变不了的。如果需要在变量中存储不匹配类型的值,必须手动把值转换或强制转换到正确的类型。
5.3 字符串模板
Kotlin可以在字符串字面值中引用局部变量,只需要在变量名称前面加上字符$.
栗子:
fun main(args: Array<String>) {
val name = if (args.size > 0) args[0] else "Kotlin"
println("Hello, $name!")
}
如果要在字符串中使用$,需要对它进行转义 \$(转义)$name 除了可以引用 局部变量之外 ,还可以引用更加复杂的表达式,只需要把表达式用花括号扩起来。
fun main(args: Array<String>) {
println("Hello, ${if (args.size > 0) args[0] else "someone"}!")
}
6 类
6.1 属性 类的概念就是把数据和处理数据的代码封装成一个单一的实体,在Java中,数据存储在字段中,并且通常是私有的。如果想让类的使用者访问到数据得提供访问方法,即getter/setter。在Java中,字段和其访问器的组合常常被叫作属性。在Kotlin中,属性是头等的语言特性,完全 替代了字段和访问器方法。在类中声明一个属性和声明一个变量一样:使用val/var关键字,前者是只读的,而后者是可变的。
当声明属性的时候,就声明了对应的访问器(只读属性有一个gettter,而可变属性则有getter/setter),例如下面的例子,声明了只读的name属性,可变的isMarried属性,其赋值和读取的方法如下所示:
class Person(
val name: String,
var isMarried: Boolean
)
fun main(args: Array<String>) {
val person = Person("Bob", true)
println(person.name)
println(person.isMarried)
person.isMarried = false
println(person.isMarried)
}
输出:
Bob
true
false
6.2 自定义访问器
假设声明一个矩形,它能判断自己是否是正方形,那么就不需要一个单独的字段来存储这个信息,此时我们可以写一个自定义的访问器:用val开头作为声明,紧跟着的是属性的名称和类型,接下来是get()关键字,最后是一个函数体。
栗子:
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}
fun main(args: Array<String>) {
val rectangle = Rectangle(60, 63)
println(rectangle.isSquare)
}
输出: false
6.3 目录和包
Kotlin中包的概念和Java类似,每个kotlin文件都能以一个package语句开头,而文件中定义的所有声明(类、函数及属性)都会被放到这个包中。 如果其他文件定义的声明也有相同的包,这个文件可以直接使用它们;如果包不同,则需要导入它们,导入语句放在文件的最前面并使用import关键字kotlin不区分导入的是类还是函数,而且,它允许使用import关键字导入任何种类的声明,可以直接导入顶层函数的名称,也可以在包名称后加上.*来导入特定包中定义的所有声明。在Java中,要把类放到和包结构相匹配的文件与目录结构中,而在kotlin中,可以把多个package声明不相同的类放在同一个文件夹中。
6.4 表示和处理选择: 枚举和when
声明枚举类时,enum是一个所谓的软关键字,只有当它出现在class前面时才有特殊的意义,在其他地方可以当做普通名称使用。 而class仍然是一个关键字,下面是一个枚举类的声明:
enum class Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
带属性的枚举类:
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
}
说明:当声明一个带属性的枚举类时,有几点需要注意:当声明每个枚举常量的时候,必须提供该常量的属性值。如果要在枚举类中定义任何方法,就要使用分号把枚举常量列表和方法分开。
6.5 使用“when”处理枚举类
when是一个有返回值的表达式,因此,作为表达式函数体,它可以去掉花括号和return语句,并省略返回类型的声明。下面是一个通过when 处理枚举类的栗子,它和Java中的switch语句类似,根据when中Color的值走到对应的分支,除此之外,我们可以把多个值用逗号间隔,合并到同一个分支:
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 main(args: Array<String>) {
println(getMnemonic(Color.BLUE))
}
输出:Battle
6.6 智能转换:合并类型检查和转换
在kotlin中,判断一个变量是否是某种类型需要使用is关键字,它和Java当中的instanceOf相似。 在Java中,在检查完后还需要显示地加上类型转换。在kotlin中,如果你检查过一个变量是某种类型,后面就不需要再转换它,可以把它当做你检查过的类型来使用。 我们用下面这个例子,Num和Sum都实现了Expr接口,通过is判断它的类型,完成递归求和。可以看到,在is判断之后,不再需要转换成Num或Sum,就可以直接访问该类的成员变量。
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))))
}
输出: 7
重构:用“when” 代替 “if”
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int =
when (e) {
is Num ->
e.value
is Sum ->
eval(e.right) + eval(e.left)
else ->
throw IllegalArgumentException("Unknown expression")
}
fun main(args: Array<String>) {
println(eval(Sum(Num(1), Num(2))))
}
输出: 3
7 迭代事物“while” 循环 和"for" 循环
7.1 while循环
kotlin和Java一样,有while循环和do-while循环,它们的语法和Java中相应的循环完全一致。
7.2 迭代数字:区间和数列
在Java当中,对于循环的处理方式为:先初始化变量,在循环的每一步更新它的值,并在值满足某个限制条件时退出循环。 而在Kotlin中,为了替代常见的循环用法,使用了 区间 的概念,其本质上就是两个值之间的间隔,这两个值通常是数字: 一个起始值,一个结束值。使用..运算符来表示区间,而结束值始终是区间的一部分。
如下:
for (c in 'A'..'F') {
val binary = Integer.toBinaryString(c.toInt())
binaryReps[c] = binary
}
7.3 迭代map 更新map,用的是TreeMap,在更新map时,我么可以像使用数组一样,只不过下标变成key值:
val binaryReps = TreeMap<Char, String>()
for (c in 'A'..'F') {
val binary = Integer.toBinaryString(c.toInt())
binaryReps[c] = binary
}
7.4 使用"in"检查集合 和区间的成员
使用in运算符来检查一个值是否在区间中,或者它的逆运算!in来检查这个值是否不在区间中,区间不仅限于字符,假如有一个支持实例比较操作的任意类(实现了java.lang.Comparable接口),就能创建这种类型的对象的区间。
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 1..100) {
print(fizzBuzz(i))
}
}
8 异常
kotlin的异常处理和Java以及其他许多语言的处理方式类似:一个函数可以正常结束,也可以在出现错误的情况下抛出异常。
方法的调用者能捕获到这个异常并处理它;如果没有处理,异常会沿着调用栈再次抛出。抛出异常时使用throw关键字,但是不必使用new关键字来创建异常实例。 throw结构是一个表达式,能作为另一个表达式的一部分使用。
import java.io.BufferedReader
import java.io.StringReader
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子句没有出现在代码中
8.2 “try”作为表达式
kotlin中的try关键字就像if和when一样,引入了一个表达式,可以把它的值赋给一个变量,并且需要用花括号把语句主体括起来。如果主体包含多个表达式,那么整个try表达式的值就是最后一个表达式的值。
import java.io.BufferedReader
import java.io.StringReader
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)
}