至今都不敢相信,我有一天會寫kotlin的文章,雖然Kotlin在Google I/O中的推出并表示也作為安卓開發(fā)的官方語言,Kotlin的發(fā)展也是越來越迅猛,但Java仍然是很多開發(fā)者的首選語言,之前習(xí)也慣了java開發(fā),Kotlin這個詞僅存在和朋友的聊天記錄中。
但是公司現(xiàn)在的項目都是kotlin開發(fā),項目開發(fā)過程中和其他時間也學(xué)習(xí)了一段時間,發(fā)現(xiàn)還是挺舒服的,所以在此分享些心得吧。
真正接觸才發(fā)現(xiàn)呢,有挺多優(yōu)點的
1.語法簡便
Kotlin的語法簡單,Java語言有嚴(yán)格的數(shù)據(jù)類型,轉(zhuǎn)換類型就會很繁瑣,同時Kotlin也不用像Java那樣中不斷的非空判斷,沒有語言基礎(chǔ)的學(xué)起來也很輕松。
2.提高開發(fā)效率
憑借簡潔直觀的語法,Kotlin提高了團(tuán)隊的效率,編寫和部署程序需要更少的代碼行和時間。
3.強(qiáng)大的兼容
a.與 Java 可互操作,可以在 Kotlin 中編寫新模塊,可以與現(xiàn)有 Java 代碼協(xié)同工作;
b.最新版本的Kotlin與之前的所有版本都是反向兼容;Kotlin 兼容所有 Java 庫和框架以及JVM;
c.可以與 Gradle 或 Maven 構(gòu)建系統(tǒng)進(jìn)行整合;
d.idea和android studio都有對應(yīng)Java轉(zhuǎn)kotlin的插件,原有的Java代碼可以直接轉(zhuǎn)換。
接下來的介紹就從和java的對比開始
類定義
java
public class Artist {
2 private long id;
3 private String name;
4 private String blog;
5
6 public long getId() {
7 return id;
8 }
9
10 public void setId(long id) {
11 this.id = id;
12 }
13
14 public String getName() {
15 return name;
16 }
17
18 public void setName(String name) {
19 this.name = name;
20 }
21
22 public String getBlog() {
23 return blog;
24 }
25
26 public void setBlog(String blog) {
27 this.blog = blog;
28 }
29
30 @Override public String toString() {
31 return "Artist{" +
32 "id=" + id +
33 ", name='" + name + '\'' +
34 ", blog='" + blog + '\'''}';
35 }
36 }
之前的話屬性+設(shè)置+獲取+toString(),一個數(shù)據(jù)類的基本功能就有了,三個屬性的類寫了三十幾行代碼
kotlin
1 data class Artist(
2 var id: Long,
3 var name: String,
4 var blog: String
5 )
對象創(chuàng)建
Java:
1 Artist artist = new Artist(1, "Dylan", "http://www.cnblogs.com/tgyf/");
Kotlin:
1 var artist = Artist(1, "Dylan", "http://www.cnblogs.com/tgyf/")
Kotlin對象創(chuàng)建不用加new關(guān)鍵字,而且語句后面不用加分號";"(即使加上也會被忽略)。
toString()
而類名前的data關(guān)鍵字,是顯示聲明該類是作為數(shù)據(jù)類使用,通過toString()打印的結(jié)果可以看出區(qū)別,打印語句:
1 println("artist.toString(): " + artist.toString())
不加data結(jié)果:
artist.toString(): Artist@61bbe9ba
加上data結(jié)果:
artist.toString(): Artist(id=1, name=Dylan, blog=http://www.cnblogs.com/tgyf/)
可以看到toString()是Kotlin自動生成的,如果類聲明不加data,只會打印出一串?dāng)?shù)字(應(yīng)該是類的內(nèi)存地址),而不是當(dāng)前對象的屬性信息。
空安全
空指針這個錯誤不用多說,太常見了,Kotlin提供了一種安全機(jī)制,盡量減少變量在使用前是null的情況。
Java:
1 String str;
2 if (str != null) {
3 //do something
4 }
對于Java代碼,編譯器不會強(qiáng)制每次使用引用變量之前進(jìn)行null判斷,即異常往往會在運(yùn)行時報出,但這正是危險所在。
Kotlin:
1 var str1: String = null //Null can not be a value of a non-null type String
2 var str2: String? = null //str2 can be null
3 var str3 = "testNull" //non-null--String type
4 var str4 = null //null
5 var str5: String //non-null--String type
6 str5 = "testNull" //assigned String value
7 var str6 //no type or initialization
8 var str7: String? = "testNull"
結(jié)合代碼中的注釋,我們來看這四行代碼想表達(dá)的意思。
第1行,編譯錯誤,kotlin規(guī)定如果顯式指明了str1的類型,這里是String,聲明時必須同時指定是否允許為空值(null),不加問號"?"表示不允許為null;
第2行,編譯通過,作第一行代碼的另一種情況,加了問號,并賦值為null;
第3行,編譯通過,隱式賦值為"testNull",Kotlin會自動推斷出str3類型為String,之后便不可再更改了,即不可再賦值為1這種整形數(shù)據(jù);
第4行,編譯通過,隱式賦值為null,那么str4就一直為null了;
第5-6行,編譯通過,前者只是指定類型,沒有賦值;后者賦予str5 String類型值"testNull"同樣不能賦值為其他類型值;
第7行,編譯錯誤,既沒有指定類型,也沒有隱式地進(jìn)行初始化,錯誤的原因應(yīng)該是編譯器不知道str6類型是什么,不能對其分配空間;
第8行,不需多解釋,str7可為null,同時賦值為"testNull";
注意:此文為了格式統(tǒng)一,沒有將編譯或運(yùn)行出錯的代碼注釋,分享的項目代碼中是可以正常編譯并運(yùn)行的。
解釋完變量定義時關(guān)于空的概念,接下來就該看看這種保護(hù)機(jī)制能否真的讓我們省心。就拿獲取字串的長度為例,Kotlin中String類有個length屬性,即調(diào)用方式為strObject.length。
有兩種形式定義的變量不用擔(dān)心(1 類型為String且不允許為null;2 類中不包含length屬性), 理由很簡單,前者不會出現(xiàn)null異常,后者獲取length屬性在編譯階段就會出錯,或者說在敲完代碼時編譯器就會標(biāo)紅提示了。所以,String類型但允許為null的才需要我們關(guān)注,因為這時候有可能出現(xiàn)運(yùn)行時異常。
對于聲明為String?的變量,訪問屬性時會涉及到問號和雙感嘆號兩個操作符("?"和"!!"),前者表示執(zhí)行后面代碼前先檢查變量賦值情況,后者表示不檢查而直接訪問屬性(危險)。
要理解清楚,最好的方法就是讓代碼說話。
1 var str2: String? = null
2 println("str2.length: " + str2.length) //compile error
3 println("str2?.length: " + str2?.length) //print null
4 println("str2!!.length: " + str2!!.length) //run exception
5 if (str2 != null) {
6 println("str2!!.length: " + str2!!.length) //don't run
7 }
8 str2 = "testNull" //assign
9 println("str2.length: " + str2.length) //print 8
10 println("str2?.length: " + str2?.length) //print 8
11 println("str2!!.length: " + str2!!.length) //print 8
12 if (str2 != null) {
13 println("str2!!.length: " + str2.length) //print 8
14 }
第2行,編譯錯誤,因為之前只是將str2聲明為可以是null同時賦值為null,所以緊接著訪問其length屬性是不允許的;
第3行,輸出"null",加了問號就會先檢查str2的賦值情況,如果是null,就不繼續(xù)執(zhí)行后半部分(.length),直接返回null;
第4行,運(yùn)行異常,不檢查的后果就是通過null引用去訪問length屬性;
第5-7行,不會執(zhí)行到if代碼塊中,這里用了類似Java中的做法;
第9行,輸出"8",到這里,相比能體會到Kotlin的智能之處了,在第八行對str2賦值之后,就不會再像第二行那樣報編譯錯誤了;
第10-14行,不需多解釋,不為null的str2,通過三種方式均可訪問length屬性;
那么這里有一個疑問,用"!!"來訪問屬性是不明智的選擇,好像"?"更穩(wěn)妥一些?畢竟后者在變量是否null的情況下都能做出相應(yīng)的處理。我所能想到的需要用"!!"的場景之一是:當(dāng)一個變量在聲明時不能馬上初始化,而在真正用到時又必須是非null的。這種情況應(yīng)該并不少見吧,那次此時"!!"就派上用場了。
先舉一個簡單粗暴的列子:
1 var str: String? = null
2 //do something to assign str
3 val str2: String = str!!
當(dāng)聲明str的時候還需后面的處理結(jié)果給它賦值,而聲明str2為非null,就必須以str!!的形式才能通過編譯。
下面再給出Android中Application類單例化代碼,就不做解釋了。
1 class App : Application() {
2 companion object {
3 private var instance: Application? = null
4 fun instance() = instance!!
5 }
6 override fun onCreate() {
7 super.onCreate()
8 instance = this
9 }
10 }
類方法擴(kuò)展
這個特性支持在現(xiàn)有類的基礎(chǔ)上擴(kuò)展方法,特別是系統(tǒng)庫中的類,因為如果是我們自定義的類,那么擴(kuò)展和添加方法沒有什么差別。
方法定義
1 fun getArtict(): Artist? {
2 return null
3 }
Kotlin中是以fun關(guān)鍵字聲明方法,沒有返回值時不需要在方法名后面寫任何類型,默認(rèn)是Unit類型(可寫可不寫,但其和null不是一回事,所以不寫返回值類型或者寫了Unit后不能夠返回null)。
擴(kuò)展
1 fun String.printStr() {
2 println("printStr: " + this)
3 }
4
5 var str = "testExtend"
6 str.printStr()
上面代碼為類String擴(kuò)展了一個printStr(),這在Java中是不可能的。因為Java中如果既不能改變原有類,又想在其基礎(chǔ)上添加方法,就得通過新建類來繼承的方式。而現(xiàn)實是Java中只能是單繼承,這個機(jī)會太珍貴了,更殘酷的是有些類還是不能繼承的。
代碼第5-6行執(zhí)行結(jié)果為:
printStr: testExtend
可見,通過this關(guān)鍵字即可獲取到對象(調(diào)用者)的值。
lambda表達(dá)式
這部分測試代碼沒有在分享的項目中,因為涉及到Android開發(fā),需要在Android項目中才能編譯或運(yùn)行,可以參考這篇。
下面以綁定控件,設(shè)置按鈕點擊事件監(jiān)聽,點擊后改變文本顯示為例。
Java:
1 Button button = (Button) findViewById(R.id.button);
2 TextView text = (TextView) findViewById(R.id.text);
3 button.setOnClickListener(new View.OnClickListener() {
4
5 @Override
6 public void onClick(View v) {
7 text.setText("Set text after click button");
8 }
9 });
做過Android開發(fā)的對這段代碼太熟悉了,盡管目前已經(jīng)出了很多開源庫,比如ButterKnife等可以不必使用findViewById()而實現(xiàn)快速綁定,但畢竟還是需要手動綁定這一步。
Kotlin:
1 button.setOnClickListener {
2 text.setText("Set text after click button")
3 text.text = "Set text after click button"
4 }
其中,buttonR.id.button,第一個textR.id.text,第二個text~TextView顯示文本。第2-3行是設(shè)置文本的兩種方式,Kotlin建議用更簡潔的第二種.text,這也是文章開頭定義數(shù)據(jù)類時屬性采用默認(rèn)訪問修飾的原因,因為private屬性就不能直接通過"."直接獲取了。
如果遇到多個Button需要共享一個onClick()怎么辦呢?Java代碼就不給出了,來看Kotlin代碼:
1 button1.setOnClickListener(this)
2 button2.setOnClickListener(this)
3 button3.setOnClickListener(this)
4
5 override fun onClick(view: View) {
6 val id = view.id
7 when (id) {
8 R.id.button1 -> selectImageBtn()
9 R.id.button2 -> clearImageBtn()
10 R.id.button3 -> sendBulletinBtn()
11 else -> { }
12 }
13 }
第1-3行,除了不需要調(diào)用findViewById()來獲取控件,設(shè)置事件監(jiān)聽和Java是類似的;
第5-13行,重寫關(guān)鍵字override,前面不必寫"@"符號,用when、->及else組合來替代原先的switch、case及default,再也不用為每種case的結(jié)尾寫上break。
總結(jié)
這篇文章先寫這么多吧,提到的知識僅是Kotlin的九牛一毛,我覺得作為開發(fā)者,學(xué)習(xí)是很有必要的,在熟練掌握了某種語言的基礎(chǔ)上,學(xué)習(xí)新的語言,有助于提升個人競爭力。