2.1、基本要素:函数和变量
1、函数
-
举例说明
-
HelloWrold.kt
-
fun main(args: Array<String>) { println("hello world!") }
-
-
分析
- 1、关键字 fun 用来声明 一个函数
- 2、参数的类型写在它的名称后面
- 3、函数可以定义在文件的最外层,不需要把它放在类中
- 4、数组就是类(和 Java不同,Kotlin没有声明数组类型的特殊语法)
- 5、使用 println代替了 System.out.println。Kotlin标准库给 Java标 准库函数提供了许多语法更简洁的包装,而 println 就是其中 一个
- 6、和许多其他现代语言一样,可以省略每行代码结尾的分号
-
-
函数结构
- 1、函数的声明以fun关键字开始;
- 2、函数名称紧随其后;
- 3、括号里面是参数列表(先写参数变量名,接着写后参数所属类型,用冒号分开);
- 4、参数列表后面是返回类型,用冒号分开
-
表达式函数体
-
可以让前面的函数变得更简单,因为它的函数体是由单个表达式构成的,可以用这个表达式作为完整的函数体,具体操作:
- 1、去掉花括号和 return 语句;
- 2、改为等号;
- 3、去掉返回类型
HTML学习的本质就是该是什么就用什么
-
代码
fun max2(a: Int, b: Int): Int = if (a > b) a else b
-
如果函数体写在花括号中,我们说这个函数有代码块体,如果它直接返回了 一个表达式,它就有表达式体
-
进一步简化,去掉返回类型
fun max3(a: Int, b: Int) = if (a > b) a else b
为什么可以去掉返回类型?
-
每一个表达式和变量都有类型
-
每个函数都有返回类型
-
对于表达式体函数来说,编译器会分析作为函数体的表达式,并把它的类型作为函数的返回类型,即使没有显示的写出来,这种分析通常被称作类型推导
-
表达式函数体小结
- 使用范围:单个表达式
- 使用场景
- 简单的单行函数中
- 对更复杂的单个表达式求值的函数中
-
-
2、变量
-
Kotlin鼓励你使用不可变的数据,而不是可变的数据
-
java中声明变量是以类型开始,但是kotlin中许多变量声明的类型可以省略,所以:
- 1、kotlin中变量的声明是从关键字开始,
- 2、然后是变量名称,
- 3、最后加上类型(如果变量没有初始化,需要显示地指定它的类型),
- 4、再加上变量的初始值(val变量要初始化)
-
不可变变量val
-
只能初始化一次
-
val answer: Int = 42
-
如果编译器能确保只有唯一一条初始化语句会被执行,可以根据条件使用不同的值来初始化它
val message: String if(canPerformOperation()){ message = "Success" }else{ message = "Failed" }
-
尽管 val 引用自身是不可变的,但是它指向的对象可能是可变的。例如,下面这段代码是完全有效的
//声明不可变引用 val languages= arrayListOf("Java") //改变引用指向的对象 languages.add("Kotlin")
-
-
-
可变变量var
-
变量值可以多次改变
- 即使var关键字允许变量改变自己的值,但它的类型却是改变不了的
-
3、更简单的字符串格式化:字符串模板
-
代码
-
val name = if (args.size > 0) args[0] else "Kotlin" println("hello $name") //打印$,对$进行转义 println("hello \$name") //复杂表达式 println("hello ,${args[0]}") //嵌套双引号 println("hello,${if(args.size>0) args[0] else "Love Kotlin"}")
-
4、语句和表达式
-
在Kotlin中,if是表达式,而不是语句
-
表达式和语句的区别
- 表达式有值,能作为另一个表达式的一部分使用
- 语句是包围着它的代码块中的顶层元素,没有自己的值
-
java中
- 所有的控制结构都是语句
- 赋值操作是表达式
-
kotlin中
- 除了for/do/do while以外,大多数控制结构都是表达式
- 赋值操作是语句
2.2、类和属性
1、属性
-
在Kotlin中,public是默认的可见性,所以可以省略
-
在Kotlin中,属性是头等的语言特性,完全替代了字段和访问器方法
-
在类中声明一个属性和声明一个变量一样:使用val和var关键字
-
例子
-
class Person(name:String,isMarried:Boolean) { val name:String="Bob" //为什么一定要初始化呢? var isMarried:Boolean=false }
-
val person=Person("Jon",true) println(person.name) println(person.isMarried)
-
-
调用构造方法不需要关键字new
-
可以直接访问属性,但调用的是getter
2、自定义访问器
-
属性访问器的自定义实现
-
假如你声明这样一个矩形,它能判断自己是否是正方形。不需要一个单独的字段来存储这个信息,因为可以随时通过检查矩形的长宽是否相等来判断:
-
代码
-
class Rectangle(val height: Int, val width: Int) { //它这里为什么可以直接写一个get()方法呢,Boolean后面直接跟着get,没有什么衔接的吗 val isSquare: Boolean get() { return height == width } }
-
属性isSquare不需要字段来保存它的值,它只有一个自定义实现的getter。它的值是每次访问属性的时候计算出来的
-
注意,不需要使用带花括号的完整语法,也可以这样写get()=height==width,对这个属性的调用依然不变
-
val rectangle=Rectangle(41,43) println(rectangle.isSquare)
-
-
3、kotlin源码布局:目录和包
-
java把所有的类组织成包。Kotlin和java有类似的包的概念,每一个Kotlin文件都能以一条package语句开头
-
而文件中定义的所有声明(类、函数及属性)都会被放到这个包中
-
Kotlin不区分导入的是类还是函数,它允许使用import关键字导入任何种类的声明。可以导入顶层函数的名称
-
例子
-
Rectangle.kt
package 二.Kotlin基础 import java.util.* class Rectangle(val height: Int, val width: Int) { //它这里为什么可以直接写一个get()方法呢,Boolean后面直接跟着get,没有什么衔接的吗 val isSquare: Boolean get() = height == width } fun createRandomRectangle():Rectangle{ val random= Random() return Rectangle(random.nextInt(),random.nextInt()) }
- Test.kt
package 二.Kotlin基础 import 二.Kotlin基础.createRandomRectangle fun main(args: Array<String>) { //如果是同一个包名下的kt文件,可以直接使用其他类的顶层函数 println(createRandomRectangle().isSquare) } >>> false
-
-
2.3、表示和处理选择:枚举和“when”
1、枚举
-
声明一个简单的枚举类
-
enum class Color { RED,ORANGE,YELLOW,GREEN,BLUE }
-
-
这是极少数Kotlin声明比Java使用了更多关键字的例子:Kotlin用了 enum class两个关键字,而Java只有enum一个关键字
-
Kotlin中,enum是一个所谓的软关键字:只有当它出现在class前面时才有特殊的意义,在其他地方可以把它当做普通的名称使用
-
可以给枚举类声明属性和方法
-
代码
//声明枚举常量的属性 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);//这里必须要有分号 //给枚举类定义一个方法 fun rgb() = (r * 256 + g) * 256 + b } >>> println(Color.BLUE.rgb()) 255
-
-
Kotlin语法中唯一必须使用分号的地方:如果要在枚举类中定义任何方法,就要使用分号把枚举常量列表和方法定义分开
2、使用“when”处理枚举类
-
when是一个有返回值的表达式,因此可以写一个直接返回when表达式的表达式体函数
-
代码
-
/** * 使用when来选择正确的枚举值 * (直接返回一个“when”表达式) */ fun getMnemonic(color: Color) = //如果颜色和枚举常量相等就返回对应的字符串 when (color) { Color.RED -> "Richard" Color.ORANGE -> "Of" Color.YELLOW -> "York" Color.GREEN -> "Gave" Color.BLUE -> "Battle" }
-
-
和Java不一样,不需要在每个分支都写上break语句(在Java中遗漏break通常会导致bug)
-
也可以把多个值合并到同一个分支,用逗号隔开
-
/** * 在一个when分支上合并多个选项 */ fun getWarmth(color: Color) = when (color) { Color.RED, Color.ORANGE, Color.YELLOW -> "warm" Color.GREEN -> "neutral" //Color.BLUE->"cold" //常量如果没有全部列出来,就需要用else语句统一处理下 else -> "unKnow" }
-
3、在“when”结构中使用任意对象
-
when允许使用任何对象
-
代码
-
fun mix(c1: Color, c2: Color) = //setOf是Kotlin集合类中的一个方法 //“when”表达式的实参可以是任何对象,它被检查是否与分支条件相等 when (setOf(c1, c2)) { //列举出能够混合的颜色对 setOf(Color.RED, Color.YELLOW) -> Color.ORANGE setOf(Color.YELLOW, Color.BLUE) -> Color.GREEN //如果没有任何其他分支支配这里就会执行 else -> throw Exception("Dirty color") }
-
-
Kotlin标准库中有一个setOf函数可以创建出一个Set,它会包含所有指定为函数实参的对象,set这种集合的条目顺序并不重要,只要两个set中包含一样的条目,它们就是相等的
-
4、使用不带参数的“when”
-
代码
-
/** * 不带参数的when */ fun mixOptimized(c1: Color, c2: Color) = //没有实参传给"when" when { (c1 == Color.RED && c2 == Color.YELLOW) || (c1 == Color.YELLOW && c2 == Color.RED) -> Color.ORANGE (c1 == Color.YELLOW && c2 == Color.BLUE) || (c1 == Color.BLUE && c2 == Color.YELLOW) -> Color.GREEN else -> throw Exception("Dirty color") }
-
-
如果没有给 when 表达式提供参数,分支条件就是任意的布尔表达式
-
这种写法的优点是不会创 建额外的对象,但代价是它更难理解
5、智能转换:合并类型检查和转换
-
例子
-
加法算术表达式求值,类似(1+2)+4,这个表达式只包含一种运算,对两个数字求和
-
如何编码呢?把它们存储在一个树状结构中,结构中的每个节点要么是一个求和(sum)要么是一个数字。Num永远都是叶子节点,而Sum节点有两个子节点:它们是求和运算的两个参数
-
下面的代码清单展示了一种简单的类结构来表示:一个叫作Expr的接口和它的两个实现类Num和Sum。注意Expr接口没有声明任何方法,它只是一个标记接口,用来给不同种类的表达式提供一个公共的类型
-
声明类的时候,使用一个冒号(:)后面跟上接口名称,来标记这个类实现了这个接口
-
//计算两个数的和 interface Expr class Num(val value: Int) : Expr class Sum(val left: Expr, val right: Expr) : Expr
-
-
Sum存储了Expr类型的实参left和right的引用,在这个例子中,它们要么是Num要么是Sum。为了存储前面提到的表达式(1+2)+4,需要创建这样一个对象Sum(Sum(Num(1),Num(2)),Num(4)),树状图如下
-
计算表达式的值
/** * 计算两个数和,这里有递归计算 */ fun eval(e: Expr): Int { if (e is Num) { return e.value } if (e is Sum) { return eval(e.left) + eval(e.right) } throw IllegalAccessException("unKnow expression") } //计算两个数的和:(1+2)+4 println(eval(Sum(Sum(Num(1), Num(2)), Num(4))));
-
Expr接口有两种实现,所以为了计算出表达式的结果值,得尝试两种选项
- 1、如果表达式是一个数字,直接返回它的值
- 2、如果是一次求和,得先计算左右两个表达式的值,再返回它们的和
-
在Kotlin中,使用is检查来判断一个变量是否是某种类型,is检查和Java中的instanceOf相似
-
如果你检查过一个变量是某种类型,后面就不需要再转换它,可以就把它当当作你检查过的类型使用。事实上编译器为你执行了类型转换,我们把这种行为成为智能转换
-
前提条件
- 1、智能转换只在变量经过is检查之后不再发生变化的情况下有效
- 2、当对类的属性进行智能转换的时候,属性必须是val,而且不能有自定义的访问器
-
使用as关键字表示到特定类型的显示转换
-
val n=e as Num
-
6、重构:用“when”代替“if”
-
源码
-
fun eval(e:Expr):Int{ if(e is Num){ return e.value } if(e is Sum){ return eval(e.left)+ eval(e.right) } throw IllegalAccessException("unKnow expression") } fun eval2(e:Expr):Int{ if(e is Num){ e.value } if(e is Sum){ eval(e.left)+ eval(e.right) } throw IllegalAccessException("unKnow expression") }
- 因为if语句有返回值,这意味着你可以用表达式语法,去掉return语句和花括号,使用if表达式作为函数体
-
-
使用“when”表达式
-
fun eval3(e:Expr):Int= when(e){ is Num -> e.value is Sum -> eval3(e.left)+ eval3(e.right) else -> throw IllegalAccessException("unKnow expression") }
-
fun eval3(e:Expr):Int { return when (e) { is Num -> e.value is Sum -> eval3(e.left) + eval3(e.right) else -> throw IllegalAccessException("unKnow expression") } }
-
when语句是表达式,表达式,有返回值
-
-
When表达式并不仅限于检查值是否相等,它允许你检查when实参值的类型
7、代码块作为“if”和“when”的分支
-
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 IllegalArgumentException("Unknown expression") }
-
>>> println(evalWithLogging(Sum(Sum(Num(1), Num(2)), Num(4)))); num:1 num:2 sum:1+2 num:4 sum:3+4 7
-
-
规则
- 代码块中最后的表达式就是结果,在所有使用代码块并期望得到一个结果的地方成立。同样的规则对try主体和catch子句也有效
- 这个规则对常规函数不成立,一个函数要么具有不是代码块的表达式函数体,要么具有包含显式return语句的代码块函数体
2.4、迭代事物:“while”循环和“for”循环
1、while循环
-
kotlin中有while循环和do-while循环,语法和java语法没什么区别
2、迭代数字:区间和数列
-
Kotlin中没有常规的Java for循环,在这种循环中,先初始化变量,在循环的每一步更新它的值,并在值满足某个限制条件时退出循环。为了替代这种最常见的循环用法,Kotlin使用了区间的概念
-
区间
- 区间本质上就是两个值之间的间隔,这两个值通常是数字:一个起始值,一个结束值。使用..运算符表示区间
- val oneToTen=1..10
- 注意Kotlin的区间是包含的或者闭合的,意味着第二个值始终是区间的一部分
-
数列
- 你能用整数区间做的最基本的事情就是循环迭代其中所有的值。如果你能迭代区间中所有的值,这样的区间被称作数列
-
例子
-
//区间遍历1..100区间 for(i in 1..100){ println(fizzBuzz(i)) } fun fizzBuzz(i: Int) = when { i % 15 == 0 -> "FizzBuzz" i % 3 == 0 -> "Fizz" i % 5 == 0 -> "Buzz" else -> "$i" }
-
when可以不带参数,不带参数,直接根据表达式的结果来判断
-
-
例子2
-
迭代一个带步长的数列它允许跳过一些数字,步长可以是附属,这种情况下数列是递减而不是递增的
-
//只遍历100以内的偶数 for(i in 100 downTo 1 step 2){ println(fizzBuzz(i)) }
-
100 downTo 1是递减的数列(步长为-1),然后setp把步长的绝对值变成了2,但方向保持不变(事实上步长被设置成了-2)
-
-
语法
- 写法 for in
- 区间 1..10
- downTo
- step
- until
- withIndex()
-
...语法始终创建的是包含结束值的区间,假如不想包含结束值,使用until函数可以创建这样的区间
-
例子
-
val size=5 for(x in 0 until size){ print("$x,") } >>> 0,1,2,3,4,
-
3、迭代map
-
例子
-
//迭代map val binaryReps= TreeMap <Char,String>() fun binaryFor() { for (char in 'A'..'F') { //把ASCII码转换成二进制 val binary=Integer.toBinaryString(char.toInt()) //存储到map中 binaryReps[char]=binary } for((key,value) in binaryReps){ println("$key=$value") } }
- 使用TreeMap让键排序
- ..语法不仅可以创建数字区间,还可以创建字符区间,这里使用它迭代从A到F所有字符,包括F
-
-
for循环允许展开迭代中的集合元素,(这里展开的是map的键值对元素),把展开的元素存储到了两个独立的变量中,key是键,value是值
-
根据键来访问和更新 map 的简明语法,可以使用 map[key]读取值,并使用 map[key] = value 设置它们,而不需要调用 get 和 put
- binaryReps[c]=binary 等价于它的Java版本 binaryReps.put(c,binary)
-
迭代集合时使用当前下标,不需要创建一个单独的变量来存储下标,使用withIndex()语法
-
//withIndex集合下标 val list= arrayListOf("10","11","1001") for((index,element) in list.withIndex()){ println("$index:$element") } >>> 0:10 1:11 2:1001
-
4、使用“in”检查集合和区间的成员
-
使用 in 运算符来检查 一个值是否在区间中,或者它的逆运算,! n,来检查这 个值是否不在区间中
-
例子1
-
fun isLetter(c:Char)=c in 'a'..'z'|| c in 'A'..'Z' println(isLetter('&')) >>> false
-
-
例子2
-
fun isNotDigit(c:Char)=c !in '0'..'9' println(isNotDigit('9')) >>> false
-
-
in和! in运算符也适用于when表达式
-
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" } println(recognize('√')) >>> I don't know
-
5、区间也不仅限于字符
-
假如有一个支持实例比较操作的任意类(实现了 java. lang . Comparable 接口),就能创建这种类型的对象的区间。如果是这样的区间, 并不能列举出这个区间中的所有对象
-
例子
-
是否可以列举出“ Java” 和“ Kotlin”之间所有的字符串?答案是不能。 但是仍然可以使用in运算符检查一个其他的对象是否属于这个区间 :
-
println(”Kotlin” in "Java” .. "Scala") >>> true
-
注意,这里字符串是按照字母表顺序进行比较的,因为String就是这样实现Comparable接口的
-
-
6、in检查也同样适用于集合
-
//这个集合不包含字符串“Kotlin” println("Kotlin" in setOf("Java","Scala")) >>> false
2.5、kotlin中的异常
1、Kotiin 中异常处理语句
Kotiin 中异常处理语句的基本形式和 Java 类似,抛出异常的方式也不例外
-
val percentage = -1 if (percentage !in 0..100) { throw IllegalArgumentException("A percentage value must be between 0 and 100:$percentage") } >>> Exception in thread "main" java.lang.IllegalArgumentException: A percentage value must be between 0 and 100:-1 at 二.Kotlin基础.HelloWorldKt.main(HelloWorld.kt:58)
和其他所有类一样,不必使用new关键字来创建异常实例
2、throw结构是一个表达式
throw能作为另一个表达式的一部分使用
-
fun exceptionFun2(number: Int) { val percentage = if (number in 0..100) number else throw IllegalAccessException("A percentage value must be between 0 and 100: $number") }
3、try、catch、finally
-
和Java一样,使用带有catch和finally子句的try结构来处理异常
-
例子
-
从给定的文件中读取一行,尝试把它解析成一个数字,返回这个数字,或者当这一行不是一个有效数字时,返回null
-
//try catch finally fun readNumber(reader:BufferedReader):Int?{//不必显式地指定这个函数可能抛出的异常 try{ val line=reader.readLine() return Integer.parseInt(line) } catch (e:NumberFormatException){ return null } finally { //finally的作用和java中的一样 reader.close() } } val reader=BufferedReader(StringReader("239")) println(readNumber(reader)) >>> 239 val reader=BufferedReader(StringReader("not a number")) println(readNumber(reader)) >>> null
-
-
和java最大的区别就是throws子句没有出现在代码中:如果用Java来写这个函数,你会显式地在函数声明后写上throws IOException。你需要这样做的原因是IOException是一个受检异常。在Java中,这种异常必须显式地处理。必须声明你的函数能抛出的所有受检异常。如果调用另外一个函数,需要处理这个函数的受检异常,或者声明你的函数也能抛出这些异常
-
kotlin不区分受检异常和未受检异常,不用指定函数抛出的异常,而且可以处理也可以不处理异常,这种设计是基于Java中使用的异常的实践做出的决定
4、try作为表达式
-
Kotlin中的try关键字就像if和when一样,引入了一个表达式,可以把它的值赋值给一个变量。不同于if,你总是需要用花括号把语句主体括起来,和其他语句一样,如果其主体包含多个表达式,那么整个try表达式的值就是最后一个表达式的值
-
例子
-
//try作为表达式 fun readNumber2(reader:BufferedReader){ val number=try{ Integer.parseInt(reader.readLine()) }catch (e:NumberFormatException){ return } println("readNumber2,$number") }
-
val reader2 = BufferedReader(StringReader("not a number")) readNumber2(reader2)
结果没有打印,因为走到catch语句的return了,println代码没有走
-
val reader3 = BufferedReader(StringReader("999")) readNumber2(reader3) >>> readNumber2,999
-
将try作为一个表达式,表达式有值,赋值给number
-
将return语句放在catch代码块中,函数在执行catch代码块后不再执行,若想代码可以继续执行,在catch子句返回一个值,见例子2
-
-
例子2
-
//try作为表达式 fun readNumber2(reader:BufferedReader){ val number=try{ Integer.parseInt(reader.readLine()) }catch (e:NumberFormatException){ 999999 } println("readNumber2,$number") }
这里竟然直接写9999就好
-
2.6、小结
1、fun关键字用来声明函数。val关键字和var关键字用来声明只读变量和可变变量
2、字符串模板帮助你避免烦琐的字符串连接。在变量名称前加{}包围一个表达式,来把值注入到字符串中
3、值对象类在Kotlin中以简洁的方式表示
4、熟悉的if现在是带返回值的表达式
5、when表达式类似于Java中的switch但功能更强大
6、在检查过变量具有某种类型之后不必显式的转换它的类型:编译器使用智能转换自动帮你完成
7、for、while和do-while循环与java类似,但是for循环现在更加方便,特别是当你需要迭代map的时候,又或是迭代集合需要下标的时候。
8、简洁的语法1..5会创建一个区间。区间和数列允许Kotlin在for循环中使用同一的语法和同一套抽象机制,并且还可以使用in运算符和!in运算符来检查值是否属于某个区间
9、Kotlin中的异常处理和Java非常类似,除了Kotlin不要求你声明函数可以抛出的异常。
附件: