《Kotlin实战》读书笔记第二章

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中,属性是头等的语言特性,在类中声明一个属性和声明一个变量一样:使用valvar关键字。

自定义访问器

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接口和其实现类NumSum

注意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循环允许展开迭代中的集合的元素,并把展开的结果存储到了两个独立变量letterbinary中。

可以用这样的展开语法在迭代集合的同时跟踪当前项的下标。

下面的代码遍历的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关键字用来声明函数,valvar分别声明只读变量和可变变量
  • 字符串模板:在变量前加上$或者用${}包围一个表达式,来把值注入字符串
  • ifwhentry catch都是带返回值的表达式
  • 检查变量类型之后不必再显示的转换,编译器用智能转换自动帮你完成
  • ..会创建一个区间,还可以使用in!n运算符来检查值是否属于区间
  • Kotlin不要求你声明函数可以抛出的异常
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值