以下内容是整理《Kotlin实战》中的内容。
第二章 Kotlin基础 中的内容,简单的栗子,简单的与Java的比较。
先贴出小结,然后是每一项内容的展开:
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不要求你声明函数可以抛出的异常。
一).简介
1、Kotlin是静态类型语言并支持类型,允许维护正确性与性能的同时保持源代码的简洁。
2、Kotlin支持面向对象和函数式两种编程风格,通过头等函数使更高级别的抽象成为可能,通过支持不可变值简化了测试和多线程安全。
3、在服务器端应用程序中它工作的很好,全面支持所有现存的Java框架,为常见的任务提供了新工具,如生成HTML和持久化。
4、在Android上它也可以工作,这得益于紧凑的运行时、对Android API特殊的编译器支持以及丰富的库,为常见Android开发任务提供了Kotlin友好的函数。
5、他是免费和开源的,全面支持主流的IDE和构建系统。
6、Kotlin时务实的、安全的、简洁的,与Java可互操作,意味着它专注于使用已经证明过的解决方案处理常见任务,
防止常见的像NullPointerException这样的错误,支持紧凑和易读的代码,以及提供于Java无限制的集成。
二).Hello World
fun main(args : Array<String>){ println("Hello World") }
在kt类中编写上面代码:
1、关键字fun用来声明一个函数;
2、参数类型写在函数后面;
3、函数可以定义在文件最外层,不需要放在类中;
4、数组就是类。和Java不同,Kotlin没有声明数组类型的特殊语法;
5、使用println代替System.out.println。
Kotlin标准库给Java标准库函数提供了许多语法更简洁的包装,而println就是其中一个。
6、和许多其他现代语言一样,可以省略每行代码结尾的分号。
三).函数
上面看到了怎么声明一个没有返回任何东西的函数。
但如果函数有一个有意义的结果,返回类型应该放在哪里呢:
fun max(a : Int, b : Int) : Int{ return if(a > b) a else b; }
上面展示了一个函数基本结构。
注:在Kotlin中,if时有结果值的表达式。它和Java中的三元运算符相似:(a>b)? a : b。
语句和表达式:
在Kotlin中,if是表达式,而不是语句,
语句和表达式的区别在于:
表达式有值,并且能作为另一个表达式的一部分使用;
而语句总是包围着它的代码块中的顶层元素,并且没有自己的值。
在Java中,所有的控制结构都是语句。
而在Kotlin中,除了循环(for、do和do/while)以外大多数控制结构都是表达式。
这种结合控制结构和其他表达式的能力让你可以简明扼要地表示许多常见的模式。
另一方面,Java中的赋值操作是表达式,在Kotlin中反而变成了语句。
这有助于避免比较和赋值之间的混淆,而这种混淆是常见的错误来源。
表达式函数体可以让前面的函数变得更简洁。
因为它的函数是由单个表达式构成的,可以用这个表达式作为完整的函数体,并去掉花括号和return语句:
fun max(a : Int, b : Int) : Int = if (a > b) a else b
如果函数体写在花括号中,我们说这个函数有代码块体。
如果它直接返回了一个表达式,它就有表达式体。
还可以进一步简化max函数,省掉返回类型:
fun max1(a : Int, b : Int) = if (a > b) a else b
每个变量和表达式都有类型,每个函数都有返回类型。
但是对表达式体函数来说,编译器会分析作为函数体的表达式,并把他的类型作为函数的返回类型,即使没有显示的写出来。
这种分析通常被称作类型推导。
注:只有表达式体函数的返回类型可以省略。
真实的项目中的函数一般很长且可以包含多条return语句,显示的写出return语句,可以帮你快速理解函数返回的是什么。
四).变量
在Java中声明变量的时候会以类型开始。在Kotlin中这样是行不通的,因为许多变量声明的类型都可以省略。
所以在Kotlin中以关键字开始,然后是变量名称。最后可以加上类型(不加也可以):
val question = "what are you 弄啥咧?" val answer = 111111
上面省略了类型声明,但是如果需要也可以显式的指定:
val answer : Int = 111111
和函数表达式一样,如果你不指定变量类型,编译器会分析初始化表达式的值,并把她的类型作为变量的类型。
4.1可变变量和不可变变量
声明变量关键字有两个:
val(来自value)---不可变引用。使用val声明的变量不能在初始化之后再次赋值。他对应的是Java的final变量。
var(来自variable)---可变引用。这种变量的值可以被改变。这种声明对应的是普通(非final)的Java变量。
默认情况下尽量使用val声明不可变变量,仅在必要的时候换成var。
在定义了val变量的代码执行期间,val变量只能进行唯一的一次初始化。
但是,如果编译器能确保直邮唯一一条初始化语句会被执行,可以根据条件使用不同的值来初始化它:
val message : String if(true){ message = "true" }else{ message = "false" }
注:尽管val引用自身是不可变的,但是它指向的对象是可变的。
下面的代码是完全有效的。
val languages = arrayListOf("Java")
languages.add("Kotlin")
即使var关键字允许变量改变自己的值,但他的类型却是改变不了的:
var answer = 11111 answer = "wonima"这就会报错。
4.2更简单的字符串格式化 : 字符串模版
fun main(args : Array<String>){ val name = if(args.isNotEmpty()) args[0] else "Kotlin" println("Hello, $name") }
上面例子介绍了一个新特性,叫字符串模版。
你声明了一个变量叫name,后面使用它,只需要在变量前加上$符号,如果你尝试试着引用一个不存在的变量,代码根本不会编译。
还可以引用复杂的表达式,只需要把表达式用花括号括起来:
println("Hello, ${args[0]}")还可以在花括号内直接嵌套双引号,只要他们处在某个表达式的范围内(即花括号内):
println("Hello, ${if (args.isNotEmpty()) args[0] else "Kotlin"}")
五).类和属性
先看一个JavaBean类:
public class Personal { private final String name; public Personal(String name){ this.name = name; } public String getName(){ return this.name; } }
将它转为Kotlin类:
class Personal(val name: String)
这种类(只有数据没有代码)通常被叫做值对象,许多语言都提供简明语法来声明她们。
从Java到Kotlin的转换过程中public修饰符消失了。
在Kotlin中,public是默认的可见性,所以你可以省略他。
5.1属性
类的概念就是把数据和处理数据的代码封装成一个单一的实体。
在Java中,数据存储在字段中,通常还是私有的。
如果想让类的使用者访问到数据,得提供访问器方法:
一个getter,可能还有一个setter。
在上面personal中可以看到访问器的栗子。
setter还可以包含额外的逻辑。
在Java中,字段和其访问器的组合常常被叫做属性,而许多框架严重依赖这个概念。
在Kotlin中属性是头等语言特性,完全替代了字段和访问器方法。
上面说过了val和var。
class Personal( val name: String, // 只读属性:生成一个字段和一个简单的getter var isMarried : Boolean // 可读写属性:一个字段、一个getter和一个setter )
声明字段的时候就声明了getter或者setter访问器。
其中setter我们也可以自定义访问器来满足一些逻辑需求。
5.2自定义访问器
假设声明一个这样的矩形,他能判断自己是否是正方形。
不需要一个单独的字段来存储这个信息,因为随时可以通过检查矩形的长宽是否相等来判断:
class Rectangle(val height : Int, val width : Int){ val isSquare : Boolean // 声明属性的getter get(){ return height == width } }
属性isSquare不需要字段来保存它的值。
他只有一个自定义实现的getter。
他的值是每次访问属性的时候计算出来的。
5.3Kotlin源码布局:目录和包
Java把所有的类组织成包。
Kotlin也有相似的概念。
Kotlin不区分导入的是类还是函数,而且,它允许使用import关键字导入任何种类的声明。
可以直接导入顶层函数的名称。
还可以在包名称后面加上.*来导入特定包中定义的所有声明。
六).表示和处理选择:枚举和“when”
when结构可以被认为是Java中switch结构的替代品,但是它更强大,也使用的更频繁。
顺便展示一个kotlin声明枚举的栗子。
6.1声明枚举类
enum class Color{ RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }
enum是一个所谓的软关键字:只有当他出现在class前面的时候才有意义,在其他地方可以把它当作普通的名称使用。
和Java一样,枚举并不是值的列表:可以给枚举类声明属性和方法:
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中必须使用分号的地方:
如果要在枚举类中定义任何方法,就要使用分号把枚举常量列表和方法定义分开。
6.2使用when处理枚举类
和if相似,when是一个有返回值的表达式,因此可以写一个直接返回when表示式的表达式体函数。
fun getMenemonic(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" }
在when分支上合并多个选项
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进来),直接使用名称就可以了。
6.3在when结构中使用任意对象
Kotlin中的when结构比Java中的switch强大得多。
switch必须使用常量(枚举常量、字符串或者数字字面值)作为分枝条件。
而when允许使用任何对象。
下面写一个函数来混合两种颜色:
fun mix(c1 : Color, c2 : Color) = when(setOf(c1, c2)){ setOf(Color.RED, Color.YELLOW) -> Color.ORANGE setOf(Color.YELLOW, Color.BLUE) -> Color.GREEN setOf(Color.BLUE, Color.VIOLET) -> Color.INDIGO else -> throw Exception("Dirty color") }
可以注意到,上面这中方式使用set比较来实现这个调色板。
Kotlin标准库又一个setOf函数可以创建出一个Set,他会包含所有指定为函数实参的对象。
上面这中方式效率有些低。
每次调用函数的时候,都会创建一些Set实例,仅仅用来检查两种给定的颜色是否和另外两种颜色匹配。
一般不是什么大问题,但是如果这个函数频繁调用,他就值得用另外一种方式重写,来避免创建额外的垃圾对象。
但是代码可读性会比较差,但是这是为了达到更好性能而必须付出的代价:
6.4使用不带参数的when
fun mixOptimized(c1 : Color, c2 : Color) = 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 (c1 == Color.BLUE && c2 == Color.VIOLET) || (c1 == Color.VIOLET && c2 == Color.BLUE) -> Color.INDIGO else -> throw Exception("Dirty color") }
如果没有参数传给when,那么分枝条件就是任意的布尔表达式。
七).迭代事物:“while”循环和“for”循环
Kotlin中有while循环和do-while循环,她们的语法和Java中相应的循环没有什么区别:
while (true){ // 当条件为true时执行循环体 } do { }while (true) // 循环第一次会无条件执行。此后当条件为true时才执行
Kotlin没有为这些循环带来新东西,一笔带过,下面看看for循环各种:
7.1迭代数字:区间和数列
首先Kotlin中for循环的区间这么写: 1..100
这就表示从1~100。
书上用一个游戏来写了一个例子:
fun fizzBuzz(i : Int) = when{ i % 15 == 0 -> "FizzBuzz" i % 3 == 0 -> "Fizz" i % 5 == 0 -> "Buzz" else -> "$i " } >>> for(i in 1..100){ print(fizzBuzz(i)) }
代码很简单,能被3或5或15整除时,返回对应的不同字符。
循环打印了100内的所有整数。
现在我们改变下,从一百倒着数,并且步长设置为2:
for(i in 100 downTo 1 step 2){ print(fizzBuzz(i)) }
7.2迭代map
使用for-in最常见的场景就是迭代集合。
这和Java中的用法一样,所以略过,下面看看迭代map。
栗子:
把一些二进制表示存在一个map中。
下面代码创建了一个map,把某些字母的二进制表示填进去,最后打印出map内容。
// 使用TreeMap让键排序 val binaryReps = TreeMap<Char, String>() // 使用字符区间迭代从A到F之间的字符 for (c in 'A'..'F'){ // 把码转换成二进制 val binary = Integer.toBinaryString(c.toInt()) // 根据键c把值存储到map中 binaryReps[c] = binary } // 迭代map,把键和值赋给两个变量 for ((letter, binary) in binaryReps){ println("$letter = $binary") }
可以看到和c#很像:
可以使用map[key]读取值,map[key] = value来设置值。
下面看一个展开语法,在迭代集合的同时跟踪下标。
不需要创建一个单独的变量来存储下标并手动增加它:
val list = arrayListOf("10", "11", "1001") for ((index, element) in list.withIndex()){ println("$index: $element") // 迭代集合时使用下标 }
7.3使用“in”检查集合和区间的成员
使用in运算符来检查一个值是否存在区间中,或者她的逆运算,!n,来检查这个值是否不在区间中。
下面展示个栗子,来判断一个字符是否属于一个字符区间:
fun isLetter(c : Char) = c in 'a'..'z' || c in 'A'..'Z' fun isNotDigit(c : Char) = c !in '0'..'9'
用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..." }
八).Kotlin中的异常
Kotlin的异常处理和Java以及其他许多语言的处理方式相似。
一个函数可以正常结束,也可以在出现错误的情况下抛出异常。
方法的调用者能捕获到这个异常并处理它;
如果没有被处理,异常会沿着调用栈再次抛出。
和所有其他类一样,不必使用new关键字来创建异常实例。
和Java不同的是,Kotlin中throw结构是一个表达式,能作为另一个表达式的一部分使用。
val percentage = if (number in 0..100) number else throw IllegalArgumentException("xxxxxxxxx")
8.1"try","catch"和"finally"
和Java一样,使用带有catch和finally子句的try结构来处理异常。
栗子:
从给定的文件中读取一行,尝试把它解析成一个数字,返回这个数字;
或者当这一行不是一个有效数字时返回null。
fun readNumber(reader : BufferedReader) : Int?{ // 不必显示的指定这个函数能抛出的异常 try { val line = reader.readLine() return Integer.parseInt(line) } catch (e : NumberFormatException){ // 异常类型在右边 return null } finally { // 和java中的finally作用一样 reader.close() } }
和Java最大的区别在于throws子句没有出现在代码中:
如果用Java来写这个函数,你会显示的在函数声明后写上throws IOEXCEPTION。
你需要这样做的原因是IOEXCEPTION是一个受检异常。
Java中这种异常必须显式的处理,必须声明你的函数能抛出的所有受检异常。
如果调用另外一个函数,需要处理这个函数的受检异常,或者声明你的函数也能抛出这些异常。