寫在文前
本文將展示在Android中會遇到的實(shí)際問題,并且使用Kotlin怎么去解決它們钩蚊。一些Android開發(fā)者在處理異步茬缩、數(shù)據(jù)庫或者處理Activity中非常冗長的listener時發(fā)現(xiàn)了很多的問題。通過一個個真實(shí)的場景敲长,我們一邊解決問題一邊學(xué)習(xí)Kotlin的特性。
快速上手
如果不知道如何在Kotlin中寫一個相當(dāng)簡單的Java表達(dá)式秉继。這里有一個簡單的訣竅祈噪,就是在AndroidStudio的Java文件中編寫一段代碼,然后將其粘貼到kt文件中尚辑,它會自動轉(zhuǎn)換為Kotlin辑鲤。
Kotlin優(yōu)勢
它更加易表現(xiàn):這是它最重要的優(yōu)點(diǎn)之一。你可以編寫少得多的代碼杠茬。
它更加安全:Kotlin是空安全的月褥,也就是說在我們編譯時期就處理了各種null的情況,避免了執(zhí)行時異常瓢喉。你可以節(jié)約很多調(diào)試空指針異常的時間宁赤,解決掉null引發(fā)的bug。
它可以擴(kuò)展函數(shù):這意味著栓票,就算我們沒有權(quán)限去訪問這個類中的代碼礁击,我們也可以擴(kuò)展這個類的更多的特性。
它是函數(shù)式的:Kotlin是基于面向?qū)ο蟮恼Z言逗载。但是就如其他很多現(xiàn)代的語言那樣,它使用了很多函數(shù)式編程的概念链烈,比如厉斟,使用lambda表達(dá)式來更方便地解決問題。其中一個很棒的特性就是Collections的處理方式强衡。我稍后會進(jìn)行介紹擦秽。
它是高度互操作性的:你可以繼續(xù)使用所有用Java寫的代碼和庫,甚至可以在一個項(xiàng)目中使用Kotlin和Java兩種語言混合編程。一行Java一行Kotlin感挥,別提有多風(fēng)騷了缩搅。
詳細(xì)實(shí)例
1. 易表現(xiàn)和簡潔性
通過Kotlin,可以更容易地避免模版代碼触幼,因?yàn)榇蟛糠值牡湫颓闆r都在語言中默認(rèn)覆蓋實(shí)現(xiàn)了硼瓣。
舉個例子,在Java中置谦,如果我們要典型的數(shù)據(jù)類堂鲤,我們需要去編寫(至少生成)這些代碼:
public class User{
private long id;
private String name;
private String url;
private String mbid;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getMbid() {
return mbid;
}
public void setMbid(String mbid) {
this.mbid = mbid;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", url='" + url + '\'' +
", mbid='" + mbid + '\'' +
'}';
}
}
我們在不使用第三方框架的基礎(chǔ)上,需要大量的set get方法和復(fù)寫基礎(chǔ)方法媒峡。
而使用Kotlin瘟栖,我們只需要通過data
關(guān)鍵字:
data class User(
var id: Long,
var name: String,
var url: String,
var mbid: String)
這個數(shù)據(jù)類,它會自動生成所有屬性和它們的訪問器谅阿, 并自動生成相應(yīng)的 equals半哟、hashcode、toString 方法签餐。
空口無憑寓涨,我們驗(yàn)證一下:
首先建立一個kt文件,新建一個簡單的User類:
data class User(var name: String)
這時候在命令行使用kotlinc編譯贱田,得到一個class文件缅茉,反編譯成Java文件,可以看到:
public final class User {
@NotNull
private String name;
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name = var1;
}
public User(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.name = name;
}
// 解構(gòu)聲明
@NotNull
public final String component1() {
return this.name;
}
@NotNull
public final User copy(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
return new User(name);
}
// $FF: synthetic method
// $FF: bridge method
@NotNull
public static User copy$default(User var0, String var1, int var2, Object var3) {
if((var2 & 1) != 0) {
var1 = var0.name;
}
return var0.copy(var1);
}
public String toString() {
return "User(name=" + this.name + ")";
}
public int hashCode() {
return this.name != null?this.name.hashCode():0;
}
public boolean equals(Object var1) {
if(this != var1) {
if(var1 instanceof User) {
User var2 = (User)var1;
if(Intrinsics.areEqual(this.name, var2.name)) {
return true;
}
}
return false;
} else {
return true;
}
}
}
事實(shí)說明在kotlin中 data 修飾符 = java中 private + getter + setter + toString + equals + hashCode
2. 空安全
當(dāng)我們使用Java開發(fā)的時候男摧,如果我們不想遇到NullPointerException蔬墩,我們就需要在每次使用它之前,不停地去判斷它是否為null耗拓。
而Kotlin是空安全的拇颅,我們通過一個安全調(diào)用操作符?
來明確地指定一個對象是否能為空。
我們可以像這樣去寫:
// 這里不能通過編譯. User對象不能是null
var notNullUser: User= null
// User可以是 null
var user: User? = null
// 無法編譯, user可能是null乔询,我們需要進(jìn)行處理
user.print()
// 只要在user != null時才會打印
user?.print()
// 使用Elvis操作符來給定一個在是null的情況下的替代值
val name = user?.name ?: "empty"
/**
如果user為可空類型樟插,又一定要調(diào)用它的成員函數(shù)和變量,可以用!!操作符
兩種可能竿刁,要么正確返回name黄锤,要么拋出空指針異常
當(dāng)user為null,你不想返回null食拜,而是拋出一個空指針異常鸵熟,你就可以使用它。
*/
var name = user!!.name
3. 擴(kuò)展方法
我們可以給任何類添加函數(shù)(View负甸,Context等)流强。比起Java的繼承機(jī)制痹届,更加簡潔和優(yōu)雅。舉個例子打月,我們可以給fragment增加一個顯示toast的函數(shù):
fun Fragment.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(getActivity(), message, duration).show()
}
我們現(xiàn)在可以這么做:
fragment.toast("Hello world!")
此處duration
已經(jīng)賦了默認(rèn)值队腐,所以這個參數(shù)可傳可不傳。
包括擴(kuò)展屬性奏篙,可以直接 類名.屬性名:類型
注意:Kotlin 的方法擴(kuò)展并不是真正修改了對應(yīng)的類文件柴淘,而是在編譯器和 IDE 方面做了處理。使我們看起來像是擴(kuò)展了方法报破。
4. 函數(shù)式支持
- Collections迭代
Kotlin使用lambda表達(dá)式來更方便地解決問題悠就。體現(xiàn)最好的就是Collections的處理方式。
list.map(
println(it) //it表示迭代的對象
)
查看源碼充易,我們可以看到實(shí)際上map
就是一個擴(kuò)展方法梗脾,給所有可以迭代的集合提供該方法,map
方法接收的參數(shù)是一個lambda表達(dá)式盹靴,類型為T炸茧,返回值為R類型(意味著任意類型),那這里T類型實(shí)際上就是list的元素類型稿静。
甚至于可以
list.map(::println)
::
表示方法或類的引用梭冠。為什么可以直接傳方法引用呢?
我們看看println方法源碼改备,可以看到println接收一個Any類也就是任意類型控漠,而且返回值為空(Kotlin中空類型為Unit類,此處源碼省略了返回值類型聲明)悬钳,所以完全符合map方法的要求盐捷。
注:類似于RxJava對數(shù)組的處理,Kotlin也提供了
flatMap
方法默勾,具體可以自己了解碉渡。
- 事件
在Java中,每次我們?nèi)ヂ暶饕粋€點(diǎn)擊事件母剥,都不得不去實(shí)現(xiàn)一個內(nèi)部類滞诺,而在Kotlin中,可以直接聲明我們要做什么环疼。
view.setOnClickListener { toast("Hello world!") }
//注:此處的toast方法是Kotlin默認(rèn)已經(jīng)提供的擴(kuò)展方法
5. 互操作性
Kotlin調(diào)用Java和Java調(diào)用Kotlin與之前的Java 類之間調(diào)用方式?jīng)]有太大差別习霹,不詳細(xì)介紹。
就舉個Java調(diào)用Kotlin的小例子:
//Kotlin
class Overloads {
fun overloaded(a: Int, b: Int = 0, c: Int = 1){
println("$a, $b, $c")
}
}
//Java
public class AccessToOverloads {
public static void main(String... args) {
Overloads overloads = new Overloads();
overloads.overloaded(1, 2, 3);
}
}
可以看到非常簡單炫隶,這里要多介紹一個Kotlin注解@JvmOverloads
淋叶。仍然定義了一個overloaded
方法,加上注解后等限,Kotlin會自動重載成n個方法(n表示參數(shù)個數(shù))
//Kotlin
class Overloads {
@JvmOverloads
fun overloaded(a: Int, b: Int = 0, c: Int = 1){
println("$a, $b, $c")
}
}
/**
在Java可以調(diào)用3個overloaded方法爸吮,分別是:
overloaded(a,b,c)
overloaded(a,b)
overloaded(a)
*/
public class AccessToOverloads {
public static void main(String... args) {
Overloads overloads = new Overloads();
overloads.overloaded(1, 2, 3);
overloads.overloaded(1);
overloads.overloaded(1,3);
}
}
6. 其他
- 單例
首先說說單例的實(shí)現(xiàn)方式,在之后的實(shí)戰(zhàn)中望门,將會經(jīng)常接觸到object
這個關(guān)鍵字形娇。
先看Java,在Java中筹误,實(shí)現(xiàn)一個單例桐早,我們需要:
保留一個單例對象的靜態(tài)實(shí)例
提供一個類方法讓外界訪問唯一的實(shí)例
構(gòu)造方法采用private修飾符
而在Kotlin中,一個修飾符就解決了厨剪。
object PlainOldSingleton {
}
怎么做到的哄酝?我們看看反編譯的結(jié)果:
可以看到寫法和Java是完全一樣的,又有一個新問題祷膳,在類加載的時候就初始化了實(shí)例陶衅,這種方式很糟糕,我們最好選擇懶加載直晨。那么在Kotlin中懶加載的2種實(shí)現(xiàn)方式如下:
class LazyNotThreadSafe {
//方式一
companion object{
val instance by lazy(LazyThreadSafetyMode.NONE) {
LazyNotThreadSafe()
}
//方式二搀军,實(shí)際是Java的直譯
private var instance2: LazyNotThreadSafe? = null
fun get() : LazyNotThreadSafe {
if(instance2 == null){
instance2 = LazyNotThreadSafe()
}
return instance2!!
}
}
}
如果想要實(shí)現(xiàn)線程安全,可以加上@Synchronized
注解勇皇,這和Java中給類加上Synchronized
修飾符是一樣的罩句。同樣@Volatile
注解和Java的Volatile修飾符作用也是一樣的。
或者使用靜態(tài)內(nèi)部類的單例方法:
class LazyThreadSafeStaticInnerObject private constructor(){
companion object{
fun getInstance() = Holder.instance
}
private object Holder{
val instance = LazyThreadSafeStaticInnerObject()
}
}
- 委托
Kotlin中敛摘,委托的實(shí)現(xiàn)依靠于關(guān)鍵字 by
门烂,
by
表示將抽象主題的實(shí)例(by后邊的實(shí)例)保存在代理類實(shí)例的內(nèi)部。
比如下面這個例子中:BaseImpl類繼承于Base接口兄淫,并可以Base接口的所有的 public 方法委托給一個指定的對象屯远。
interface Base {
fun display()
}
class BaseImpl : Base {
override fun display() {
print("baseimpl display")
}
}
class ProxyClass(base: Base) : Base by base
//程序入口
fun main(args: Array<String>) {
var base = BaseImpl()
var proxy = ProxyClass(base)
proxy.display()
}
- 泛型
在Java中,一般使用Gson庫來解析Json拖叙。調(diào)用方法的時候氓润,我們需要傳入想要轉(zhuǎn)成的類的Class。我們都知道Java的泛型實(shí)際上是偽泛型薯鳍,對泛型支持的底層實(shí)現(xiàn)采用的是類型擦除的方式(只有在編譯期才有)咖气。
所以當(dāng)使用Gson.fromJson(String json , Class<T> classOf)
方法時,雖然傳入了類型參數(shù)挖滤,當(dāng)實(shí)際上這個T仍然是個Object崩溪。
而在Kotlin中,可以使用reified斩松,告別Class伶唯。
reified的意思是具體化。作為Kotlin的一個方法泛型關(guān)鍵字惧盹,它代表你可以在方法體內(nèi)訪問泛型指定的JVM類對象乳幸。
inline fun <reified T: Any> Gson.fromJson(json: String): T{
//封裝了`Gson.fromJson(String json , Class<T> classOf)`方法
return fromJson(json, T::class.java)
}
這里需要指定T類型為Any瞪讼,即Object類。
接著可以不需要傳入Class粹断,直接調(diào)用
fun main(args: Array<String>) {
val json = "{state:0,result:'success',name:'test'}"
var result : ReifiedBean = Gson().fromJsonNew(json)
println(result.name+","+result.result)
}
這要?dú)w功于inline符欠,inline 意味著編譯的時候真正要編譯到調(diào)用點(diǎn)。那么哪個方法調(diào)用了它瓶埋,參數(shù)的類型都是確定的希柿。也就不需要傳入Class了
** 7. 擺脫不必要的依賴**
Kotlin替換了許多第三方庫,如ButterKnife养筒、Google Autovalue曾撤、Retrolambda、Lombok和一些RxJava代碼晕粪。
但是也是可以100%兼容RxJava的挤悉,舉個讀取本地文本逐個字打印的例子。
好了兵多,言歸正傳尖啡。
普通的獲取View方法,需要一個個去findViewById
而使用Kotlin后
可能有人注意到了剩膘,還是需要findViewById靶普丁!怠褐!騙子畏梆!說好的優(yōu)雅呢?完全沒覺得更加簡潔澳卫痢5煊俊!別急磷杏,Kotlin常用的獲取控件方式不是這樣的溜畅,容我介紹個Kotlin庫——Anko。
3. Kotlin庫——Anko
簡介
Anko是Kotlin官方開發(fā)的一個讓開發(fā)Android應(yīng)用更快速更簡單的Kotlin庫
1. 再也不用findViewById
做過Android開發(fā)的人都知道极祸,布局文件寫的多了慈格,findViewById也是一個很大的工作量,而且還要先聲明變量遥金,在findViewById然后再強(qiáng)轉(zhuǎn)成我們的控件浴捆,使用方式一般如下
TextView username;
username=(TextView)findViewById(R.id.user);
username.setText("我是一個TextView");
有時候?qū)懙氖遣皇窍胪拢赡苡行┤苏f現(xiàn)在不是有一些注解的庫稿械,如butterknife,當(dāng)我們使用注解時可以不用findViewById了,使用方式如下
@BindView(R.id.user)
TextView username;
username.setText("我是一個TextView");
確實(shí)是這樣选泻,使用注解后確實(shí)給我們少了一些工作量,不過這依然沒有最簡單化,最簡單的就是我們可以直接給id為user的控件直接賦值页眯,或許你會感覺這有點(diǎn)不可思議梯捕。不過Kotlin確實(shí)做到了。我們可以直接這樣寫
user.text="我是一個TextView"
user就是我們布局文件聲明的id窝撵,.text
就相當(dāng)于setText()
科阎,在Kotlin語言中,我們看不到了像Java中的set/get方法了忿族。
當(dāng)我們想這樣使用的時候(不用findViewById,直接使用xml控件id)
我們需要在gradle加入apply plugin: ‘kotlin-android-extensions’
,需要加入下面一句代碼
import kotlinx.android.synthetic.main.activity_login.*
注:activity_login就是我們的布局
import org.jetbrains.anko.toast
import org.jetbrains.anko.onClick
class Main2Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
my_textView.text = "kotline test"
my_textView.textColor = Color.BLUE
my_button.text = "Click"
my_button.onClick { toast("aa") }
}
}
為什么Anko不需要.setText
可以直接.text
呢蝌矛?其實(shí)這是通過擴(kuò)展函數(shù)實(shí)現(xiàn)的道批,我們看下內(nèi)部的實(shí)現(xiàn)細(xì)節(jié):
public var TextView.text: CharSequence
get() = getText()
set(v) = setText(v)
2. Anko Layout
通常我們使用xml文件寫我們的布局,但是存在有一些缺點(diǎn):如不是類型安全入撒,不是空安全隆豹,解析xml文件消耗更多的CPU和電量等等。
而Anko Layout可以使用DSL動態(tài)創(chuàng)建我們的UI茅逮,并且它比我們使用Java動態(tài)創(chuàng)建布局方便很多璃赡。主要是更簡潔,它擁有類似xml創(chuàng)建布局的層級關(guān)系献雅,能讓我們更容易閱讀碉考。
verticalLayout {
val textView = textView("textview")
val name = editText()
val button=button()
button.onClick {
toast("${name.text}")
}
}
我們在OnCreate方法中可以去掉setContentView,然后加入上面代碼就可以顯示如下圖的效果挺身,即一個垂直的線性布局中侯谁,放了一個TextView,一個EditText,和一個Button。并且Button中有一個點(diǎn)擊事件章钾,當(dāng)點(diǎn)擊時將EditText的內(nèi)容以toast顯示墙贱。
在上面創(chuàng)建UI過程中,我們直接把創(chuàng)建UI的代碼寫在onCreate方法中了贱傀,當(dāng)然惨撇,還有一種寫法。我們創(chuàng)建一個內(nèi)部類實(shí)行AnkoComponent接口府寒,并重寫createView方法魁衙,該方法返回一個View,也就是我們創(chuàng)建的布局椰棘。修改如下
inner class UI : AnkoComponent<LoginActivity> {
override fun createView(ui: AnkoContext<LoginActivity>): View {
return with(ui){
verticalLayout {
val textView=textView("我是一個TextView"){
textSize = sp(17).toFloat()//自定義字體大小
textColor=context.resources.getColor(R.color.red)//自定義顏色
}.lparams{
margin=dip(10)//它表示將10dp轉(zhuǎn)換為像素
height= dip(40)
width= matchParent
}
val name = editText("EditText")
button("Button") {
onClick { view ->
toast("Hello, ${name.text}!")
}
}
}
}
}
}
然后在onCreate方法中加一句代碼,即可創(chuàng)建我們的布局頁面了纺棺。如下
UI().setContentView(this@LoginActivity)
其中,dip(10)
邪狞,表示將10dp轉(zhuǎn)換為像素的意思祷蝌,是Anko的擴(kuò)展函數(shù),說到擴(kuò)展函數(shù)帆卓,我發(fā)現(xiàn)Kotlin源碼里大量地使用擴(kuò)展函數(shù)巨朦,這也是Kotlin語言的優(yōu)勢之一米丘。確實(shí)很強(qiáng)大,例如dip擴(kuò)展(摘取View擴(kuò)展)
inline fun View.dip(value: Int): Int = context.dip(value)
fun Context.dip(value: Int): Int = (value * resources.displayMetrics.density).toInt()
就如我們之前說的toast糊啡、text也是拓展函數(shù)一樣
inline fun AnkoContext<*>.toast(message: CharSequence) = ctx.toast(message)
fun Context.toast(message: CharSequence) = Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
但是為了界面和邏輯分離拄查,界面還是建議使用xml,所以這里就不對Anko Layout多做介紹了棚蓄。
3. 其他方面
比如網(wǎng)絡(luò)請求AsyncTask
doAsync {
//后臺執(zhí)行代碼
uiThread {
//UI線程
toast("線程${Thread.currentThread().name}")
}
}
其他內(nèi)容可以直接訪問Anko
Kotlin的缺點(diǎn)
盡管 Kotlin 非常棒堕扶,但是它并不完美。我列舉了一些我不喜歡的部分梭依。
1. 沒有命名空間
Kotlin 允許你在文件中定義頂級的函數(shù)和屬性稍算,但是這會帶來困擾——所有從 Kotlin 引用的頂級聲明無法區(qū)分。這讓我們有時候在讀代碼時很難快速確定用的是哪一個函數(shù)役拴。
例如糊探,你定義這樣一個頂級函數(shù):
fun foo() {...}
你可以通過 foo() 調(diào)用。
如果你在不同的包里面也存在同樣的方法河闰,在調(diào)用時就不能明顯區(qū)分出是調(diào)用的哪個方法科平。你可以通過在前面添加包名的方式去調(diào)用,但是如果 Java 約定的包名很深姜性,似乎不太友好瞪慧。
一種近似的解決方案是使用單例的 object 類。
object FooActions { fun foo() {...}}
這樣你在 Kotlin 中可以通過 FooActions.foo() 調(diào)用部念,但是在 Java 中你必須要這樣 FooActions.INSTANCE.foo()
這樣調(diào)用汞贸,這看起來很麻煩。
你也可以使用 @JvmStatic 去注解該方法印机,從而省掉INSTANCE
矢腻。
其實(shí)沒有命名空間并不是什么大不了的事,但是如果 Kotlin 能夠提供的話射赛,能省不少事多柑。
2. 沒有靜態(tài)修飾符
Kotlin為靜態(tài)函數(shù)和屬性提供了一個和 Java 不一樣的處理方式。并不是說有多爛楣责,只是覺得讓代碼變得不干凈而且沒有必要竣灌。
例如,在 Android 的 View 類中定義的靜態(tài)屬性 View.VISIBLE 和靜態(tài)函數(shù) View.inflate
public class View {
public static final int VISIBLE = 0x00000000;
public static final int INVISIBLE = 0x00000004;
public static View inflate(Context context, int resource) {...}
}
這個定義是簡單的秆麸。然而初嘹,在 Kotlin 代碼中:
class View {
companion object {
@JvmField
val VISIBLE: Int = 0x00000000
@JvmField
val INVISIBLE: Int = 0x00000004
@JvmStatic
fun inflate(context: Context, resource: Int) {...}
}
}
注:companion object為伴生對象
盡管 Kotlin 的版本并沒有那么恐怖,但是它的復(fù)雜程度超過了我對這門語言的預(yù)期沮趣。如果去掉注解屯烦,你在 Java 中就不得不使用這樣可怕的語法去調(diào)用:
// With annotations:
View.VISIBLE;
//Without annotations:
View.Companion.getVISIBLE();
3. 編譯方法數(shù)量
Kotlin 肯定會減少項(xiàng)目中的代碼行數(shù),但是它也會提高代碼在編譯以后的方法數(shù)。主要原因就是 Kotlin 屬性的實(shí)現(xiàn)方式驻龟。
和 Java 不一樣温眉,Kotlin 沒有提供單獨(dú)定義域的方式。你必須使用 val 或者 var 來聲明變量翁狐。這樣有一個好處类溢,就是省去了像 Java 一樣定義 getters 和 setters 方法。
但是這需要一定的成本露懒。每一個public的 val 變量都會生成一個「支持域」和一個能被 Java 調(diào)用的 getter 方法闯冷。每一個public的 var 變量都會生成 getter 和 setter 方法。
// kt 文件:
// 默認(rèn)就是public懈词,無需額外添加public修飾符
val strValPublic: String = "strValPublic"
var strVarPublic: String = "strVarPublic"
// 以下是反編譯結(jié)果:
public final class VarAndValKt {
@NotNull
private static final String strValPublic = "strValPublic";
@NotNull
private static String strVarPublic = "strVarPublic";
@NotNull
public static final String getStrValPublic() {
return strValPublic;
}
@NotNull
public static final String getStrVarPublic() {
return strVarPublic;
}
public static final void setStrVarPublic(@NotNull String var0) {
Intrinsics.checkParameterIsNotNull(var0, "<set-?>");
strVarPublic = var0;
}
}
拓展:Intrinsics.checkParameterIsNotNull 方法其實(shí)很簡單窃躲,原理:
public static void checkParameterIsNotNull(Object value, String paramName) {
if (value == null) {
throwParameterIsNullException(paramName);
}
}
其實(shí)所有空安全的秘密都在這個類里面了
慶幸的是,私有屬性的 getters 和 setters 會生成域而不是生成方法钦睡。
// kt文件:
private val strValPrivate: String = "strValPrivate"
private var strVarPrivate: String = "strVarPrivate"
// 以下是反編譯結(jié)果:
public final class VarAndValKt {
private static final String strValPrivate = "strValPrivate";
private static String strVarPrivate = "strVarPrivate";
}
所以如果你把項(xiàng)目中Java代碼轉(zhuǎn)成Kotlin,而且之前的 Java 代碼中定義了大量的公開域(這在定義常量的時候很常見)躁倒,你會驚奇的發(fā)現(xiàn)最終編譯生成的方法數(shù)量大幅上升荞怒。
如果你的 Android 應(yīng)用快接近方法數(shù)限制了,我建議你為不需要自定義 getter 方法的常量加上 @JvmField 注解秧秉。這樣會阻止 getters 方法的生成褐桌,從而減少你的方法數(shù)。
// kt 文件:
@JvmField
val strValPublic: String = "strValPublic"
@JvmField
var strVarPublic: String = "strVarPublic"
// 以下是反編譯結(jié)果:
// 注意看象迎,get set方法消失荧嵌,取而代之的是private修飾符變成了public
public final class VarAndValKt {
@JvmField
@NotNull
public static final String strValPublic = "strValPublic";
@JvmField
@NotNull
public static String strVarPublic = "strVarPublic";
}
4. 沒有CE機(jī)制
Kotlin官網(wǎng)對CE的解釋:
翻譯一下:
Kotlin 沒有受檢的異常。這其中有很多原因砾淌,但我們會提供一個簡單的例子啦撮。
以下是 JDK 中 StringBuilder 類實(shí)現(xiàn)的一個示例接口
Appendable append(CharSequence csq) throws IOException;
這個簽名是什么意思? 它是說汪厨,每次我追加一個字符串到一些東西(一個 StringBuilder赃春、某種日志、一個控制臺等)上時我就必須捕獲那些 IOException劫乱。 為什么织中?因?yàn)樗赡苷趫?zhí)行 IO 操作(Writer 也實(shí)現(xiàn)了 Appendable)…… 所以它導(dǎo)致這種代碼隨處可見的出現(xiàn)
我們看到Java的CE機(jī)制被詬病了很久,但是如果你經(jīng)過理性的分析衷戈,就會發(fā)現(xiàn)狭吼,Java 的有些設(shè)計看起來“繁復(fù)多余”,實(shí)際上卻是經(jīng)過深思熟慮的決定殖妇。Java 的設(shè)計者知道有些地方可以省略刁笙,卻故意把它做成多余的。我們不能盲目地以為簡短就是好,多寫幾個字就是丑陋不優(yōu)雅采盒,其實(shí)不是那樣的旧乞。
Kotlin有異常機(jī)制抛虏,但不要求你在函數(shù)的類型里面聲明可能出現(xiàn)的異常類型材蛛,也不使用靜態(tài)類型系統(tǒng)對異常的處理進(jìn)行檢查和驗(yàn)證凝化。那當(dāng)我每調(diào)用一個函數(shù)(不管是標(biāo)準(zhǔn)庫函數(shù)归榕,第三方庫函數(shù)惧笛,還是隊(duì)友寫的函數(shù)榛泛,甚至我自己寫的函數(shù))综液,我都會疑惑這個函數(shù)是否會拋出異常薛窥。由于函數(shù)類型上不需要標(biāo)記它可能拋出的異常叉橱,為了確保一個函數(shù)不會拋出異常挫以,你就需要檢查這個函數(shù)的源代碼,以及它調(diào)用的那些函數(shù)的源代碼窃祝,甚至整個調(diào)用樹掐松!
在這種疑慮的情況下,你就不得不做最壞的打算粪小,你就得把代碼寫成:
try
{
foo()
}
catch (e:Exception)
{
printf(e)
}
因?yàn)椴恢?foo 函數(shù)里面會有什么異常出現(xiàn)大磺,所以你的 catch 語句里面也不知道該做什么。大部分人只能在里面放一條 log探膊,記錄異常的發(fā)生杠愧。這是一種非常糟糕的寫法,不但繁復(fù)逞壁,而且可能掩蓋運(yùn)行時錯誤流济。
那么 Java 呢?因?yàn)?Java 有 CE腌闯,所以當(dāng)你看到一個函數(shù)沒有聲明異常绳瘟,就可以放心的省掉 try-catch。所以這個問題姿骏,自然而然就被避免了稽荧,你不需要在很多地方疑惑是否需要寫 try-catch。Java 編譯器的靜態(tài)類型檢查會告訴你工腋,在什么地方必須寫 try-catch姨丈,或者加上 throws 聲明。
結(jié)尾
在學(xué)習(xí)過程中擅腰,我發(fā)現(xiàn)蟋恬,如果有著扎實(shí)的Java基礎(chǔ),這東西掌握起來是很快的趁冈,所以到底學(xué)不學(xué)Kotlin歼争,其實(shí)是不用著急的拜马。一個新的語言想要快速的普及,那么可能只有在運(yùn)行效率上有所提升沐绒,才是最大的優(yōu)勢俩莽,而Kotlin并不具備這樣的屬性。
我們可以看下Java和Kotlin的編譯速度對比乔遮。
編譯速度對比
我不會試圖比較一行代碼的編譯速度扮超;相反,比較的是將代碼從Java轉(zhuǎn)換為Kotlin是否會影響其總體構(gòu)建的時間蹋肮。
在轉(zhuǎn)換之前出刷,App Lock的Java代碼有5,491個方法和12,371行代碼。 改寫后坯辩,這些數(shù)字下降到4,987方法和8,564行Kotlin代碼馁龟。 在重寫期間沒有發(fā)生大的架構(gòu)更改,因此在重寫之前和之后測試編譯時間應(yīng)該很好地了解Java和Kotlin之間的構(gòu)建時間的差異漆魔。我寫了一個shell來重復(fù)執(zhí)行g(shù)radle坷檩。所有測試連續(xù)進(jìn)行10次。
-
clean + 不用Gradle daemon Build
這是兩種語言中構(gòu)建時間最差的情況:從冷啟動運(yùn)行一個clean的構(gòu)建改抡。 對于這個測試矢炼,我禁用了Gradle daemon。
這里是十個構(gòu)建所花費(fèi)的時間:
對于沒有Gradle daemon 并且clean構(gòu)建雀摘,Java編譯比Kotlin快17%,但是大部分人不會這么編譯他們的代碼八拱。
- clean +Gradle daemon Build
可以看到阵赠,Kotlin第一次運(yùn)行所花費(fèi)的時間與上一個方案的時間相同,但后續(xù)運(yùn)行的性能逐步提高肌稻。
對于clean + Gralde daemon 編譯清蚀,Java編譯比Kotlin快13%。
所以Kotlin編譯在完整代碼情況下比Java慢一點(diǎn)爹谭。 但是你通常只會對幾個文件進(jìn)行更改后編譯枷邪,所以,我們來看看Kotlin在增量編譯是否可以趕上Java诺凡。
- 增量編譯
所以雖然Java在clean構(gòu)建比Kotlin 快10-15%东揣,但這些情況很少。 對于大多數(shù)開發(fā)人員來說腹泌,更常見的情況是部分構(gòu)建嘶卧,隨著Gradle daemon運(yùn)行和增量編譯的開啟,Kotlin編譯速度快或略快于Java凉袱。
所以芥吟,還是那句話侦铜,一個新的語言想要快速的普及,在運(yùn)行效率上有所提升钟鸵,才是最大的優(yōu)勢钉稍,Kotlin肯定值得學(xué)習(xí)的,但并沒有傳的那么夸張棺耍。有精力就去學(xué)習(xí)贡未,有自己的學(xué)習(xí)計劃也可以放一放,延后再學(xué)烈掠。
我想只有用得多了羞秤,Kotlin的優(yōu)勢才會慢慢展現(xiàn)出來,這需要一個較為漫長的過渡期左敌。
轉(zhuǎn)載請注明 原文出處:http://www.reibang.com/p/f364e3f9cc36
有錯誤請多多指正瘾蛋!