语句和表达式:
在Kotlin中,if是表达式,而不是语句。语句和表达式的区别在于,表达式有值,并且能作为另一个表达式的一部分使用;而语句总是包围着它的代码块中的顶层元素,并且没有自己的值。在Java中,所有的控制结构都是语句。而在Kotlin中,除了循环(for、do和do/while)以外大多数控制结构都是表达式。这种结合控制结构和其他表达式的能力让你可以简明扼要地表示许多常见的模式,另一方面,Java中的赋值操作是表达式,在Kotlin中反而变成了语句,这有助于避免比较和赋值之间混淆,而这种混淆是常见的错误来源。
表达式函数体 Convert to expression body
代码块函数体 Convert to block body
fun max(a : Int , b : Int): Int = if (a > b) a else b
如果函数体写在花括号中,这个函数就有代码块体,如果直接返回一个表达式,就有表达式体
fun max(a : Int, b : Int) = if(a > b) a else b
该函数没有声明返回类型,是因为通过类型推导,编译器会分析该表达式函数体,并把它的类型作为函数的返回类型。PS : 只有表达式体函数的返回类型可以省略。对于有返回值的代码块体函数,必须显式地写出返回类型和return语句。
1.基本要素:函数和变量
如果不能提供可以赋给这个变量的值的信息,编译器就无法推断出它的类型
可变变量和不可变量
声明变量的关键字有两个:
val(来自value) - 不可变引用。使用val声明的变量不能在初始化之后再次赋值。它对应的是Java的final变量
var(来自variable) - 可变引用。这种变量的值可以被改变。这种声明对应的是普通(非final)的Java变量
默认情况下,应尽可能地使用val关键字来声明所有的Kotlin变量
更简单的字符串格式化:字符串模板 $
fun main(args: Array<String>) { if (args.size > 0) { println("Hello , ${args[0]}") } } fun main(args: Array<String>) { println("Hello,${if (args.size > 0) args[0] else "shit"}") }2.类和属性
在Kotlin中public是默认的可见性,可以省略
class Rectangle(val height: Int, val width: Int) { val isSquare: Boolean get() = height == width // get(){ // return height == width // } } val rectangle = Rectangle(40, 41) println(rectangle.isSquare)Kotlin不区分导入的是类还是函数,而且它允许使用import关键字导入任何种类的声明,可以直接导入顶层函数的名称
3.表示和处理选择:枚举和"when"
3.1 声明枚举类
Kotlin中,enum是一个所谓的软关键字:只有当它出现在class前面时才有特殊意义,在其他地方可以把它当作普通的名称使用
enum class Color(val r: Int, val g: Int, val b: Int) { RED(255, 0, 0), GREEN(0, 255, 0), BLUE(0, 0, 255), ORANGE(255, 165, 0), INDIGO(75, 0, 130), VIOLET(238, 130, 238), YELLOW(255, 255, 0); fun rgb() = (r * 256 + g) * 256 + b }当声明每个枚举常量时,必须提供该常量的属性值,Kotlin语法中唯一必须使用分号的地方 : 如果在枚举类中定义任何方法,就要使用分号把枚举常量列表和方法定义分开。
3.2 使用"when"处理枚举类
when相当于Java中的switch语句
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" }和Java不同,在Kotlin中不需要在每个分支都写上break语句,如果匹配成功,只有对应的分支会执行,也可以把多个值合并到同一个分支,只需要用逗号隔开这些值。
也可以通过导入这些枚举常量值来简化代码
import com.caotong.kotlin.Color.* fun getWarmth(color: Color) = when (color) { RED, ORANGE, YELLOW -> "warm" GREEN -> "neutral" BLUE, INDIGO, VIOLET -> "cold" }3.3 在"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") }Kotlin标准函数库中有一个setOf函数创建出一个Set,会包含所有指定为函数实参的对象。set这种集合的条目顺序并不重要,只要两个set中包含一样的条目,它们就是相等的。所以setOf(c1,c2)和setOf(RED,YELLOW)是相等的,意味着c1是RED和c2是YELLOW的时候相等,反过来,c1是YELLOW和c2是RED的时候也会相等。
when表达式把它的实参依次和所有分支匹配,直到某个分支满足条件
3.4 不带参数的When
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 == VIOLET && c2 == BLUE) || (c1 == BLUE && c2 == VIOLET) -> INDIGO else -> throw Exception("Dirtry color") }mixOptimized函数比mix函数性能更优,但是代码可读性变差。
3.5 智能转换:合并类型检查和转换
例子:写一个函数对 (1+2)+4 这样的算术表达式求值。用怎样的形式编码这种表达式?把它们储存在一个树状结构中,结构中每个节点要么是一次求和(Sum)要么是一个数字(Num)。Num永远都是叶子节点,而Sum节点有两个子节点:它们是求和运算的两个参数。
interface Expr class Num(val value: Int) : Expr class Sum(val left: Expr, val right: Expr) : Expr println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))声明类的时候,使用一个冒号(:)后面接上接口名称,来标记这个类实现了这个接口
该函数通过Java的方式写,再重构成Kotlin风格:
fun eval(e: Expr): Int { if (e is Num) { val n = e as Num return n.value } if (e is Sum) { return eval(e.left) + eval(e.right) } throw Exception("Unknown expression") }Kotlin中,使用is检查来判断一个变量是否是某种类型,is检查和Java中instanceOf相似。但是在Java中,如果已经检查过一个变量是某种类型并且要把它当作这种类型来访问其成员时,在instanceOf检查之后还需要显式地加上类型转换。如果最初的变量会使用超过一次,常常选择把类型转换的结果储存在另一个单独的变量中。Kotlin中,编译器帮你完成了这些工作,如果你检查过一个变量是某种类型,后面就不再需要转换它,可以把它当作你检查过的类型使用。事实上编译器为你执行了类型转换,我们把这种行为称为智能转换。
使用as关键字来表示到特定类型的显式转换
3.6 重构:用"when"代替"if"
将eval函数重构成符合Kotlin语言习惯的风格
使用有返回值的if表达式:
fun eval(e: Expr): Int = if (e is Num) { e.value } else if (e is Sum) { eval(e.right) + eval(e.left) } else { throw Exception("Unknown expression") }使用when代替if层叠:
fun eval(e: Expr): Int = when (e) { is Num -> e.value is Sum -> eval(e.left) + eval(e.right) else -> throw Exception("error") }3.7 代码块作为"if"和"when"的分支
使用分支中含有混合操作的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 Exception("Unknown expression") }4.迭代事物:"while"循环和"for"循环
4.1 "while"循环
Kotlin有while循环和do-while循环,语法上Java中对应的循环没区别
4.2 迭代数字:区间和数列
区间:本质上是两个值之间的间隔,这两个值通常是数字:一个起始值,一个结束值,使用 .. 运算符表示区间:val oneToTen = 1..10 Kotlin的区间是包含的或者闭合的,意味着第二个值始终是区间的一部分
数列:当用整数区间循环迭代其中所有的值,这样的区间称作数列
使用when实现Fizz-Buzz游戏(能被3整除输出"fizz",能被5整除输出"buzz",能被3和5的公倍数整除输出"fizzbuzz")
fun fizzBuzz(i: Int) = when { i % 15 == 0 -> "FizzBuzz" i % 5 == 0 -> "Buzz" i % 3 == 0 -> "Fizz" else -> "$i" } for (i in 1..100) { println(fizzBuzz(i)) }在上一个例子上增加需求:从100开始倒着计数且只计偶数
for (i in 100 downTo 1 step 2) { println(fizzBuzz(i)) }downTo:递减
step:每次循环跳过的数字(步长)
until:不包含结束值 for (x in 0 until size) 等同于 for (x int 0..size -1)
4.3 迭代map
初始化并迭代map
fun main() { val binayReps = TreeMap<Char, String>() for (c in 'A'..'F') { val binary = Integer.toBinaryString(c.toInt()) binayReps[c] = binary } for ((latter, binary) in binayReps) { println("$latter = $binary") } val list = arrayListOf("10", "11", "12") for ((index, element) in list.withIndex()) { println("$index:$element") } }4.4 使用"in"检查集合和区间的成员
使用in运算符来检查一个值是否在区间中,或者它的逆运算,!in,来检查这个值是否不在区间中。
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z' fun isNotDigit(c: Char) = c !in '0'..'9' println(isLetter('S')) println(isNotDigit('3'))用in检查作为when分支
fun recognize(c: Char) = when (c) { in '0'..'9' -> "It's a digit" in 'a'..'z', in 'A'..'Z' -> "is letter" else -> "I don't know" }4.5 Kotlin中的异常
Kotlin中异常处理语句的基本形式和Java类似,抛出异常的方式也不例外
和所有其他类一样,不必使用new关键字来创建异常实例
和Java不同的是,Kotlin中throw结构是一个表达式,能作为另一个表达式的一部分使用
fun readNumber(reader: BufferedReader) :Int? { try { val line = reader.readLine(); return Integer.parseInt(line) }catch (e:NumberFormatException){ return null }finally { reader.close() } }
Kotlin并不区分受检异常和未受检异常,不用指定函数抛出的异常,而且可以处理也可以不处理异常。
小结
fun关键字用来声明函数。val关键字和var关键字分别用来声明只读变量和可变变量
字符串模板帮助你避免繁琐的字符串连接。在变量名称前加上$前缀或者用${}包围一个表达式,来把值注入到字符串中
值对象类在Kotlin中以简洁的方式表示'
熟悉的if现在是带返回值的表达式
when表达式类似于Java中的switch
编译器中有智能转换来帮助变量的类型转换
区间和数列允许Kotlin在for循环中使用统一的语法和同一套抽象机制,并且可以使用in运算符和!in运算符来检查值是否属于某个区间
Kotlin中异常处理和Java相似,Kotlin中不要求声明函数可以抛出的异常