原文2019年2月21日發(fā)表于微信公眾號 [Stephen的技術(shù)博客]
將Kotlin引入Android項目
自從2017年Kotlin被Google確定為Android官方開發(fā)語言,已經(jīng)有越來越多的小伙伴將Kotlin引入到了項目中孤紧,而且Kotlin本身的坑也被越填越平妒貌,想了一下肴敛,是時候嘗試把Kotlin引入到我們的項目里了他爸。這篇文章就來對比一下引入Kotlin之后到底開發(fā)效率獲得了怎樣的提升故黑。
目標
首先定個小目標遥诉,我們不是要把項目里面的存量Java代碼全部轉(zhuǎn)為Kotlin拢驾,而是令Kotlin與Java共存,Kotlin可以調(diào)用原有的Java代碼定铜,Java代碼也可以調(diào)用新引入的Kotlin阳液,用Kotlin開發(fā)新功能,充分利用它的新特性揣炕。
詳細對比
那么接下來就開始帘皿。要引入Kotlin,其實只需做以下配置(Android Studio):
project下的gradle
buildscript {
ext.kotlin_version = '1.2.41'
...
dependencies
{
...
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
app下的gradle
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
然后我們找一個布局比較簡單的Activity畸陡,將Java轉(zhuǎn)成Kotlin鹰溜,走起:
右鍵點擊一個java文件,會出現(xiàn)以下Menu:
選擇Convert Java File to Kotlin File丁恭,java類瞬間轉(zhuǎn)成了kotlin曹动,是不是很強大_
對比一下原先java的CGMoreActivity代碼行數(shù)是242,轉(zhuǎn)成kotlin后縮減到了171牲览!下面具體來看kotlin究竟做了哪些改變:
不用再寫findViewById了
大家是不是對findViewById已經(jīng)深惡痛絕了呢墓陈?認為大可不必寫這樣的代碼?那么引入kotlin之后竭恬,你的夢想就實現(xiàn)了跛蛋。在Convert之后熬的,IDE不會自動把findViewById的代碼刪除掉痊硕,但是你可以手動把這些代碼刪除,然后用xml上view的id直接調(diào)用這個view押框。舉個例子岔绸,有這么一個view:
<LinearLayout android:id="@+id/ll_more_one" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:background="@drawable/white_bg_selector" android:gravity="center" android:orientation="vertical" android:paddingBottom="15dp" android:paddingTop="15dp"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" android:src="@mipmap/more_one" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="@string/safe_protect" android:textColor="@color/black2" android:textSize="13dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:text="@string/more_fragment_text1" android:textColor="@color/grey4" android:textSize="11dp" /> </LinearLayout>
它的id是ll_more_one,在CGMoreActvity.kt里面你就可以直接這樣寫:
ll_more_one!!.setOnClickListener { val intent = Intent(_activity, CGWebViewActivity::class.java) intent.putExtra("url", GlobalConstants.getUrlSafeInsurance()) intent.putExtra("title", getString(R.string.safe_insurance)) intent.putExtra("disableShare", true) startActivity(intent) }
不用寫非空判斷了
我們寫java代碼的時候橡伞,總會擔(dān)心這個ll_more_one會是null盒揉,所以就這樣寫:
java:
if(ll_more_one != null){ ll_more_one.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ... } });}
kotlin:
ll_more_one!!.setOnClickListener { ... }
!!表示如果這個ll_more_one是null,就會拋出空指針異常兑徘,但是你不用顯式的判斷刚盈。那么如果你想讓這個變量即使為null,編譯器也不報異常挂脑,可以將!!改成?藕漱。
沒有new關(guān)鍵字欲侮,不用寫匿名內(nèi)部類了
相信大家在上面也看到了,以前累贅的new View.OnClickListener()的代碼不復(fù)存在肋联,在kotlin里面威蕉,如果要獲得一個類的實例,直接調(diào)用ClassName()橄仍。
靜態(tài)變量轉(zhuǎn)變成了伴生對象
接著再把我們封裝的Retrofit工廠類做個convert韧涨。由于kotlin里沒有靜態(tài)變量的概念,原先的靜態(tài)變量侮繁,靜態(tài)方法統(tǒng)統(tǒng)轉(zhuǎn)成伴生對象虑粥。
java:
public class HttpUtil { private static final int DEFAULT_TIMEOUT = 10; private static ApiService apiService, cacheApiService; /** * 初始化獲取代理對象 */ public static ApiService api() { if (apiService == null) { synchronized (HttpUtil.class) { if (apiService == null) { retrofit2.Retrofit retrofit = new retrofit2.Retrofit.Builder() .baseUrl(GlobalConstants.getApiHost()) .addConverterFactory(GsonConverterFactory.create())//添加gson轉(zhuǎn)換器 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//添加rxjava轉(zhuǎn)換器 .client(getOkHttpClient(false))//構(gòu)建對應(yīng)的OkHttpClient .build(); apiService = retrofit.create(ApiService.class); } } } return apiService; } ...}
kotlin:
class HttpUtil { companion object { private val DEFAULT_TIMEOUT = 10 private var apiService: ApiService? = null private var cacheApiService: ApiService? = null /** * 初始化獲取代理對象 */ fun api(): ApiService? { if (apiService == null) { synchronized(HttpUtil::class.java) { if (apiService == null) { val retrofit = retrofit2.Retrofit.Builder() .baseUrl(GlobalConstants.getApiHost()) .addConverterFactory(GsonConverterFactory.create())//添加gson轉(zhuǎn)換器 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//添加rxjava轉(zhuǎn)換器 .client(getOkHttpClient(false))//構(gòu)建對應(yīng)的OkHttpClient .build() apiService = retrofit.create(ApiService::class.java) } } } return apiService } } ...}
在一個對象里面定義這些靜態(tài)變量和方法,調(diào)用的時候要這樣寫:
HttpUtil.Companion.api().getCheckNoticeNew(params).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new RetrofitObserver<CheckNoticeNew>() { ... });
改動也不算大宪哩。
強大的data class
kotlin有一個強大的新特性data class舀奶,令我對它愛不釋手,來對比一下使用data class前后的代碼簡潔度:
java:
public class Developer { private String name; private int age; public Developer(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Developer developer = (Developer) o; if (age != developer.age) return false; return name != null ? name.equals(developer.name) : developer.name == null; } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + age; return result; } @Override public String toString() { return "Developer{" + "name='" + name + '\'' + ", age=" + age + '}'; }}
kotlin:
data class Developer(var name: String, var age: Int)
沒錯斋射!就是這樣一行代碼搞定育勺,編譯器為我們自動實現(xiàn)了getters,setters罗岖,equals涧至,toString,hashCode這些方法桑包。那么接下來就來改造一下我們的GSON解析類南蓬。
java:
public class BannerItemData extends CommonJson { public Data data; public BannerItemData(String code, String message) { super(code, message); } public class Data { public List<BannerItem> adList; }}
kotlin:
data class BannerItemData(val data: Data) : CommonJson()data class Data(val adList: List<BannerItem>)
這里有兩個地方要注意,繼承一個類的時候默認調(diào)用他的無參構(gòu)造方法哑了,如果沒有要加上赘方;如果需要嵌套類,可以直接在下面寫一個弱左,如例子中的Data類窄陡。
除此之外,還有其他的一些新特性拆火。
簡單快捷的字符串拼接
java:
String firstName = "Amit";String lastName = "Shekhar";String message = "My name is: " + firstName + " " + lastName;
kotlin:
val firstName = "Amit"val lastName = "Shekhar"val message = "My name is: $firstName $lastName"
用java拼接字符串時必須將""字符串與變量用+連接起來跳夭,相當(dāng)繁瑣,在kotlin直接用一個""就可以了们镜,在其中用$引用變量即可币叹。
簡便的when語句
java:
int score = // some score;String grade;switch (score) { case 10: case 9: grade = "Excellent"; break; case 8: case 7: case 6: grade = "Good"; break; case 5: case 4: grade = "OK"; break; case 3: case 2: case 1: grade = "Fail"; break; default: grade = "Fail"; }
kotlin:
var score = // some scorevar grade = when (score) { 9, 10 -> "Excellent" in 6..8 -> "Good" 4, 5 -> "OK" in 1..3 -> "Fail" else -> "Fail"}
在java里面用switch語句進行分支判斷,即使可以合并一些結(jié)果相同的case項模狭,但是代碼仍然冗長颈抚;在kotlin則可以巧妙的使用,和in將同類項輕松合并,代碼簡潔度迅速提高嚼鹉。
簡便的map遍歷
java:
for (Map.Entry<String, String> entry: map.entrySet()) { }
kotlin:
for ((key, value) in map) { }
Map.Entry作為java中遍歷map的迭代器贩汉,令代碼的復(fù)雜度大大提高九妈,而kotlin中根本不需要用這種復(fù)雜的方法。
簡便的字符串拆分
java:
String[] splits = "param=car".split("=");String param = splits[0];String value = splits[1];
kotlin:
val (param, value) = "param=car".split("=")
再不需要定義String數(shù)組雾鬼,從數(shù)組中取出拆開的字符串萌朱,kotlin的split函數(shù)可以將值賦給對應(yīng)的變量。
對象拷貝
java:
public class Developer implements Cloneable { private String name; private int age; public Developer(String name, int age) { this.name = name; this.age = age; } @Override protected Object clone() throws CloneNotSupportedException { return (Developer)super.clone(); }}// cloning or copyingDeveloper dev = new Developer("Mindorks", 30);try { Developer dev2 = (Developer) dev.clone();} catch (CloneNotSupportedException e) { // handle exception}
kotlin:
data class Developer(var name: String, var age: Int)// cloning or copyingval dev = Developer("Mindorks", 30)val dev2 = dev.copy()// in case you only want to copy selected propertiesval dev2 = dev.copy(age = 25)
前面提到的data class還自動實現(xiàn)了clone方法策菜,但當(dāng)然在kotlin這邊叫copy方法晶疼,而且如果只是想修改其中的部分屬性值,也是相當(dāng)輕松的又憨。
標簽
//1fun foo() { ints.forEach lit@ { if (it == 0) return@lit print(it) }}//2fun foo() { ints.forEach { if (it == 0) return@forEach print(it) }}
既允許先定義標簽翠霍,例如lit@,在forEach循環(huán)return處用@lit就回到了標簽定義處蠢莺,繼續(xù)forEach的下一個循環(huán)寒匙;又允許直接用@forEach跳到.forEach處,繼續(xù)下一個循環(huán)躏将。2是對1寫法的簡化锄弱,兩者輸出結(jié)果相同。
實現(xiàn)List的排序
java:
List<Profile> profiles = loadProfiles(context);Collections.sort(profiles, new Comparator<Profile>() { @Override public int compare(Profile profile1, Profile profile2) { if (profile1.getAge() > profile2.getAge()) return 1; if (profile1.getAge() < profile2.getAge()) return -1; return 0; }});
kotlin:
val profile = loadProfiles(context)profile.sortedWith(Comparator({ profile1, profile2 -> if (profile1.age > profile2.age) return@Comparator 1 if (profile1.age < profile2.age) return@Comparator -1 return@Comparator 0}))
kotlin中對Comparator的實現(xiàn)祸憋,輕松使用lambda語法以及標簽会宪,代碼顯得簡潔優(yōu)雅。
說了kotlin與java對比的這么多優(yōu)點蚯窥,主要代碼簡潔程度的極大提高掸鹅,大家是不是躍躍欲試了呢?不過凡事都有兩面拦赠,我在這里再給大家總結(jié)一下到目前為止我發(fā)現(xiàn)的使用kotlin的缺點巍沙。
kotlin的缺點
代碼可讀性降低
毫無疑問,java雖然語法繁瑣荷鼠,但是由于他本身的強類型句携、面向?qū)ο蟮葘傩裕诜爆嵉耐瑫r語法也比較單一颊咬,代碼寫出來如行云流水务甥,可讀性高牡辽,這也是他擁有眾多程序員的原因之一喳篇。反觀kotlin,代碼雖然簡潔了态辛,但是就像js那樣麸澜,可讀性是比較低的。
需要投入學(xué)習(xí)成本
如果原先只是一個單純使用java做Android開發(fā)的程序員奏黑,沒有學(xué)習(xí)過js炊邦,python等腳本語言编矾,又或者即使學(xué)過,但仍然需要花一些時間熟習(xí)kotlin這門語言馁害,才能輕松使用窄俏。而且如果在一個正在開發(fā)中的項目里面引入kotlin,通常都不會是把java全部替換成kotlin碘菜,而是混用凹蜈,這樣又會有同時使用兩種語言的情況,需要在兩種語法之間切換忍啸。
小部分代碼更繁瑣了
java:
String appUrl = mVersionUpdate.appUrl.trim();
kotlin:
val appUrl = mVersionUpdate!!.appUrl.trim { it <= ' ' }
調(diào)用字符串的trim方法仰坦,在java里面trim()就完了,而kotlin還要寫一段{it <= ' '}计雌。
總括來講悄晃,瑕不掩瑜,kotlin還是一種比java更優(yōu)秀的編程語言凿滤,而且更接近現(xiàn)代的編程語言妈橄,將是未來的趨勢。至于是否在項目中采用翁脆,相信各位看官心里自有考量眷细。