函數(shù)和變量
Hello, world!
學(xué)習(xí)就從如何用Kotlin編寫一個(gè)“Hollo World”開始吧!先看熟悉的Java:
public static void main(String[] args) {
System.out.println("Hello, world!");
}
復(fù)制代碼然后那,是Kotlin的寫法:
fun main(args: Array<String>) {
println("Hello, world!")
}
可以看到Kotlin中:
fun 關(guān)鍵字用來聲明一個(gè)函數(shù);
main 是方法名;
args: Array<String>表示參數(shù),可以發(fā)現(xiàn)于java中先類型后變量名相反,Kotlin中是先變量名,然后:,然后是類型聲明。
Kotlin中沒有聲明數(shù)組的特殊語法,而是用Array表示數(shù)組,有點(diǎn)類似集合的感覺;
println代替了System.out.println,這是Kotlin標(biāo)準(zhǔn)庫(kù)給Java標(biāo)準(zhǔn)庫(kù)函數(shù)提供了許多語法更簡(jiǎn)潔的包裝;
不知道你有沒有注意到;Kotlin中省略了分號(hào)。
函數(shù)
熟悉Java的你可能會(huì)想返回值在哪里那?怎么沒有那?
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
: Int這里就出現(xiàn),表示返回值是一個(gè)int類型,那為什么上面那個(gè)函數(shù)沒有寫那?其實(shí)上面那個(gè)函數(shù)也有返回值,返回值是空,也就是void,在Kotlin中其實(shí)是: Unit,而: Unit默認(rèn)可以省略,所以就看不到返回值的聲明了。
同樣方法在對(duì)比Java的看下:
public static int max(int a, int b) {
if (a > b) {
return a;
} else {
return b;
}
}
是不是發(fā)現(xiàn)了方法體中if的使用好像有區(qū)別?
在Kotlin中,if是表達(dá)式,而不是語句。語句和表達(dá)式的區(qū)別在于,表達(dá)式有值,并且能作為另一個(gè)表達(dá)式的一部分使用;而語句總是包圍著它的代碼塊中的頂層元素,并且沒有自己的值。在Java中,所用的控制結(jié)構(gòu)都是語句。而在Kotlin中,除了循環(huán)(for,do,do/while)以外大多數(shù)控制結(jié)構(gòu)都是表達(dá)式。
表達(dá)式函數(shù)體
如果一個(gè)方法的函數(shù)體是由單個(gè)表達(dá)式構(gòu)成的,可以用這個(gè)表達(dá)式作為完整的函數(shù)體,并且去掉花括號(hào)和return語句:
fun max(a: Int, b: Int) = if (a > b) a else b
在android studio中通過
alt+enter可以喚起操作,提供了在兩種函數(shù)風(fēng)格之間轉(zhuǎn)換的方法:"Convert
to expression body"(轉(zhuǎn)換成表達(dá)式函數(shù)體)和 "Convert to block body"(轉(zhuǎn)換成代碼塊函數(shù)體)
細(xì)心的你或許注意到此處的表達(dá)式函數(shù)體也沒有寫出返回類型,作為一門靜態(tài)類型的語言,Kotlin不是要求每個(gè)表達(dá)式都應(yīng)該在編譯期具有類型嗎?事實(shí)上,每個(gè)變量和表達(dá)式都有類型,每個(gè)函數(shù)都有返回類型。但是對(duì)表達(dá)式體函數(shù)來說,編譯器會(huì)分析作為函數(shù)體的表達(dá)式,并把它的類型作為函數(shù)的返回類型,即使沒有顯示得寫出來。這種縫隙通常被稱作類型推導(dǎo)。
變量
在Java中變量的聲明是從類型開始的,就像這樣:
final String str = "this is final string";
int a = 12;
但是在Kotlin中這樣是行不通的,因?yàn)樵S多變量聲明的類型都可以省略。所以在Kotlin中以關(guān)鍵字開始,然后是變量名稱,最后可以加上類型(也可以省略):
val str: String = "this is a final string"
var a = 12
其中: String也是可以省略的,通過=右邊推導(dǎo)出左邊變量的類型是String,就像a變量省略類型。
可變變量和不可變變量
聲明變量的關(guān)鍵字有兩個(gè):
val(來自value)——不可變引用。在初始化之后不能再次賦值,對(duì)應(yīng)Java中final修飾符。
var(來自variable)——可變引用。這種變量的值可以被改變,對(duì)應(yīng)Java中的普通變量。
雖然var表示可變,并且如上面看到的也省略的類型,乍一看似乎和js等腳本語言類似,可以直接賦值另一種類型的值,比如這樣:
var a = 12
a = "string"http://錯(cuò)誤
但實(shí)際上,這樣做是錯(cuò)誤的,即使var關(guān)鍵字允許變量改變自己的值,但它的類型卻是改變不了的。此處a變量在首次賦值時(shí)就確定了類型,這里的類型是Int,再次賦值String類型的值時(shí)就會(huì)提示錯(cuò)誤,并且運(yùn)行也會(huì)發(fā)生ClassCastException。
注意,盡管val引用自身是不可變的,但是它指向的對(duì)象可能是可變的,例如:
val languages = arrayListOf("Java")
languages.add("Kotlin")
其實(shí)和Java中一致,final定義一個(gè)集合,集合中的數(shù)據(jù)是可以改變的。
字符串模板
val name = "HuXiBing"
println("Hello, $name!")
這是一個(gè)Kotlin的新特性,在代碼中,你申明了一個(gè)變量name,并且后面的字符串字面值中使用了它。和許多腳本語言一樣,Kotlin讓你可以在字符串字面值中引用局部變量,只需要在變量名稱前面加上字符$,這等價(jià)于Java中的字符串鏈接 "Hello, " + name + "!" ,效率一樣但是更緊湊。
通過轉(zhuǎn)換成Java代碼,我們可以看到這兩句代碼其實(shí)是這樣的:
String name = "HuXiBing";
String var3 = "Hello, " + name + '!';
System.out.println(var3);
當(dāng)然,表達(dá)式會(huì)進(jìn)行靜態(tài)檢查,如果你試著引用一個(gè)不存在的變量,代碼根本不會(huì)編譯。
如果要在字符串中使用$,你需要對(duì)它轉(zhuǎn)義:println("\$x")會(huì)打印$x,并不會(huì)吧x解釋成變量的引用。
還可以引用更復(fù)雜的表達(dá)式,而不是僅限于簡(jiǎn)單的變量名稱,只需要把表達(dá)式用花括號(hào)括起來:
println("1 + 2 = ${1 + 2}")
還可以在雙引號(hào)中直接嵌套雙引號(hào),只要它們?cè)谀硞€(gè)表達(dá)式的范圍內(nèi)(即花括號(hào)內(nèi)):
val a = 12
println("a ${if (a >= 10) "大于等于10" else "小于10"}")
類和屬性
先來看一個(gè)簡(jiǎn)單的JavaBean類Person,目前它只有一個(gè)屬性:name。
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
在Java中,構(gòu)造方法的方法體常常包含完全重復(fù)的代碼:它把參數(shù)賦值給有著相同名稱的字段。在Kotlin中,這種邏輯不用這么多的樣板代碼就可以表達(dá)。
使用Convert Java File To Kotlin File將這個(gè)對(duì)象轉(zhuǎn)換成Kotlin:
class Person(val name: String)
復(fù)制代碼這種只有數(shù)據(jù)沒有其他代碼的類通常被叫做值對(duì)象,許多語言都提供簡(jiǎn)明的語法來聲明它們。
注意從Java到Kotlin的轉(zhuǎn)換過程中public修飾符消失了,在Kotlin中默認(rèn)是public,所以可以省略它。
屬性
類的概念就是把數(shù)據(jù)和處理數(shù)據(jù)的代碼封裝成一個(gè)單一的實(shí)體。在Java中,數(shù)據(jù)存儲(chǔ)在字段中,通常還是私有的。如果想讓類的使用者訪問到數(shù)據(jù),得提供訪問器方法:一個(gè)getter,可能還有一個(gè)setter。在Person類中你已經(jīng)看到了訪問器的例子。setter還可以包含額外的邏輯,包括汗蒸傳給它的值、發(fā)送關(guān)于變化的通知等。
在Java中,字段和其訪問器的組合常常被叫做屬性,在Kotlin中,屬性時(shí)頭等的語言特性,完全代替了字段和訪問器的方法。在類中聲明一個(gè)屬性和聲明一個(gè)變量一樣:使用val和var關(guān)鍵字。聲明成val的屬性是只讀的,而var屬性是可變的。
class Person(
val name: String,//只讀屬性,生成一個(gè)字段和一個(gè)簡(jiǎn)單的getter
var isMarried: Boolean//可寫屬性:生成一個(gè)字段、一個(gè)getter、一個(gè)setter
)
看看轉(zhuǎn)換成Java的代碼可能更清晰一點(diǎn):
public final class Person {
@NotNull
private final String name;
private boolean isMarried;
@NotNull
public final String getName() {
return this.name;
}
public final boolean isMarried() {
return this.isMarried;
}
public final void setMarried(boolean var1) {
this.isMarried = var1;
}
public Person(@NotNull String name, boolean isMarried) {
super();
Intrinsics.checkParameterIsNotNull(name, "name");
this.name = name;
this.isMarried = isMarried;
}
}
簡(jiǎn)單的說就是平時(shí)我們用代碼模板生成的bean,在Kotlin中連模板都不需要使用了,編譯時(shí)會(huì)自動(dòng)生成對(duì)應(yīng)的代碼。
在Java中使用應(yīng)該比較熟悉了,是這個(gè)是這樣的:
Person person = new Person("HuXiBing", true);
System.out.println(person.getName());
System.out.println(person.isMarried());
生成的getter和setter方法都是在屬性名稱前加上get和set前綴作為方法名,但是有一種例外,如果屬性時(shí)以is開頭,getter不會(huì)增加前綴,而它的setter名稱中is會(huì)被替換成set。所以你調(diào)用的將是isMarried()。
而在Kotlin中使用是這樣的:
val person = Person("HuXiBing", true) //調(diào)用構(gòu)造方法不需要關(guān)鍵字new
println(person.name) //可以直接訪問屬性,但調(diào)用的時(shí)getter
println(person.isMarried)
在Kotlin中可以直接引用屬性,不在需要調(diào)用getter。邏輯沒有變化,但代碼更簡(jiǎn)潔了??勺儗傩缘膕etter也是這樣:在Java中,使用person.setMarried(false)來表示離婚,而在Kotlin中,可以這樣寫:person.isMarried = false。
自定義訪問器
如果getter和setter方法中需要額外的邏輯,可以通過自定義訪問器的方式實(shí)現(xiàn)。例如現(xiàn)在有這樣一個(gè)需求:聲明一個(gè)矩形,它能判斷自己是否是一個(gè)正方形。不需要一個(gè)單獨(dú)的字段來存儲(chǔ)這個(gè)信息,因?yàn)榭梢噪S時(shí)通過檢查矩形的長(zhǎng)寬是否相等來判斷:
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {//聲明屬性的getter
return height == width
}
}
屬性isSquare不需要字段來保存它的值,它只有一個(gè)自定義實(shí)現(xiàn)的getter,它的值是每次訪問屬性的時(shí)候計(jì)算出來的。還記得之前的表達(dá)式函數(shù)體嗎?此處也可以轉(zhuǎn)成表達(dá)式體函數(shù):get() = height == width。
同樣的看下轉(zhuǎn)換成Java代碼更好理解:
public final class Rectangle {
private final int height;
private final int width;
public final boolean isSquare() {
return this.height == this.width;
}
public final int getHeight() {
return this.height;
}
public final int getWidth() {
return this.width;
}
public Rectangle(int height, int width) {
this.height = height;
this.width = width;
}
}
Kotlin源碼布局:目錄和包
與Java類似,每一個(gè)Kotlin文件都能以一條package語句開頭,而文件中定義的所有聲明(類、函數(shù)及屬性)都會(huì)被放到這個(gè)包中。如果其他文件中定義的聲明也有相同的包,這個(gè)文件可以直接使用它們;如果包不相同,則需要導(dǎo)入它們。和Java一樣,導(dǎo)入語句放在問價(jià)你的最前面使用關(guān)鍵字import:
package com.huburt.imagepicker
import java.util.Random
Java中的包和導(dǎo)入聲明:
package com.huburt.imagepicker;
import java.util.Random;
僅僅省略了; 還有點(diǎn)不同的時(shí)Kotlin不區(qū)分導(dǎo)入是類還是函數(shù)(是的Kotlin的函數(shù)可以單獨(dú)存在,不是一定要聲明在類中)。例如:
import com.huburt.other.createRandom
com.huburt.other是包名,createRandom是方法名,直接定義在Kotlin文件的頂層函數(shù)。
在Java中,要把類放在和包結(jié)構(gòu)相匹配的文件與目錄結(jié)構(gòu)中。而在Kotlin中包層級(jí)機(jī)構(gòu)不需要遵循目錄層級(jí)結(jié)構(gòu),但是不管怎樣,遵循Java的目錄布局更根據(jù)包結(jié)構(gòu)把源碼文件放到對(duì)應(yīng)的目錄中是個(gè)更好的選擇,避免一些不期而遇的錯(cuò)誤。
表示和處理選擇:枚舉和When
聲明枚舉
Kotlin中聲明枚舉:
enum class Color {
RED, ORANGE, YELLOW, GREEN, BLUE
}
而Java中枚舉的聲明:
enum Color {
RED, ORANGE, YELLOW, GREEN, BLUE
}
這是極少數(shù)Kotlin聲明比Java使用了更多關(guān)鍵字的例子(多了class關(guān)鍵字)。Kotlin中,enum是一個(gè)軟關(guān)鍵字,只有當(dāng)它出現(xiàn)在class前面是才有特殊的意義,在其他地方可以把它當(dāng)做普通的名稱使用,與此不同的是,class任然是一個(gè)關(guān)鍵字,要繼續(xù)使用名稱clazz和aClass來聲明變量。
和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, 255),
BLUE(0, 0, 255);
fun rgb() = (r * 256 + g) * 256 + b
}
枚舉常量用的聲明構(gòu)造的方法和屬性的語法與之前你看到的常規(guī)類一樣。當(dāng)你聲明每個(gè)枚舉常量的時(shí)候,必須提供該常量的屬性值。注意這個(gè)向你展示了Kotlin語法中唯一必須使用分號(hào)(;)的地方:如果要在枚舉類中定義任何方法,就要使用分號(hào)把枚舉常量列表和方法定義分開。
使用When處理枚舉類
對(duì)于Java,通常使用switch來匹配枚舉,例如這樣:
public String getColorStr(Color color) {
String str = null;
switch (color) {
case RED:
str = "red";
break;
case BLUE:
str = "blue";
break;
case GREEN:
str = "green";
break;
case ORANGE:
str = "orange";
break;
case YELLOW:
str = "yellow";
break;
}
return str;
}
而Kotlin中沒有switch,取而代之的是when。和if相似,when是一個(gè)有返回值的表達(dá)式,因此我們寫一個(gè)直接返回when表達(dá)式的表達(dá)式體函數(shù):
fun getColorStr(color: Color) =
when (color) {
Color.RED -> "red"
Color.ORANGE -> "orange"
Color.YELLOW -> "yellow"
Color.GREEN -> "green"
Color.BLUE -> "blue"
}
//調(diào)用方法
println(getColorStr(Color.RED))
上面的代碼根據(jù)傳進(jìn)來的color值找到對(duì)應(yīng)的分支。和Java不一樣,你不需要在每個(gè)分支都寫上break語句(在Java中遺漏break通常會(huì)導(dǎo)致bug)。如果匹配成功,只有對(duì)應(yīng)的分支會(huì)執(zhí)行,也可以把多個(gè)值合并到同一個(gè)分支,只需要逗號(hào)(,)隔開這些值。
fun getColorStr(color: Color) =
when (color) {
Color.RED, Color.ORANGE, Color.YELLOW -> "yellow"
Color.GREEN -> "neutral"
Color.BLUE -> "cold"
}
如果覺得寫了太多的Color,可以通過導(dǎo)入的方式省略:
import com.huburt.other.Color //導(dǎo)入類
import com.huburt.other.Color.* //導(dǎo)入枚舉常量
fun getColorStr(color: Color) =
when (color) {
RED, ORANGE, YELLOW -> "yellow" //直接使用常量名稱
GREEN -> "neutral"
BLUE -> "cold"
}
在When結(jié)構(gòu)中使用任意對(duì)象
Kotlin中的when結(jié)構(gòu)比Java中switch強(qiáng)大的多。switch要求必須使用常量(枚舉常量、字符串或者數(shù)字字面值)作為分支條件。而when允許使用任何對(duì)象。我們使用這種特性來寫一個(gè)函數(shù)來混合兩種顏色:
fun mix(c1: Color, c2: Color) {
when (setOf(c1, c2)) {
setOf(Color.RED, Color.YELLOW) -> Color.ORANGE
setOf(Color.YELLOW, Color.BLUE) -> Color.GREEN
else -> throw Exception("Dirty color")
}
}
setOf是Kotlin標(biāo)準(zhǔn)函數(shù)庫(kù)中一個(gè)方法,用于創(chuàng)建Set集合(無序的)。
when表達(dá)式把setOf(c1, c2)生成的set集合依次和所有的分支匹配,直到某個(gè)分支滿足條件,執(zhí)行對(duì)應(yīng)的代碼(返回混合后顏色值或者拋出異常)。
能使用任何表達(dá)式作為when的分支條件,很多情況下會(huì)讓你的代碼既簡(jiǎn)潔又漂亮。
使用不帶參數(shù)的When
你可能意識(shí)到上面的例子效率多少有些低。沒此調(diào)用這個(gè)函數(shù)的時(shí)候它都會(huì)創(chuàng)建一些Set實(shí)例,僅僅是用來檢查兩種給定的顏色是否和另外兩種顏色匹配。一般這不是什么大問題,但是如果這個(gè)函數(shù)調(diào)用很頻繁,它就非常值得用另一種方式重寫。來避免創(chuàng)建額外的垃圾對(duì)象。
fun mixOptimized(c1: Color, c2: Color) =
when {
(c1 == Color.RED && c2 == Color.YELLOW) ||
(c1 == Color.YELLOW && c2 == Color.RED) -> Color.ORANGE
(c1 == Color.BLUE && c2 == Color.YELLOW) ||
(c1 == Color.YELLOW && c2 == Color.BLUE) -> Color.GREEN
else -> throw Exception("Dirty color")
}
如果沒有給when表達(dá)式提供參數(shù),分支條件就是任意的布爾表達(dá)式。mixOptimized方法和上面那個(gè)例子做了一模一樣的事情,這種寫法不會(huì)穿件額外的對(duì)象。
智能轉(zhuǎn)換
在Java中經(jīng)常會(huì)有這樣一種情形:用父類申明引用一個(gè)子類對(duì)象,當(dāng)要使用子類的某個(gè)方式時(shí),需要先判斷是否是哪個(gè)子類,如果是的話在強(qiáng)轉(zhuǎn)成子類對(duì)象,調(diào)用子類的方法,用代碼的話就是如下的情況:
class Animal {
}
class Dog extends Animal {
public void dig() {
System.out.println("dog digging");
}
}
Animal a = new Dog();
if (a instanceof Dog) {
((Dog) a).dig();
}
在Kotlin中,編譯器會(huì)幫你完成強(qiáng)轉(zhuǎn)的工作。如果你檢查過一個(gè)變量是某種類型,后面就不需要轉(zhuǎn)換它,就可以把它當(dāng)做你檢查過的類型使用(調(diào)用方法等),這就是智能轉(zhuǎn)換。
val d = Animal()
if (d is Dog) {
d.dig()
}
這里is是檢查一個(gè)變量是否是某種類型(某個(gè)類的實(shí)例),相當(dāng)于Java中的instanceof??梢钥吹絛變量是一個(gè)Animal對(duì)象,通過is判斷是Dog后,無需強(qiáng)轉(zhuǎn)就能調(diào)用Dog的方法。
智能轉(zhuǎn)換只在變量經(jīng)過is檢查且且之后不再發(fā)生變化的情況下有效。當(dāng)你對(duì)一個(gè)類的屬性進(jìn)行智能轉(zhuǎn)換的時(shí)候,這個(gè)屬性必須是一個(gè)val屬性,而且不能有自定義的訪問器。否則,每次對(duì)屬性的訪問是否都能返回相同的值將無從驗(yàn)證。
在Kotlin中用as關(guān)鍵字來顯示轉(zhuǎn)換類型(強(qiáng)轉(zhuǎn)):
val d = Animal()
val dog = d as Dog
ps:其實(shí)只是省略強(qiáng)轉(zhuǎn)代碼,個(gè)人感覺作用不是很明顯。
用When代替If
Kotlin和Java中if有什么不同,之前已經(jīng)提到過了。如if表達(dá)式用在適用Java三元運(yùn)算符的上下文中:if (a > b) a else b (Kotlin)和a > b ? a : b(Java)效果一樣。Kotlin沒有三元運(yùn)算符,因?yàn)?code>if表達(dá)式有返回值,這一點(diǎn)和Java不同。
對(duì)于較少的判斷分支用if沒有問題,但是較多的判斷分支則用when是更好的選擇,有相同的作用,并且都是表達(dá)式,都有返回值。
代碼塊作為If和When的分支
上面的例子滿足條件的分支執(zhí)行只有一行代碼,但如果某個(gè)分支中代碼不止一行還如何處理那?當(dāng)然是把省略的{}加上作為代碼塊啦:
val a = 1
val b = 2
var max = if (a > b) {
println(a)
a
} else b
var max2 = when {
a > b -> {
println(a)
a
}
else -> b
}
代碼塊中最后一個(gè)表達(dá)式就是結(jié)果,也就是返回值。
對(duì)比Java的代碼:
int a = 1;
int b = 2;
int max;
if (a > b) {
System.out.println(a);
max = a;
} else {
max = b;
}
//無法使用switch
少了賦值操作,并且when的使用在多條件的情況下也更方便。是不是慢慢發(fā)現(xiàn)Kotlin的美妙了?
循環(huán)
While循環(huán)
Kotlin中while和do-while循環(huán)與Java完全一致,這里不再過多敘述。
迭代數(shù)字:區(qū)間和數(shù)列
Kotlin中有區(qū)間的概念,區(qū)間本質(zhì)上就是兩個(gè)值之間的間隔,這兩個(gè)值通常是數(shù)字:一個(gè)起始值,一個(gè)結(jié)束值,使用..運(yùn)算符來表示區(qū)間:
val oneToOne = 1 .. 10
復(fù)制代碼注意Kotlin的區(qū)間是包含的或者閉合的,意味著第二個(gè)值始終是區(qū)間的一部分。如果不想包含最后那個(gè)數(shù),可以使用函數(shù)until創(chuàng)建這個(gè)區(qū)間:val x = 1 until 10,等同于val x = 1 .. 9
你能用整數(shù)區(qū)間做的最基本的事情就是循環(huán)迭代其中所有的值。如果你能迭代區(qū)間中所有的值,這樣的區(qū)間被稱作數(shù)列。
我們用整數(shù)迭代來玩Fizz-Buzz游戲。游戲玩家輪流遞增計(jì)數(shù),遇到能被3整除的數(shù)字就用單詞fizz代替,遇到能被5整除的數(shù)字則用單詞buzz代替,如果一個(gè)數(shù)字是3和5的公倍數(shù),你得說FizzBuzz。
fun fizzBuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz"
i % 5 == 0 -> "Buzz"
i % 3 == 0 -> "Fizz"
else -> "$i"
}
fun play() {
for (i in 1..100) {
print(fizzBuzz(i))
}
}
Kotlin中for循環(huán)僅以唯一一種形式存在,其寫法:for <item> in <elements>。
區(qū)間1 .. 100也就是<elements>,因此上面這個(gè)例子遍歷了這個(gè)數(shù)列,并調(diào)用fizzBuzz方法。
假設(shè)想把游戲變得復(fù)雜一點(diǎn),那我們可以從100開始倒著計(jì)數(shù),并且只計(jì)偶數(shù)。
for (i in 100 downTo 1 step 2) {
print(fizzBuzz(i))
}
這里100 downTo 1是遞減的數(shù)列(默認(rèn)步長(zhǎng)是1),并且設(shè)置步長(zhǎng)step為2,表示每次減少2。
迭代map
我們用一個(gè)打印字符二進(jìn)制表示的小程序作為例子。
val binaryReps = TreeMap<Char, String>()//使用TreeMap讓鍵排序
for (c in 'A'..'F') {//使用字符區(qū)間迭代從A到F之間的字符
val binary = Integer.toBinaryString(c.toInt())//吧ASCII碼換成二進(jìn)制
binaryReps[c] = binary//根據(jù)鍵c把值存入map
}
for ((letter, binary) in binaryReps) {//迭代map,把鍵和值賦給兩個(gè)變量
println("$letter = $binary")
}
..語法不僅可以創(chuàng)建數(shù)字區(qū)間,還可以創(chuàng)建字符區(qū)間。這里使用它迭代從A到F的所有字符,包括F。
for循環(huán)允許展開迭代中集合的元素(map的鍵值對(duì)),把展開的結(jié)果存儲(chǔ)到兩個(gè)獨(dú)立的變量中:letter是鍵,binary是值。
map中可以根據(jù)鍵老訪問和更新map的簡(jiǎn)明語法。使用map[key]讀取值,并使用map[key] = value設(shè)置它們,而不需要地愛用get和put。這段binaryReps[c] = binary等價(jià)于Java中的binaryReps.put(c, binary);
你還可以使用展開語法在迭代集合的同時(shí)跟蹤當(dāng)前項(xiàng)的下標(biāo)。不需要?jiǎng)?chuàng)建一個(gè)單獨(dú)的變量來存儲(chǔ)下標(biāo)并手動(dòng)增加它:
val list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()) {
println("$index : $element")
}
使用in檢查集合和區(qū)間的成員
使用in運(yùn)算符來檢查一個(gè)值是否在區(qū)間中,或者它的逆運(yùn)算!in來檢查這個(gè)值是否不在區(qū)間中。下面展示了如何使用in來檢查一個(gè)字符是否屬于一個(gè)字符區(qū)間。
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'z'
fun isNotDigit(c: Char) = c !in '0'..'9'
這種檢查字符是否是英文字符的技巧看起來很簡(jiǎn)單。在底層,沒有什么特別處理,依然會(huì)檢查字符的編碼是否位于第一個(gè)字母編碼和最后一個(gè)字母編碼之間的某個(gè)位置(a <= c && c <= z)。但是這個(gè)邏輯被簡(jiǎn)潔地隱藏到了標(biāo)準(zhǔn)庫(kù)中的區(qū)間類實(shí)現(xiàn)。
in運(yùn)算符合!in也適用于when表達(dá)式。
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關(guān)鍵字,并且throw結(jié)構(gòu)是一個(gè)表達(dá)式,能作為另一個(gè)表達(dá)式的一部分使用。
val b = if (a > 0) a else throw Exception("description")
try、catch、finally
和Java一樣,使用帶有catch和finally子句的try結(jié)構(gòu)來處理異常,下面這個(gè)例子從給定的文件中讀取一行,嘗試把它解析成一個(gè)數(shù)字,返回這個(gè)數(shù)字;或者當(dāng)這一行不是一個(gè)有效數(shù)字時(shí)返回null。
fun readNumber(reader: BufferedReader): Int? {
try {
val line = reader.readLine()
return Integer.parseInt(line)
} catch (e: NumberFormatException) {
return null
} finally {
reader.close()
}
}
Int? 表示值可能是int類型,也可能null,Kotlin獨(dú)特的null機(jī)制,不帶?標(biāo)識(shí)的聲明無法賦值null,在之后的文章中會(huì)具體介紹。
和Java最大的區(qū)別就是throws子句沒有出現(xiàn)在代碼中:如果用Java來寫這個(gè)函數(shù),你會(huì)顯示地在函數(shù)聲明的后寫上throws IOException。你需要這樣做的原因是IOException是一個(gè)受檢異常。在Java中,這種異常必須顯示地處理。必須申明你的函數(shù)能拋出的所有受檢異常。如果調(diào)用另外一個(gè)函數(shù),需要處理這個(gè)函數(shù)的受檢異常,或者聲明你的函數(shù)也能拋出這些異常。
和其他許多現(xiàn)在JVM語言一樣,Kotlin并不區(qū)分受檢異常和未受檢異常。不用指定函數(shù)拋出的異常,而且可以處理也可以不處理異常。這種設(shè)計(jì)是基于Java中使用異常實(shí)踐做出的決定。經(jīng)驗(yàn)顯示這些Java規(guī)則常常導(dǎo)致許多毫無意義的重新拋出或者忽略異常的代碼,而且這些規(guī)則不能總是保護(hù)你免受可能發(fā)生的錯(cuò)誤。
try作為表達(dá)式
Kotlin中try關(guān)鍵字就像if和when一樣,引入了一個(gè)表達(dá)式,可以把它的值賦給一個(gè)變量。例如上面這個(gè)例子也可以這樣寫:
fun readNumber(reader: BufferedReader): Int? =
try {
val line = reader.readLine()
Integer.parseInt(line)
} catch (e: NumberFormatException) {
null
} finally {
reader.close()
}
如果一個(gè)try代碼塊執(zhí)行一切正常,代碼塊中最后一個(gè)表達(dá)式就是結(jié)果。如果捕獲到了一個(gè)異常,相應(yīng)的catch代碼塊中最后一個(gè)表達(dá)式就是結(jié)果。