寫在開頭:本人打算開始寫一個Kotlin系列的教程,一是使自己記憶和理解的更加深刻,二是可以分享給同樣想學(xué)習(xí)Kotlin的同學(xué)囊颅。系列文章的知識點會以《Kotlin實戰(zhàn)》這本書中順序編寫复颈,在將書中知識點展示出來同時,我也會添加對應(yīng)的Java代碼用于對比學(xué)習(xí)和更好的理解架谎。
Kotlin教程(一)基礎(chǔ)
Kotlin教程(二)函數(shù)
Kotlin教程(三)類炸宵、對象和接口
Kotlin教程(四)可空性
Kotlin教程(五)類型
Kotlin教程(六)Lambda編程
Kotlin教程(七)運算符重載及其他約定
Kotlin教程(八)高階函數(shù)
Kotlin教程(九)泛型
Kotlin與Java互轉(zhuǎn)
Kotlin代碼在編譯后都會轉(zhuǎn)成Java文件,對于編寫的Kotlin谷扣,我們可以通過工具提前看到轉(zhuǎn)換后的Java代碼土全,具體方式是:
在as中找到Tools>Kotlin>Show Kotlin Bytecode捎琐,然后點面板上的Decompile。
對于之前寫好的Java代碼裹匙,我們也可以用工具轉(zhuǎn)換成Kotlin代碼瑞凑,方法是:
Code > Convert Java File To Kotlin File
函數(shù)和變量
Hello, world!
學(xué)習(xí)就從如何用Kotlin編寫一個“Hollo World”開始吧!先看熟悉的Java:
public static void main(String[] args) {
System.out.println("Hello, world!");
}
然后那概页,是Kotlin的寫法:
fun main(args: Array<String>) {
println("Hello, world!")
}
可以看到Kotlin中:
fun
關(guān)鍵字用來聲明一個函數(shù)籽御;
main
是方法名;
args: Array<String>
表示參數(shù)惰匙,可以發(fā)現(xiàn)于java中先類型后變量名相反技掏,Kotlin中是先變量名,然后:
项鬼,然后是類型聲明哑梳。Kotlin中沒有聲明數(shù)組的特殊語法,而是用Array表示數(shù)組绘盟,有點類似集合的感覺鸠真;
println
代替了System.out.println
,這是Kotlin標(biāo)準(zhǔn)庫給Java標(biāo)準(zhǔn)庫函數(shù)提供了許多語法更簡潔的包裝龄毡;
不知道你有沒有注意到;
吠卷,Kotlin中省略了分號。
函數(shù)
熟悉Java的你可能會想返回值在哪里那沦零?怎么沒有那祭隔?
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
: Int
這里就出現(xiàn),表示返回值是一個int類型蠢终,那為什么上面那個函數(shù)沒有寫那序攘?其實上面那個函數(shù)也有返回值,返回值是空寻拂,也就是void程奠,在Kotlin中其實是: Unit
,而: Unit
默認(rèn)可以省略祭钉,所以就看不到返回值的聲明了瞄沙。
同樣方法在對比Java的看下:
public static int max(int a, int b) {
if (a > b) {
return a;
} else {
return b;
}
}
是不是發(fā)現(xiàn)了方法體中if的使用好像有區(qū)別?
在Kotlin中慌核,if是表達式距境,而不是語句。語句和表達式的區(qū)別在于垮卓,表達式有值垫桂,并且能作為另一個表達式的一部分使用;而語句總是包圍著它的代碼塊中的頂層元素粟按,并且沒有自己的值诬滩。在Java中霹粥,所用的控制結(jié)構(gòu)都是語句。而在Kotlin中疼鸟,除了循環(huán)(for后控,do,do/while)以外大多數(shù)控制結(jié)構(gòu)都是表達式空镜。
表達式函數(shù)體
如果一個方法的函數(shù)體是由單個表達式構(gòu)成的浩淘,可以用這個表達式作為完整的函數(shù)體,并且去掉花括號和return語句:
fun max(a: Int, b: Int) = if (a > b) a else b
ps:在as中通過alt+enter可以喚起操作吴攒,提供了在兩種函數(shù)風(fēng)格之間轉(zhuǎn)換的方法:"Convert to expression body"(轉(zhuǎn)換成表達式函數(shù)體)和 "Convert to block body"(轉(zhuǎn)換成代碼塊函數(shù)體)
細心的你或許注意到此處的表達式函數(shù)體也沒有寫出返回類型张抄,作為一門靜態(tài)類型的語言,Kotlin不是要求每個表達式都應(yīng)該在編譯期具有類型嗎洼怔?事實上欣鳖,每個變量和表達式都有類型,每個函數(shù)都有返回類型茴厉。但是對表達式體函數(shù)來說,編譯器會分析作為函數(shù)體的表達式什荣,并把它的類型作為函數(shù)的返回類型矾缓,即使沒有顯示得寫出來。這種縫隙通常被稱作類型推導(dǎo)稻爬。
變量
在Java中變量的聲明是從類型開始的嗜闻,就像這樣:
final String str = "this is final string";
int a = 12;
但是在Kotlin中這樣是行不通的,因為許多變量聲明的類型都可以省略桅锄。所以在Kotlin中以關(guān)鍵字開始琉雳,然后是變量名稱,最后可以加上類型(也可以省略):
val str: String = "this is a final string"
var a = 12
其中: String
也是可以省略的友瘤,通過=
右邊推導(dǎo)出左邊變量的類型是String翠肘,就像a
變量省略類型。
可變變量和不可變變量
聲明變量的關(guān)鍵字有兩個:
val
(來自value)——不可變引用辫秧。在初始化之后不能再次賦值束倍,對應(yīng)Java中final修飾符。
var
(來自variable)——可變引用盟戏。這種變量的值可以被改變绪妹,對應(yīng)Java中的普通變量。
雖然var
表示可變柿究,并且如上面看到的也省略的類型邮旷,乍一看似乎和js等腳本語言類似,可以直接賦值另一種類型的值蝇摸,比如這樣:
var a = 12
a = "string"http://錯誤
但實際上婶肩,這樣做是錯誤的办陷,即使var
關(guān)鍵字允許變量改變自己的值,但它的類型卻是改變不了的狡孔。此處a
變量在首次賦值時就確定了類型懂诗,這里的類型是Int
,再次賦值String
類型的值時就會提示錯誤苗膝,并且運行也會發(fā)生ClassCastException殃恒。
注意,盡管val
引用自身是不可變的辱揭,但是它指向的對象可能是可變的离唐,例如:
val languages = arrayListOf("Java")
languages.add("Kotlin")
其實和Java中一致,final定義一個集合问窃,集合中的數(shù)據(jù)是可以改變的亥鬓。
字符串模板
val name = "HuXiBing"
println("Hello, $name!")
這是一個Kotlin的新特性,在代碼中域庇,你申明了一個變量name
嵌戈,并且后面的字符串字面值中使用了它。和許多腳本語言一樣听皿,Kotlin讓你可以在字符串字面值中引用局部變量熟呛,只需要在變量名稱前面加上字符$
,這等價于Java中的字符串鏈接 "Hello, " + name + "!"
尉姨,效率一樣但是更緊湊庵朝。
通過轉(zhuǎn)換成Java代碼,我們可以看到這兩句代碼其實是這樣的:
String name = "HuXiBing";
String var3 = "Hello, " + name + '又厉!';
System.out.println(var3);
當(dāng)然九府,表達式會進行靜態(tài)檢查,如果你試著引用一個不存在的變量覆致,代碼根本不會編譯侄旬。
如果要在字符串中使用$
,你需要對它轉(zhuǎn)義:println("\$x")
會打印$x
煌妈,并不會吧x
解釋成變量的引用勾怒。
還可以引用更復(fù)雜的表達式,而不是僅限于簡單的變量名稱声旺,只需要把表達式用花括號括起來:
println("1 + 2 = ${1 + 2}")
還可以在雙引號中直接嵌套雙引號笔链,只要它們在某個表達式的范圍內(nèi)(即花括號內(nèi)):
val a = 12
println("a ${if (a >= 10) "大于等于10" else "小于10"}")
類和屬性
先來看一個簡單的JavaBean類Person,目前它只有一個屬性: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中澈缺,這種邏輯不用這么多的樣板代碼就可以表達坪创。
使用Convert Java File To Kotlin File將這個對象轉(zhuǎn)換成Kotlin:
class Person(val name: String)
這種只有數(shù)據(jù)沒有其他代碼的類通常被叫做值對象炕婶,許多語言都提供簡明的語法來聲明它們。
注意從Java到Kotlin的轉(zhuǎn)換過程中public
修飾符消失了莱预,在Kotlin中默認(rèn)是public
柠掂,所以可以省略它。
屬性
類的概念就是把數(shù)據(jù)和處理數(shù)據(jù)的代碼封裝成一個單一的實體依沮。在Java中涯贞,數(shù)據(jù)存儲在字段中,通常還是私有的危喉。如果想讓類的使用者訪問到數(shù)據(jù)宋渔,得提供訪問器方法:一個getter,可能還有一個setter辜限。在Person類中你已經(jīng)看到了訪問器的例子皇拣。setter還可以包含額外的邏輯,包括汗蒸傳給它的值薄嫡、發(fā)送關(guān)于變化的通知等氧急。
在Java中,字段和其訪問器的組合常常被叫做屬性毫深,在Kotlin中态蒂,屬性時頭等的語言特性,完全代替了字段和訪問器的方法费什。在類中聲明一個屬性和聲明一個變量一樣:使用val和var關(guān)鍵字。聲明成val的屬性是只讀的手素,而var屬性是可變的鸳址。
class Person(
val name: String,//只讀屬性,生成一個字段和一個簡單的getter
var isMarried: Boolean//可寫屬性:生成一個字段泉懦、一個getter稿黍、一個setter
)
看看轉(zhuǎn)換成Java的代碼可能更清晰一點:
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;
}
}
簡單的說就是平時我們用代碼模板生成的bean,在Kotlin中連模板都不需要使用了崩哩,編譯時會自動生成對應(yīng)的代碼巡球。
在Java中使用應(yīng)該比較熟悉了,是這個是這樣的:
Person person = new Person("HuXiBing", true);
System.out.println(person.getName());
System.out.println(person.isMarried());
生成的getter和setter方法都是在屬性名稱前加上get和set前綴作為方法名邓嘹,但是有一種例外酣栈,如果屬性時以is開頭,getter不會增加前綴汹押,而它的setter名稱中is會被替換成set矿筝。所以你調(diào)用的將是isMarried()
。
而在Kotlin中使用是這樣的:
val person = Person("HuXiBing", true) //調(diào)用構(gòu)造方法不需要關(guān)鍵字new
println(person.name) //可以直接訪問屬性棚贾,但調(diào)用的時getter
println(person.isMarried)
在Kotlin中可以直接引用屬性窖维,不在需要調(diào)用getter榆综。邏輯沒有變化,但代碼更簡潔了铸史”谴可變屬性的setter也是這樣:在Java中,使用person.setMarried(false)
來表示離婚琳轿,而在Kotlin中判沟,可以這樣寫:person.isMarried = false
。
自定義訪問器
如果getter和setter方法中需要額外的邏輯利赋,可以通過自定義訪問器的方式實現(xiàn)水评。例如現(xiàn)在有這樣一個需求:聲明一個矩形,它能判斷自己是否是一個正方形媚送。不需要一個單獨的字段來存儲這個信息中燥,因為可以隨時通過檢查矩形的長寬是否相等來判斷:
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {//聲明屬性的getter
return height == width
}
}
屬性isSquare
不需要字段來保存它的值,它只有一個自定義實現(xiàn)的getter塘偎,它的值是每次訪問屬性的時候計算出來的疗涉。還記得之前的表達式函數(shù)體嗎?此處也可以轉(zhuǎn)成表達式體函數(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類似咱扣,每一個Kotlin文件都能以一條package
語句開頭,而文件中定義的所有聲明(類涵防、函數(shù)及屬性)都會被放到這個包中闹伪。如果其他文件中定義的聲明也有相同的包,這個文件可以直接使用它們壮池;如果包不相同偏瓤,則需要導(dǎo)入它們。和Java一樣椰憋,導(dǎo)入語句放在問價你的最前面使用關(guān)鍵字import
:
package com.huburt.imagepicker
import java.util.Random
Java中的包和導(dǎo)入聲明:
package com.huburt.imagepicker;
import java.util.Random;
僅僅省略了;
還有點不同的時Kotlin不區(qū)分導(dǎo)入是類還是函數(shù)(是的Kotlin的函數(shù)可以單獨存在厅克,不是一定要聲明在類中)。例如:
import com.huburt.other.createRandom
com.huburt.other
是包名橙依,createRandom
是方法名证舟,直接定義在Kotlin文件的頂層函數(shù)。
在Java中窗骑,要把類放在和包結(jié)構(gòu)相匹配的文件與目錄結(jié)構(gòu)中女责。而在Kotlin中包層級機構(gòu)不需要遵循目錄層級結(jié)構(gòu),但是不管怎樣创译,遵循Java的目錄布局更根據(jù)包結(jié)構(gòu)把源碼文件放到對應(yīng)的目錄中是個更好的選擇鲤竹,避免一些不期而遇的錯誤。
表示和處理選擇:枚舉和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
是一個軟關(guān)鍵字碘橘,只有當(dāng)它出現(xiàn)在class前面是才有特殊的意義,在其他地方可以把它當(dāng)做普通的名稱使用吱肌,與此不同的是痘拆,class任然是一個關(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)你聲明每個枚舉常量的時候桥氏,必須提供該常量的屬性值。注意這個向你展示了Kotlin語法中唯一必須使用分號(;
)的地方:如果要在枚舉類中定義任何方法猛铅,就要使用分號把枚舉常量列表和方法定義分開字支。
使用When處理枚舉類
對于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
是一個有返回值的表達式欠雌,因此我們寫一個直接返回when
表達式的表達式體函數(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ù)傳進來的color值找到對應(yīng)的分支。和Java不一樣疙筹,你不需要在每個分支都寫上break
語句(在Java中遺漏break通常會導(dǎo)致bug)富俄。如果匹配成功,只有對應(yīng)的分支會執(zhí)行而咆,也可以把多個值合并到同一個分支霍比,只需要逗號(,
)隔開這些值。
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)中使用任意對象
Kotlin中的when
結(jié)構(gòu)比Java中switch
強大的多。switch
要求必須使用常量(枚舉常量凹蜂、字符串或者數(shù)字字面值)作為分支條件馍驯。而when
允許使用任何對象。我們使用這種特性來寫一個函數(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ù)庫中一個方法玛痊,用于創(chuàng)建Set集合(無序的)汰瘫。
when
表達式把setOf(c1, c2)
生成的set集合依次和所有的分支匹配,直到某個分支滿足條件擂煞,執(zhí)行對應(yīng)的代碼(返回混合后顏色值或者拋出異常)混弥。
能使用任何表達式作為when
的分支條件,很多情況下會讓你的代碼既簡潔又漂亮。
使用不帶參數(shù)的When
你可能意識到上面的例子效率多少有些低蝗拿。沒此調(diào)用這個函數(shù)的時候它都會創(chuàng)建一些Set實例晾捏,僅僅是用來檢查兩種給定的顏色是否和另外兩種顏色匹配。一般這不是什么大問題哀托,但是如果這個函數(shù)調(diào)用很頻繁惦辛,它就非常值得用另一種方式重寫。來避免創(chuàng)建額外的垃圾對象仓手。
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
表達式提供參數(shù)胖齐,分支條件就是任意的布爾表達式。mixOptimized
方法和上面那個例子做了一模一樣的事情嗽冒,這種寫法不會穿件額外的對象呀伙。
智能轉(zhuǎn)換
在Java中經(jīng)常會有這樣一種情形:用父類申明引用一個子類對象,當(dāng)要使用子類的某個方式時添坊,需要先判斷是否是哪個子類剿另,如果是的話在強轉(zhuǎn)成子類對象,調(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中驰弄,編譯器會幫你完成強轉(zhuǎn)的工作。如果你檢查過一個變量是某種類型速客,后面就不需要轉(zhuǎn)換它戚篙,就可以把它當(dāng)做你檢查過的類型使用(調(diào)用方法等),這就是智能轉(zhuǎn)換溺职。
val d = Animal()
if (d is Dog) {
d.dig()
}
這里is
是檢查一個變量是否是某種類型(某個類的實例)岔擂,相當(dāng)于Java中的instanceof
±嗽牛可以看到d
變量是一個Animal
對象乱灵,通過is
判斷是Dog
后,無需強轉(zhuǎn)就能調(diào)用Dog
的方法七冲。
智能轉(zhuǎn)換只在變量經(jīng)過is
檢查且且之后不再發(fā)生變化的情況下有效痛倚。當(dāng)你對一個類的屬性進行智能轉(zhuǎn)換的時候,這個屬性必須是一個val
屬性澜躺,而且不能有自定義的訪問器蝉稳。否則,每次對屬性的訪問是否都能返回相同的值將無從驗證掘鄙。
在Kotlin中用as
關(guān)鍵字來顯示轉(zhuǎn)換類型(強轉(zhuǎn)):
val d = Animal()
val dog = d as Dog
ps:其實只是省略強轉(zhuǎn)代碼耘戚,個人感覺作用不是很明顯。
用When代替If
Kotlin和Java中if
有什么不同操漠,之前已經(jīng)提到過了收津。如if
表達式用在適用Java三元運算符的上下文中:if (a > b) a else b
(Kotlin)和a > b ? a : b
(Java)效果一樣。Kotlin沒有三元運算符,因為if
表達式有返回值撞秋,這一點和Java不同长捧。
對于較少的判斷分支用if
沒有問題,但是較多的判斷分支則用when
是更好的選擇部服,有相同的作用唆姐,并且都是表達式,都有返回值廓八。
代碼塊作為If和When的分支
上面的例子滿足條件的分支執(zhí)行只有一行代碼奉芦,但如果某個分支中代碼不止一行還如何處理那?當(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
}
代碼塊中最后一個表達式就是結(jié)果剧蹂,也就是返回值声功。
對比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ì)上就是兩個值之間的間隔,這兩個值通常是數(shù)字:一個起始值简烤,一個結(jié)束值剂邮,使用..
運算符來表示區(qū)間:
val oneToOne = 1 .. 10
注意Kotlin的區(qū)間是包含的或者閉合的,意味著第二個值始終是區(qū)間的一部分横侦。如果不想包含最后那個數(shù)挥萌,可以使用函數(shù)until
創(chuàng)建這個區(qū)間:val x = 1 until 10
,等同于val x = 1 .. 9
你能用整數(shù)區(qū)間做的最基本的事情就是循環(huán)迭代其中所有的值枉侧。如果你能迭代區(qū)間中所有的值引瀑,這樣的區(qū)間被稱作數(shù)列。
我們用整數(shù)迭代來玩Fizz-Buzz游戲榨馁。游戲玩家輪流遞增計數(shù)憨栽,遇到能被3整除的數(shù)字就用單詞fizz代替,遇到能被5整除的數(shù)字則用單詞buzz代替翼虫,如果一個數(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>
灭衷,因此上面這個例子遍歷了這個數(shù)列次慢,并調(diào)用fizzBuzz方法。
假設(shè)想把游戲變得復(fù)雜一點,那我們可以從100開始倒著計數(shù)迫像,并且只計偶數(shù)劈愚。
for (i in 100 downTo 1 step 2) {
print(fizzBuzz(i))
}
這里100 downTo 1
是遞減的數(shù)列(默認(rèn)步長是1),并且設(shè)置步長step
為2闻妓,表示每次減少2菌羽。
迭代map
我們用一個打印字符二進制表示的小程序作為例子。
val binaryReps = TreeMap<Char, String>()//使用TreeMap讓鍵排序
for (c in 'A'..'F') {//使用字符區(qū)間迭代從A到F之間的字符
val binary = Integer.toBinaryString(c.toInt())//吧ASCII碼換成二進制
binaryReps[c] = binary//根據(jù)鍵c把值存入map
}
for ((letter, binary) in binaryReps) {//迭代map由缆,把鍵和值賦給兩個變量
println("$letter = $binary")
}
..
語法不僅可以創(chuàng)建數(shù)字區(qū)間注祖,還可以創(chuàng)建字符區(qū)間。這里使用它迭代從A到F的所有字符均唉,包括F是晨。
for循環(huán)允許展開迭代中集合的元素(map的鍵值對),把展開的結(jié)果存儲到兩個獨立的變量中:letter是鍵舔箭,binary是值罩缴。
map中可以根據(jù)鍵老訪問和更新map的簡明語法。使用map[key]
讀取值层扶,并使用map[key] = value
設(shè)置它們箫章,而不需要地愛用get和put。這段binaryReps[c] = binary
等價于Java中的binaryReps.put(c, binary);
你還可以使用展開語法在迭代集合的同時跟蹤當(dāng)前項的下標(biāo)镜会。不需要創(chuàng)建一個單獨的變量來存儲下標(biāo)并手動增加它:
val list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()) {
println("$index : $element")
}
使用in檢查集合和區(qū)間的成員
使用in
運算符來檢查一個值是否在區(qū)間中檬寂,或者它的逆運算!in
來檢查這個值是否不在區(qū)間中。下面展示了如何使用in
來檢查一個字符是否屬于一個字符區(qū)間稚叹。
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'z'
fun isNotDigit(c: Char) = c !in '0'..'9'
這種檢查字符是否是英文字符的技巧看起來很簡單焰薄。在底層,沒有什么特別處理扒袖,依然會檢查字符的編碼是否位于第一個字母編碼和最后一個字母編碼之間的某個位置(a <= c && c <= z
)塞茅。但是這個邏輯被簡潔地隱藏到了標(biāo)準(zhǔn)庫中的區(qū)間類實現(xiàn)。
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..."
}
Kotlin中的異常
Kotlin的異常處理語句基本形式與Java類似野瘦,除了不需要new
關(guān)鍵字,并且throw
結(jié)構(gòu)是一個表達式飒泻,能作為另一個表達式的一部分使用鞭光。
val b = if (a > 0) a else throw Exception("description")
try、catch泞遗、finally
和Java一樣惰许,使用帶有catch
和finally
子句的try
結(jié)構(gòu)來處理異常,下面這個例子從給定的文件中讀取一行史辙,嘗試把它解析成一個數(shù)字汹买,返回這個數(shù)字佩伤;或者當(dāng)這一行不是一個有效數(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獨特的null機制,不帶?
標(biāo)識的聲明無法賦值null见妒,在之后的文章中會具體介紹孤荣。
和Java最大的區(qū)別就是throws
子句沒有出現(xiàn)在代碼中:如果用Java來寫這個函數(shù),你會顯示地在函數(shù)聲明的后寫上throws IOException
须揣。你需要這樣做的原因是IOException
是一個受檢異常盐股。在Java中,這種異常必須顯示地處理耻卡。必須申明你的函數(shù)能拋出的所有受檢異常遂庄。如果調(diào)用另外一個函數(shù),需要處理這個函數(shù)的受檢異常劲赠,或者聲明你的函數(shù)也能拋出這些異常涛目。
和其他許多現(xiàn)在JVM語言一樣,Kotlin并不區(qū)分受檢異常和未受檢異常凛澎。不用指定函數(shù)拋出的異常霹肝,而且可以處理也可以不處理異常。這種設(shè)計是基于Java中使用異常實踐做出的決定塑煎。經(jīng)驗顯示這些Java規(guī)則常常導(dǎo)致許多毫無意義的重新拋出或者忽略異常的代碼沫换,而且這些規(guī)則不能總是保護你免受可能發(fā)生的錯誤。
try作為表達式
Kotlin中try
關(guān)鍵字就像if和when一樣最铁,引入了一個表達式讯赏,可以把它的值賦給一個變量。例如上面這個例子也可以這樣寫:
fun readNumber(reader: BufferedReader): Int? =
try {
val line = reader.readLine()
Integer.parseInt(line)
} catch (e: NumberFormatException) {
null
} finally {
reader.close()
}
如果一個try
代碼塊執(zhí)行一切正常冷尉,代碼塊中最后一個表達式就是結(jié)果漱挎。如果捕獲到了一個異常,相應(yīng)的catch
代碼塊中最后一個表達式就是結(jié)果雀哨。