Kotlin学习与实践 (二)基础
1、 函数和变量
直奔主题不啰嗦
* a.关键字 fun 用来声明函数。
* b.参数的类型写在参数名字的后面。
* c.函数可以定义再文件的最外层,不需要把它放入类中。
* d.数组就是类。 和Java不同Kotlin没有声明数组类型的特殊语法。
* f.使用println代替了System.out.println。 Kotlin标准库给Java的标准库函数提供了许多语法更简洁的包装,而println就是其中一个。
* g.和许多现代语言一样,Kotlin可以省略每行代码结尾的分号
fun main(args: Array<String>) { println("Hello Kotlin !") println(max(6, 8)) }
* 参数的类型和方法返回值的类型都声明在后面,中间用:隔开
* 没有返回值的方法声明的时候就不需要再方法末尾加上返回值类型
* 函数(方法)声明的总结:
* 使用 “fun” 来声明函数,函数名称紧随其后,括号括起来的是参数列表,参数列表的后面跟着返回类型,返回类型和参数列表之间用":"隔开。
fun max(a: Int, b: Int): Int { return if (a > b) a else b }
* 语句和表达式:
* 在Kotlin中,if是表达式,而不是语句。语句和表达式的区别在于:
* 表达式有值,并且可以作为另一个表达式的一部分使用;
* 而语句总是包围着它的代码块中的顶层元素,并没有自己的值。
* 再Java中所有的控制语句都是语句,而再Kotlin中,除了循环(for\do\while)以外的大多数控制结构都是表达式。
* 另一方面,Java中的赋值语句是表达式,在Kotlin中反而变成了语句。
* 表达式函数体
* 函数体由单个表达式构成的,可以用这个表达式作为完成的函数体,并去掉花括号和return。
* 如果函数体写在花括号内,我们说这个函数有代码块体。如果它直接返回了一个表达式,他就是表达式体。
fun max2(a: Int, b: Int): Int = if (a > b) a else b
* 对于表达式函数来说,编译器会分析作为函数体的表达式,并把它的类型作为函数的返回类型,即使没有显式地写出来。
* 这种分析通常被称作“类型推导”
* 注意:只有表达式函数的返回类型可以省略。对于有返回值的代码块体函数,必须显式地写出返回类型和return语句。
fun max3(a: Int, b: Int) = if (a > b) a else b fun min(a: Int, b: Int) = if (a < b) a else b fun min2(a: Int, b: Int): Int = if (a < b) a else b fun min3(a: Int, b: Int): Int { return if (a < b) a else b }
2、变量
* 在Java中要先声明变量的类型 然后加上关键字 最后是变量的名称 例如 public String name = "Mauiie"
* 在Kotlin 中声明变量的时候以关键字开始,然后是变量名称,最后加上变量类型(类型可以省去) 例如 val/var name:String = "Mauiie"
* 和表达式一样如果不声明变量的类型,Kotlin 会自动分析变量的类型 类型推导 例如 var/val name = "Mauiie"
val answer2: Int = 50 //省略写法 val question = "this is a String variate" val answer = 42 fun test() { //如果声明的变量没有初始化器,就必须显示的声明他的类型 val testAnswer: Int testAnswer = 42 }
* Kotlin 中声明变量有两个关键字
*
* val (来自value) -- 不可变引用。使用val 声明的变量不可以再次被赋值. 相当于Java 中的 final 变量
* var (来自variable) -- 可变引用。 这种变量的值可以被改变. 相当于Java中的非 final 变量
* 尽管 var 允许变量改变自己的值,但是不能改变变量的类型。
/* *在定义了val 变量的代码块执行期间,val变量只能进行唯一一次初始化,但是如果编译器能确保唯一一条初始化语句会被执行 * 可以根据条件使用不同的值来初始化它 * */ fun testValWithMoreThanOneValue(a: Int) { val message: String if (a < 5) { message = "a > 10" } else if (a in 6..14) { message = "6 <= a <= 14" } else { message = "a > 15"; } } /* * 另外尽管 val 引用自身是不可变的,但是他指向的对象是可变的,这点和Java 的final一样,例如 * */ fun testValChange() { val languages = arrayListOf("Java") languages.add("PHP") }
3、package
* 在Kotlin中 每一个Kotlin文件都能以一条 package语句开头,而文件中的所有的声明(类、函数、属性)都在这个包中
* 如果其他文件中定义的也是同一个包,这个文件则可以直接使用它们 如果package不一样则需要导入(import)
* Kotlin 不区分导入的是类还是函数,而且它允许使用 import 关键字导入任何种类的声明。可以直接导入顶层函数的名称
* Kotlin 中包层级结构不需要遵循目录层级结构 但是大多数情况下 我们还是要遵循目录层级关系的
package com.mauiie.kotlin.chapter1basic import java.util.Random class Rectangle1(val height: Int, val width: Int) { val isSquauare: Boolean get() = height == width } fun createRectangle(): Rectangle1 { val random = Random() return Rectangle1(random.nextInt(), random.nextInt()) } fun main(args: Array<String>) { println(createRectangle().isSquauare) }
4、字符串模板
* 字符串模板Kotlin可以让你再字符串字面值中引用局部变量,只需要再局部变量前加上字符 $ ,
* 而且不仅仅限于变量,还可以引用表达式,只要把表达式使用花括号括起来
fun main(args: Array<String>) { val name = if (args.size > 0) args[0] else "Kotlin" println("Hello $name") } fun testStringTemplate(): String { val strings = arrayListOf<String>() strings.add("jaja") strings.add("yihuihuijia") val templates = "haha , ${if (strings.isEmpty()) "dsa" else "13245"} ! jiu dian ban xia ban hui jia" return "Hello , ${strings.size}" }
5、类和属性
* Java中bean或者pojo类(只有数据没有其他代码)通常被称为值对象
* 再Kotlin中,public 是默认可见性,所以在声明类的时候可以省略 public 下面就是Java和Kotlin声明值对象的对比
//Java //public class Persin{ // private final String name; // // public Persion(String name){ // this.name = name; // } // public String getName(){ // return name; // } // //} class Person(val name: String)
* 在Kotlin中,字段和其访问器的组合常常被叫做属性。
* 再Kotlin中属性是头等语言特性,完全代替了字段和访问器方法。在类中声明一个属性和声明一个变量一样:使用var 和 val。
* 声明var对象是可变的(既有getter 又有 setter),声明的val对象是只读的(只有 getter)。
class Student( val name: String, var isMerried: Boolean )
类的使用
* 构造省去 Java中的new 关键字,直接跟构造函数
* 直接访问或者操作属性 不用调用 getter 或者 setter方法
fun useClass() { val persin = Person("Bob") val student = Student("John", false) student.isMerried = true // student.name = "dsa" val 不能修改 println(persin.name) println(student.name + student.isMerried) }
自定义访问器
* 下面的类中的属性 isSquare 不需要字段来保存它的值。它只有一个自定义的实现的 getter,它的值每次都是算出来的
class Rectangle(val height: Int, val width: Int) { val isSquare: Boolean get() { return height == width } }
6、enum和when
* Kotlin 使用 enum class 两个关键字来声明枚举类 而 Java中只需要一个enum关键字
* Kotlin 中 enum 是一个软关键字,它必须和 class一起才有特殊意义,在其他地方仍可以使用做变量名。
enum class Color1 { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }
* 和Java一样,Kotlin 枚举并不只是值的列表,可以给枚举声明属性和方法
enum class Color(val r: Int, val g: Int, val b: Int) { RED(255, 0, 0), ORANGE(155, 165, 0), YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0, 0, 255), INDIGO(75, 0, 130), VIOLET(238, 130, 238); //这是Kotlin中唯一使用分号的地方:如果要在枚举中定义任何方法,就要使用分号把枚举常量列表和方法定义分开 fun rgb() = (r * 256 + g) + 256 + b }
* Kotlin中的when 语句相当于 Java中的 switch 语句
* 和 if 相似 Kotlin 语句也是表达式,因此可以写一个直接返回when 表达式的表达式体函数
* 而且还不需要再分支语句后面写上break 语句
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" }
* 导入Color 的枚举常量 之后可以直接使用
import com.mauiie.kotlin.chapter1basic.Color.* fun getCold(color: Color) = when (color) { RED, ORANGE, YELLOW -> "warm" GREEN -> "neutral" BLUE, INDIGO, Color.VIOLET -> "cold" }
* 和Java的switch语句不一样之处在于,switch语句必须使用常量(枚举常量、字符串或者数字字面值)
* 而Kotlin允许使用任何对象
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") } }
不带参数的“when”
*如果没有给when表达式参数,分支条件就是任意的布尔表达式。
* mixOptimized 与 mix 做的是同样的事 不带参数的优点是不会创建多余的对象(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") } }
7、智能转换:合并类型检查和转换
/** * Expr 是一个没有声明任何方法的接口 * 它只是一个标记接口,用来给不用的表达式提供一个公共的类型 */ interface Expr /** * Num 是接口Expr的一个实现类 实现接口使用一个冒号(:) + 接口 既可标记一个类实现了一个接口 */ class Num(val value: Int) : Expr class Sum(val left: Expr, val right: Expr) : Expr
* 在Kotlin中,使用 is 检查来判断一个变量是否是某个类型 它和Java的instanceOf 相似
* 在Java中如果你已经检查过一个变量是某种类型并且要把它当做这种类型来访问其成员时,在instanceOf检查之后还需要显式地加上类型转换。
* 如果最初的变量会使用超过一次,常常选择把类型转换之后的结果存储在另一个单独的变量中
* 在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 IllegalArgumentException("Unknown expression ") }
智能转换只在变量经过is检查之后不再发生变化的情况下有效!当你对一个类的属性进行只能转换的时候,就想上面的例子一样这个属性必须是val属性,而且不能有自定义的访问器,否则每次对属性的访问都能返回同样的值将无从验证
* Kotlin的if(a>b) a else b 和Java的 a>b?a:b 一样
* Kotlin 没有三元运算符,因为if表达式有返回值,这一点和Java不同
* 所有我们可以把上面的 eval 方法改造成表达式函数 (使用表达式体语法去掉return和花括号使用if表达式作为函数体)
fun Eval(e: Expr): Int = if (e is Num) { e.value } else if (e is Sum) { Eval(e.left) + Eval(e.right) } else { throw IllegalArgumentException("Unknown expression ") }
* 如果if 分支中只有一个表达式,花括号也是可以省略的,如果if 分支是一个代码块,代码块中的最后一个表达式会被作为结果返回
fun EVAL(e: Expr): Int = if (e is Num) e.value else if (e is Sum) EVAL(e.left) + EVAL(e.right) else throw IllegalArgumentException("Unknown expression ")
* when 表达式不仅可以用于检查值是否相等 还允许检车实参值的类型(类型检查也应用了一次智能转换所以不需要额外的类型转换就可以访问Sum和Num的成员变量)
fun evalByWhen(e: Expr): Int = when (e) { is Num -> e.value is Sum -> eval(e.left) + eval(e.right) else -> throw IllegalArgumentException("Unknown expression") }
* if 和 when 都可以使用代码块作为分支体。 这种情况下代码块中的最后一个表达式就是结果。
* “代码块中最后的表达式就是结果” 再所有使用代码块并期望得到一个结果的地方都成立!比如 try catch、lambda
* 但是,但是,但是:这个规则对常规的函数不成立!!!! 一个函数要么具有不是代码块的表达式函数体,要么具有包含显示return语句的代码块函数体
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("num :$left + $right") println("num :${evalWithLogging(e.left) + evalWithLogging(e.right)}") left + right } else -> { println("IllegalArgumentException") throw IllegalArgumentException("Unknown expression") } }
8、循环迭代
* Kotlin中没有常规的JAVA 的for循环,在这种循环中先初始化一个变量,再循环的每一步更新变量的值,并在值满足一定条件的时候退出循环。
* 为了替代Java的这常规的for循环,Kotlin退出了“区间”的概念
* 区间本质上就是两个值之间的间隔,这两个值通常是数字,一个是起始值,一个是结束值,中间使用..运算符来标识区间
* 注意Kotlin的区间是包含(闭合)的,意味着第二个值始终是区间的一部分
//整数区间能做的最基本的事就是循环迭代其中所有的值。 如果你能迭代区间中所有的值,这样的区间称作数列 val oneToTen = 1..10
为了后面的例子先给出一个基础函数
fun fizzBuzz(i: Int) = when { i % 15 == 0 -> "FizzBuzz" i % 3 == 0 -> "Fizz" i % 5 == 0 -> "Buzz" else -> "$i" }
* 最简单的迭代
fun easyIteration() { for (i in 1..100) { println(fizzBuzz(i)) } }
* 迭代一个带步长的区间 步长为2,每次正向(递增)2, 步长还可以为负值(递减)
fun iterationWithStep() { for (i in 1..100 step 2) { println("this is $i") println(fizzBuzz(i)) } }
* 迭代map for循环允许展开迭代集合的元素(在这个例子中:展开的是map键值对集合)。
* 把展开的结果存储到了两个变量中:letter 存储键,binary 存储值
fun mapIteration() { val binaryReps = TreeMap<Char, String>() for (c in 'A'..'Z') { val binary = Integer.toBinaryString(c.toInt()) binaryReps[c] = binary //根据键来访问个更新map的简明语法 : 使用map[key] 来读取值,使用map[key] = value 来设置它的值 } for ((letter, binary) in binaryReps) { println("$letter = $binary") } }
* 同样展开迭代的方法一样可以用来迭代集合的同事追踪当前项的下标,不需要像Java 一样建一个单独的变量来存储它
fun listIteration() { val sList = arrayListOf("10", "11", "1001") for ((index, element) in sList.withIndex()) { println("$index : $element") } }
关键字 in 除了能像上面的例子中用于迭代 in 还可以用来检查区间或者几个是否包含了某个值
* 使用in关键字来检查一个值是否在区间(集合)中,或者它的逆运算,!n 来检查这个值是否不在区间(集合)中
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z' //可以变换成 a <= c && c >= z fun isNotDigit(c: Char) = c !in '0'..'9'
* in 和 !in 运算也适用于when
fun recognize(c: Char) = { when (c) { in '0'..'9' -> "It's a digit" // in 'a'..'z' -> "It's a letter" // in 'A'..'Z' -> "It's a letter" in 'a'..'z', in 'A'..'Z' -> "It's a letter" else -> "I don't konw" } }
注意 : 区间不仅限于字符。 假如有一个支持实例比较的任意类(实现了 java.lang.Comparable 接口),就能创建这样的区间。
如果是这样的区间,并不能列举出这个区间中的所有对象
同样 in 检查也适用于集合
fun testIn() { println("Kotlin" in "Java" .. "Scala") println("Kotlin" in setOf("Java","Kotlin","Swift","C")) }
9、异常
* Kotlin 的异常处理和Java以及其他许多语言的处理方法相似。一个函数可以正常结束也可以在错误的情况下抛出异常。
* 方法的调用者可以捕获到这个异常并且处理它,如果不处理异常会沿着调用栈再次抛出
* 和所有其他类一样,不必使用new 关键字来创建异常实例。
* 和Java不同的是,Kotlin中throw 结构是一个表达式,能作为另一个表达式的一部分使用
fun testThrow(percentage: Int) = if (percentage in 0..100) "right" else if (percentage in 101..105) throw IllegalArgumentException("a percentage value must be between 0 and 100 $percentage") else throw IOException("xxx")
和Java最大的区别是 throws 子句没有出现在代码中: 如果用Java来写上面的函数必须要显示的在函数声明后面写上 throw xxException ,这样做的原因是Java中IOException 是受检异常,在Java中受检异常必须显示地处理。->必须声明你的函数所能抛出的异常。
如果调用另一个函数需要处理这个函数的受检异常,或者声明你的函数也能抛出这个异常Kotlin并不区分受检异常和未受检异常。不用制定函数抛出的异常,而且可以选择处理或者不处理异常。
这种设计是基于Java中使用异常的事件得出的,经验显示这些Java规则总是引起许多无意义的异常抛出,而这些规则不能总是保护你的代码免受可能发生的错误。
* try 可以作为表达式.
* Kotlin中的try关键字就像 if 和 when 一样,引入了一个表达式,可以把它返回或者赋值给别的变量,
* 不同于if try 的主体总需要用花括号括起来,和其他表达式一样如果主体包含多个表达式最后一个表达式就是整个try表达式的值
fun tryTest(reader: BufferedReader) { val number = try { val readLine = reader.readLine() Integer.parseInt(readLine) } catch (e: NumberFormatException) { return //如果执行到此处就返回不会再往下执行了 } println("number is $number") } fun tryTest2(reader: BufferedReader) { val number = try { val readLine = reader.readLine() Integer.parseInt(readLine) } catch (e: NumberFormatException) { null //如果执行到此处会给 number 复制 null 然后继续往下执行 } println("number is $number") }