从点滴基础探究Kotlin的独特魅力
Posted 码个蛋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从点滴基础探究Kotlin的独特魅力相关的知识,希望对你有一定的参考价值。
码个蛋第320次推文
博客:https://www.jianshu.com/p/fbbd68bffc69
从接触Rxjava了解到函数式编程,从了解函数式编程想起Lambda表达式,从Lambda表达式接触到"Lambda表达式遇上Kotlin",这一次我是真的爱上了Kotlin,代码比Java更加简洁的同时却不晦涩.
再次接触Kotlin,对Kotlin的新特性又有了一个更加深刻的认识: Kotlin可谓取众多语言之精华!这一次我从爱变成了痴迷.
了解新特性,请查看《为什么从放弃到痴迷Kotlin》 : https://www.jianshu.com/p/9a419b60e2c5 而探究Kotlin独特魅力,要从细节入手,这篇博文从以下几个方面讲述Kotlin是如何的独具魅力!
目录
剖析方法结构
函数
变量
字符串模板
注释
自定义访问器
源码布局:目录和包
表示和处理选择:枚举和"when"
迭代事物:"while"和"for"循环
异常
综上(熟悉kotlin的,可以直接跳转至综上)
0
剖析方法结构
fun main(args:Array<String>){
println("Hello World")
}
② 参数的类型在名称后面
③ 函数可以定义在外层,没有必要放在类中
④ 数组就是类.kotlin中没有声明数组类型的语法
⑤ kotlin标准库含有针对java标准库函数进行的简洁的包装:println就是一个
③ 没有分号
1
函数
带有两个 Int 参数、返回 Int 的函数
fun sum(a: Int, b: Int): Int {
return a + b
}
println(sum(1,2))
06-06 12:39:11.123 1993-1993/com.best.chapter_01 I/System.out: 3
返回值是函数体的函数
fun max(a: Int, b: Int): Int {
return if(a>b) a else b //这句话就是函数体
}
println(max(1,2))
06-06 12:43:22.632 2081-2081/? I/System.out: 2
说明:
① kotlin中,if语句是表达式,不是语句,类似于三元运算符.
② kotlin中,除了循环(for do 和 do while)以外大多数控制结构都是表达式.
③ 语句和表达式的区别:表达式有值,并且能作为另一个表达式的一部分使用;语句总是包围着它的代码中的顶层元素,并且没有自己的值.
④ return if(a>b) a else b : 这句话就是函数体
表达式函数体
fun max(a: Int, b: Int): Int = if (a > b) a else b
说明:
① 如果函数体是由单个表达式构成,可以用这个表达式作为完整的函数体,并去掉花括号和return语句.
② 如果函数体是在花括号中,我们说这个函数由代码块体;如果它直接返回一个表达式,它就有表达式体.
③ IDEA提供表达式函数体与代码块函数体之间的转换意向动作.
返回值类型自动推断:
fun max(a: Int, b: Int) = if (a > b) a else b
说明:
① 对于表达式函数来说:编译器会分析作为函数体的表达式,并把它的类型作为函数的返回类型,即使没有显式地写出来.这种分析通常被称为类型推导.
② 只有表达式体函数的返回类型可以省略,代码块体函数的返回类型和return语句必须显示写出来.
返回无意义的值
fun printSum(a: Int, b: Int) :Unit{
println("sum of $a and $b is ${a + b}")
}
fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}
06-07 11:40:34.006 1844-1844/? I/System.out: sum of 1 and 2 is 3
06-07 11:40:34.006 1844-1844/? I/System.out: kotlin.Unit
2
变量
val和var
① val:对应的是Java的final变量,不可变引用,不能初始化之后再次赋值.
② var:对应的是非final变量.
③ 尽可能地使用val关键字来声明所有的kotlin变量,仅在必要的时候换成var.使用不可变变量以及不可变对象及无副作用的函数让你的代码更接近函数式编程风格.一次赋值(只读)的局部变量:
val a: Int = 1 // 立即赋值
val b = 2 // 自动推断出 `Int` 类型
val c: Int // 如果没有初始值类型不能省略
c = 3 // 明确赋值
可变变量:
var x = 5 // 自动推断出 `Int` 类型
x += 1
顶层变量:
val PI = 3.14
var关键字允许变量改变自己的值,但它的类型却是改变不了的.
var answer = 42
var answer = "HELLO"
说明:编译报错:编译器只会根据初始化器来推断变量的类型,在决定类型的时候不会考虑后续的赋值操作.
3
字符串模板
简单的变量名称
var a = 1
val s1 = "a is $a"
println(s1)
06-09 21:42:37.708 2092-2092/? I/System.out: a is 1
fun main(args: Array<String>) {
var name = if(args.size>0) args[0] else "kotlin"
println("Hello,$name!")
}
Hello,kotlin!
任意表达式
fun printSum(a: Int, b: Int){
println("sum of $a and $b is ${a + b}")
}
println(printSum(1, 2))
06-09 21:40:22.212 2007-2007/? I/System.out: sum of 1 and 2 is 3
双引号嵌套双引号(只要它们处在某个表达式的范围内,即花括号内)
fun printSum(a: Int, b: Int){
println("Hello,${if (a >b) a else "other" }")
}
printSum(1,2)
06-09 22:27:54.452 2465-2465/com.best.chapter_01 I/System.out: Hello,other
4
注释
// 这是一个行注释
/* 这是一个多行的
块注释。 */
5
类和属性
类
class Person(var name: String, var isMarried: Boolean)
说明: 这种类(只有数据没有代码)通常叫做值对象(不好理解,先搁置,回头再看)
属性
class Person(var name: String, var isMarried: Boolean)
说明:
① kotlin中属性是头等的语言特性,完全代替了字段和访问器方法.
② 声明一个属性和声明一个变量一样:使用val和var关键字:val代表属性是可读的,var代表属性是可变的.
③ 当你声明属性的时候,就声明了对应的访问器(只读属性有一个setter,而可写属性既有getter也有setter)
Kotlin使用Person类
val person = Person("Green", true);
println(person.name)
println(person.isMarried)
① 创建对象不用new
② 引用属性,不再需要调用getter和setter
③ setter的具体操作方法:
val person = Person("Green", true);
println(person.name)
println(person.isMarried)
person.name = "Tom"
person.isMarried = false
println(person.name)
println(person.isMarried)
6
自定义访问器
/**
* Created by FuKaiqiang on 2018-06-04.
*/
class Rectangle(var width: Int, var height: Int) {
var isRectanggle: Boolean = false
get() = width == height //声明属性的getter
}
val rectangle = Rectangle(5, 5)
println(rectangle.isRectanggle)
06-10 01:54:49.759 3355-3355/? I/System.out: true
7
源码布局:目录和包
- 包名: com.example.baidu
- 目录:
- ui: MainActivity SpashActivity
- util : AppUtil
- widget
- entity
...
说明:
① 包名下有目录,目录下有类,类中有方法等等.
② 每个类都有自己的包名,每个方法都有的引用路径.
③ 如上则是: Java中,目录层次结构照搬了包层次结构
- 包名:com.example.baidu
- example.kt
- shapes.kt
说明:
① kotlin中:包层次结构不需要遵循目录层次结构.
② kotlin中:可以把多个类放在同一个文件中,并且文件的名字可以随意定义,也没有对磁盘上源文件的布局强加任何限制.
③ 因为每个类代码都很小,所以kotlin建议把众多类放在一个.kt文件中即可.
8
表示和处理选择:枚举和"when"
强调: kotlin中使用"when"来替代java中的"switch".
声明枚举类:
enum class Colors {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
说明:
① 极少数kotlin声明比java使用了更多的关键字的例子:kotlin用了enum class 两个关键字,而java只有enum一个关键字.
② enum是一个所谓的软关键字:只有当它出现在class前面时才有特殊的意义,在其他地方可以把它当做普通的名称使用.
枚举并不是值得代表,可以给枚举类声明属性和方法
/**
* Created by FuKaiqiang on 2018-06-10.
*/
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 // 给枚举类定义一个方法
}
说明:
① 枚举常量在声明的时候,必须提供常量的属性值.
② 这个例子展示了 kotlin 中唯一需要使用分号的地方:如果在枚举中定义任何方法,就要使用分号把枚举常量列表和方法定义分开.
使用"when"处理枚举类
fun getMnemonic(color:Color) = //直接返回一个When表达式
when(color){ // 如果颜色和枚举常量相等就返回对应的字符串
Color.RED ->"Richard"
Color.ORANGE ->"Of"
Color.YELLOW ->"York"
Color.GREEN ->"Gave"
Color.BLUE ->"Battle"
Color.INDIGO ->"In"
Color.VIOLET ->"Vain"
}
println(getMnemonic(Color.GREEN))
06-10 10:50:24.277 5279-5279/? I/System.out: Gave
说明:
① when 是一个有返回值的表达式,这里直接返回when表示式的表达式函数体.
② 你不用书写break,避免因为漏写break而导致的bug.
③ 可以把多个值合并到同一个分支,只需要用逗号隔开这些值.
fun getWarmth(color: Color) = when (color) {
Color.RED, Color.ORANGE, Color.YELLOW -> "warm"
Color.GREEN -> "neutral"
Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold"
}
println(getWarmth(Color.ORANGE))
06-10 10:57:23.959 5374-5374/com.best.chapter_01 I/System.out: warm
说明:
① 上面这个例子用的是枚举的完整名称,即指定了枚举类的名称Color.
② 可以通过导入这些常量值和常量值所在的类的方法来简化代码.
import com.best.chapter_01.Color.* // 显式地导入枚举常量就可以使用它们的名称
fun getWarmth(color: Color) = when (color) {
RED, ORANGE, YELLOW -> "warm"
GREEN -> "neutral"
BLUE, INDIGO, VIOLET -> "cold"
}
println(getWarmth(Color.GREEN))
06-10 11:02:05.599 5462-5462/com.best.chapter_01 I/System.out: neutral
"when"结构中使用任意对象
import com.best.chapter_01.Color.*
fun mix(c1: Color, c2: Color) =
when (setOf(c1, c2)) { // when表达式的实参可以是任何对象,它被检查是否与分支条件相等
setOf(RED, YELLOW) -> ORANGE // 列举出能够混合的颜色对
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Dirty color") // 如果没有任何其他分支匹配这里就会执行
}
println(mix(RED, YELLOW))
06-10 11:09:59.130 5561-5561/? I/System.out: ORANGE
说明:
① java中的switch要求必须使用常量(枚举常量\字符串\数字字面值)作为分支条件.
② kotlin废弃了switch,使用when代替switch,允许使用任何对象.
③ kotlin标准库中有setOf函数,可以创建出一个Set,会包含所有指定为函数实参的对象.条目顺序不重要,只要两个set中包含一样的条目,它们就是相等的.
④ 上面这些例子都是等式检查,接下来会看到条件还可以是任意的布尔表达式.
使用不带参数的"when"
fun mixOptimized(c1: Color, c2: Color) =
when { // 没有实参传给when
(c1 == RED && c2 == YELLOW) ||
(c2 == RED && c1 == YELLOW) -> ORANGE
(c1 == YELLOW && c2 == BLUE) ||
(c2 == YELLOW && c1 == BLUE) -> GREEN
(c1 == BLUE && c2 == VIOLET) ||
(c2 == BLUE && c1 == VIOLET) -> INDIGO
else -> throw Exception("Dirty color") // 如果没有任何其他分支匹配这里就会执行
}
println(mixOptimized(BLUE, YELLOW))
06-16 22:43:26.391 1959-1959/? I/System.out: GREEN
说明:
① 如果每次调用函数的时候,就创建一些Set实例,那创建频繁就会出问题.
② 如果when表达式没有参数,分支条件就是任意的布尔表达式.这样可读性差一些,但是性能更好.
智能转换: 合并类型检查和转换
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 //显示地转换成类型Num 是多余的
return n.value
}
if (e is Sum) {
return eval(e.right) + eval(e.left) // 变量e被智能地转换了类型
}
throw IllegalArgumentException("Unknown expression")
}
println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
说明:
① 检查过一个变量是某种类型,后面就不再需要转换它,可以就把它当作你检查过的类型使用.实际上编译器进行了类型转换,这种行为称为智能转换.
② 使用 as 关键字来表示到特定类型的显示转换.
③ 当你对一个类的属性进行智能转换的时候,这个属性必须是一个val属性,而且不能有自定义的访问器.
④ 经过智能转换的值会用不同的背景颜色着重表示,这样就更容易发现这个值是事先检查过的.
重构: 用"when" 代替 "if"
kotlin没有三元运算符,因为if表达式有返回值.而当我们使用if的时候,可以使用表达式语法: 去掉return语句和花括号,使用if表达式作为函数体.
1.使用有返回值的 if 表达式
省略return
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")
}
println(eval(Sum(Num(1), Num(2))))
06-17 00:31:37.587 2492-2492/com.best.chapter_01 I/System.out: 3
省略花括号
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")
2.使用 "when" 代替 if 层叠
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value // 应用了 智能转换 检查实参类型的"when"分支
is Sum -> eval(e.left) + eval(e.right)
else -> throw IllegalArgumentException("Unknown expression")
}
说明:
① when表达式并不仅限于检查值是否相等----这里使用了另一种 when 分支的形式,允许你检查 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 // 如果 e 的类型是 Sum 就会返回这个表达式
}
else -> throw IllegalArgumentException("Unknown expression")
}
06-17 01:01:02.375 2872-2872/com.best.chapter_01 I/System.out: num: 1
06-17 01:01:02.375 2872-2872/com.best.chapter_01 I/System.out: num: 2
06-17 01:01:02.375 2872-2872/com.best.chapter_01 I/System.out: sum: 1 +2
06-17 01:01:02.375 2872-2872/com.best.chapter_01 I/System.out: 3
说明:
① 代码块中最后的表达式就是结果,在所有使用代码块并期望得到一个结果的地方成立.
② 这个规则对常规函数不成立 : 一个函数体要么具有不是代码块的表达式函数体,要么具有包含具体return 语句的代码块函数体.
9
迭代循环: "while" 循环和"for" 循环
"while" 循环 ( 无新特性 可略过)
while (condition){ // 当 condition 为 true 时执行循环体
/*...*/
}
do {
/*...*/
}while (condition) // 循环体第一次会无条件地执行.此后,当condition 为true时才执行
迭代数字: 区间和数列
var oneToTen = 1..10
说明:
① kotlin 没有常规的 for 循环 ,而用 区间 替代.
② 区间本质上是两个值之间的间隔,这两个值通常是数字: 一个是起始值 , 一个是结束值.使用 in 运算符表示区间.
③ kotlin的区间是包含的,即闭合的,意味着第二个值始终是区间的一部分.
④ 整数区间做的最基本的事情就是循环迭代其中所有的值.如果你能迭代区间中所有的值,这样的区间被称作数列.
fun main(args: Array<String>) {
for (i in 1 .. 100) {
print(fizzBuzz(i))
}
}
fun fizzBuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz "
i % 3 == 0 -> "Fizz "
i % 5 == 0 -> "Buzz "
else -> "$i "
}
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 ...
说明: 在android Activity中结果不会输出,不知道为什么,但是println却可以.
for (i in 100 downTo 1 step 2) {
print(fizzBuzz(i))
}
Buzz 98 Fizz 94 92 FizzBuzz 88 86 Fizz 82 ...
说明:
① 这是在迭代一个带步长的数列,它允许跳过一些数字.
② 步长也可以负数,这种情况下数列是递减而不是递增的.
③ 100 downTo 1 : 递减数列(步长为-1).step 把步长的绝对值变成了2 , 但方向保持不变(事实上,步长被设置成了 -2) .
④ .. 语法是闭合区间,而如果想使用半闭合区间(不包含指定结束的值),使用util函数即可. for ( x in 0 util size) == for ( x in 0 .. size - 1)
迭代 map
初始化并迭代 map
var binaryReps = TreeMap<Char, String>() // 使用TreeMap让键排序
for (c in 'A'..'F') { // 使用字符区间迭代从A到F之间的字符
var binary = Integer.toBinaryString(c.toInt()) // 把ASCII码转换成二进制
binaryReps[c] = binary // 根据键c把值存储到map中
}
for ((letter, binary) in binaryReps) { // 迭代map,把键和值赋给两个变量
println("$letter = $binary")
}
06-17 04:06:11.919 4184-4184/? I/System.out: A = 1000001
06-17 04:06:11.919 4184-4184/? I/System.out: B = 1000010
06-17 04:06:11.919 4184-4184/? I/System.out: C = 1000011
06-17 04:06:11.919 4184-4184/? I/System.out: D = 1000100
06-17 04:06:11.919 4184-4184/? I/System.out: E = 1000101
06-17 04:06:11.919 4184-4184/? I/System.out: F = 1000110
说明:
① .. 语法不仅可以创建数字区间,还可以创建字符区间.这里迭代从A到F的字符.
② for 循环允许展开迭代中的集合的元素(这里是 map 键值对集合) : 把展开的结果存储到了两个独立的变量中 : letter 是键, binary 是值.
③ 可以根据键来访问和更新map : 使用map[key] 读取值 , 并使用 map[key] = value 设置值,而不需要调用 get 和 put .
展开语法在迭代集合的同时跟踪当前项的下标
var list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()) {
println("$index: $element")
}
06-17 04:18:11.968 4300-4300/com.best.chapter_01 I/System.out: 0: 10
06-17 04:18:11.968 4300-4300/com.best.chapter_01 I/System.out: 1: 11
06-17 04:18:11.968 4300-4300/com.best.chapter_01 I/System.out: 2: 1001
使用 "in" 检查集合和区间的成员
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'
println(isLetter('q'))
println(isNotDigit('x'))
说明:
① in 运算符来检查一个值是否在区间中 , 或者 它的逆运算 !n 来检查这个值是否不在区间中.
② in 简洁的隐藏了标准库中的区间类的具体的实现逻辑
c in 'a' .. 'z' 变换成 a <= c && c <= z
③ in 也适用于 when 表达式
fun recognize(c: Char) = when(c){
in '0'..'9' ->"It is a digit !"
in 'a'..'z',in 'A'..'Z' ->"It is a letter!" // 可以组合多种区间
else -> "I don't know..."
}
println(recognize('8'))
06-20 10:59:29.402 3321-3321/com.best.chapter_01 I/System.out: It is a digit !
④ in 同样适用于集合
println("kotlin" in setOf("java","scala"))
06-20 11:02:01.495 3435-3435/? I/System.out: false
⑤补充: 区间不仅限于字符.也适用于支持实例比较操作的任意类(实现了java.lang.Comparable接口)
println("kotlin" in "java".."scala")
06-20 11:07:11.911 3527-3527/com.best.chapter_01 I/System.out: true
说明: 这种情况下,虽然不能列举出"java"和"kotlin"之间的字符串,但是仍然可以使用in 运算符检查一个其他对象是否属于这个区间.
10
kotlin 中的异常
与java类似,方法的调用者能捕获到这个异常并处理它;如果没有被处理,异常会沿着调用栈再次抛出.
val percentage = 110
if (percentage !in 0..100) {
throw IllegalArgumentException("A percentage value must be between
0 and100: $percentage")
}
java.lang.IllegalArgumentException: A percentage value must be between
说明:
① 不用new 关键字来创建异常实例.
② kotlin 中 throw 结构是一个表达式,可以作为另一个表达式的一部分使用:
var number = 50
val percentage =
if (number in 0..100)
number
else
throw IllegalArgumentException("A percentage value must be between
0 and100: $number")
println(percentage)
06-20 11:22:06.025 3988-3988/com.best.chapter_01 I/System.out: 50
"try" "catch" 和 "finally"
像在java中一样使用 "try"
fun readNumber(reader: BufferedReader): Int? { // 不必显式地指定这个函数可能抛出的异常
try {
val line = reader.readLine()
return Integer.parseInt(line)
} catch (e: NumberFormatException) { // 异常类型在右边
return null // 发生异常的情况下使用的值
} finally {
reader.close()
}
}
var reader = BufferedReader(StringReader("123"))
println(readNumber(reader))
06-20 11:28:57.529 4157-4157/? I/System.out: 123
说明:
① 和Java最大的区别是不用显示地抛出异常.
② kotlin 不区分受检异常和未受检异常.不用指定函数抛出的异常,而且可以处理也可以不处理异常. 受检异常: 比如 IOException就是一个受检异常.
"try" 作为表达式
fun readNumber(reader: BufferedReader) {
var number = try {
Integer.parseInt(reader.readLine()) // 没有异常发生时使用这个值
} catch (e: NumberFormatException) {
null // 发生异常的情况下使用的值
}
println(number)
}
var reader = BufferedReader(StringReader("not a number"))
readNumber(reader)
06-20 11:37:49.109 4262-4262/com.best.chapter_01 I/System.out: null
说明:
① 不同于if,try 引入表达式以后总是需要用花括号把语句主体括起来.
② 和其他语句一样,如果其主体包含多个表达式,那么整个try表达式的值就是最后一个表达式的值 , 比如 catch 里面的 null
11
综上
① fun 关键字用来声明函数 . val 关键字和 var 关键字分别用来声明只读变量和可变变量.
② 字符串模板帮你避免繁琐的字符串连接. 在变量名称前加上 $ 前缀 或者 用${ }包围一个表达式,来把值注入到字符串中.
③ 值对象类在kotlin中简洁的方式表示.
④ kotlin 中的 if 是带返回值的表达式.
⑤ when 表达式替代了 java 中的 switch ,而且更强大.
⑥ 检查过变量具有某种类型之后不必显示地转换它的类型 : 这就叫 智能转换.
⑦ for 在kotlin中更加方便,特别是需要迭代map的时候,又或者迭代集合需要下标的时候.
⑧ 1..5 会创建一个区间. 可以使用 in 运算符和 !in运算符来检查值是否属于某个区间.
⑨ kotlin 中的异常和java相似,除了kotlin不要求你声明函数可以抛出的异常.
以上是关于从点滴基础探究Kotlin的独特魅力的主要内容,如果未能解决你的问题,请参考以下文章