將Kotlin引入Android項目

原文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鹰溜,走起:

image

右鍵點擊一個java文件,會出現(xiàn)以下Menu:

image

選擇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)代的編程語言妈橄,將是未來的趨勢。至于是否在項目中采用翁脆,相信各位看官心里自有考量眷细。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鹃祖,隨后出現(xiàn)的幾起案子溪椎,更是在濱河造成了極大的恐慌,老刑警劉巖恬口,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件校读,死亡現(xiàn)場離奇詭異,居然都是意外死亡祖能,警方通過查閱死者的電腦和手機歉秫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來养铸,“玉大人雁芙,你說我怎么就攤上這事〕” “怎么了兔甘?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鳞滨。 經(jīng)常有香客問我洞焙,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任澡匪,我火速辦了婚禮熔任,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唁情。我一直安慰自己疑苔,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布甸鸟。 她就那樣靜靜地躺著夯巷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哀墓。 梳的紋絲不亂的頭發(fā)上趁餐,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音篮绰,去河邊找鬼后雷。 笑死,一個胖子當(dāng)著我的面吹牛吠各,可吹牛的內(nèi)容都是我干的臀突。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼贾漏,長吁一口氣:“原來是場噩夢啊……” “哼候学!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纵散,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤梳码,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后伍掀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掰茶,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年蜜笤,在試婚紗的時候發(fā)現(xiàn)自己被綠了濒蒋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡把兔,死狀恐怖沪伙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情县好,我是刑警寧澤围橡,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站聘惦,受9級特大地震影響某饰,放射性物質(zhì)發(fā)生泄漏儒恋。R本人自食惡果不足惜善绎,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一黔漂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧禀酱,春花似錦炬守、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至曹洽,卻和暖如春鳍置,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背送淆。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工税产, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人偷崩。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓辟拷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親阐斜。 傳聞我的和親對象是個殘疾皇子衫冻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345