將Kotlin引入Android項目

原文2019年2月21日發表于微信公眾號 [Stephen的技術博客]

將Kotlin引入Android項目

自從2017年Kotlin被Google確定為Android官方開發語言,已經有越來越多的小伙伴將Kotlin引入到了項目中,而且Kotlin本身的坑也被越填越平,想了一下,是時候嘗試把Kotlin引入到我們的項目里了。這篇文章就來對比一下引入Kotlin之后到底開發效率獲得了怎樣的提升。

目標

首先定個小目標,我們不是要把項目里面的存量Java代碼全部轉為Kotlin,而是令Kotlin與Java共存,Kotlin可以調用原有的Java代碼,Java代碼也可以調用新引入的Kotlin,用Kotlin開發新功能,充分利用它的新特性。

詳細對比

那么接下來就開始。要引入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轉成Kotlin,走起:

image

右鍵點擊一個java文件,會出現以下Menu:

image

選擇Convert Java File to Kotlin File,java類瞬間轉成了kotlin,是不是很強大_
對比一下原先java的CGMoreActivity代碼行數是242,轉成kotlin后縮減到了171!下面具體來看kotlin究竟做了哪些改變:

不用再寫findViewById了

大家是不是對findViewById已經深惡痛絕了呢?認為大可不必寫這樣的代碼?那么引入kotlin之后,你的夢想就實現了。在Convert之后,IDE不會自動把findViewById的代碼刪除掉,但是你可以手動把這些代碼刪除,然后用xml上view的id直接調用這個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代碼的時候,總會擔心這個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關鍵字,不用寫匿名內部類了

相信大家在上面也看到了,以前累贅的new View.OnClickListener()的代碼不復存在,在kotlin里面,如果要獲得一個類的實例,直接調用ClassName()。

靜態變量轉變成了伴生對象

接著再把我們封裝的Retrofit工廠類做個convert。由于kotlin里沒有靜態變量的概念,原先的靜態變量,靜態方法統統轉成伴生對象。
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轉換器                            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//添加rxjava轉換器                            .client(getOkHttpClient(false))//構建對應的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轉換器                                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//添加rxjava轉換器                                .client(getOkHttpClient(false))//構建對應的OkHttpClient                                .build()                        apiService = retrofit.create(ApiService::class.java)                    }                }            }            return apiService        }     }     ...}

在一個對象里面定義這些靜態變量和方法,調用的時候要這樣寫:

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)

沒錯!就是這樣一行代碼搞定,編譯器為我們自動實現了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>)

這里有兩個地方要注意,繼承一個類的時候默認調用他的無參構造方法,如果沒有要加上;如果需要嵌套類,可以直接在下面寫一個,如例子中的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拼接字符串時必須將""字符串與變量用+連接起來,相當繁瑣,在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語句進行分支判斷,即使可以合并一些結果相同的case項,但是代碼仍然冗長;在kotlin則可以巧妙的使用,和in將同類項輕松合并,代碼簡潔度迅速提高。

簡便的map遍歷

java:

for (Map.Entry<String, String> entry: map.entrySet()) { }

kotlin:

for ((key, value) in map) { }

Map.Entry作為java中遍歷map的迭代器,令代碼的復雜度大大提高,而kotlin中根本不需要用這種復雜的方法。

簡便的字符串拆分

java:

String[] splits = "param=car".split("=");String param = splits[0];String value = splits[1];

kotlin:

val (param, value) = "param=car".split("=")

再不需要定義String數組,從數組中取出拆開的字符串,kotlin的split函數可以將值賦給對應的變量。

對象拷貝

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還自動實現了clone方法,但當然在kotlin這邊叫copy方法,而且如果只是想修改其中的部分屬性值,也是相當輕松的。

標簽

//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循環return處用@lit就回到了標簽定義處,繼續forEach的下一個循環;又允許直接用@forEach跳到.forEach處,繼續下一個循環。2是對1寫法的簡化,兩者輸出結果相同。

實現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的實現,輕松使用lambda語法以及標簽,代碼顯得簡潔優雅。

說了kotlin與java對比的這么多優點,主要代碼簡潔程度的極大提高,大家是不是躍躍欲試了呢?不過凡事都有兩面,我在這里再給大家總結一下到目前為止我發現的使用kotlin的缺點。

kotlin的缺點

代碼可讀性降低

毫無疑問,java雖然語法繁瑣,但是由于他本身的強類型、面向對象等屬性,在繁瑣的同時語法也比較單一,代碼寫出來如行云流水,可讀性高,這也是他擁有眾多程序員的原因之一。反觀kotlin,代碼雖然簡潔了,但是就像js那樣,可讀性是比較低的。

需要投入學習成本

如果原先只是一個單純使用java做Android開發的程序員,沒有學習過js,python等腳本語言,又或者即使學過,但仍然需要花一些時間熟習kotlin這門語言,才能輕松使用。而且如果在一個正在開發中的項目里面引入kotlin,通常都不會是把java全部替換成kotlin,而是混用,這樣又會有同時使用兩種語言的情況,需要在兩種語法之間切換。

小部分代碼更繁瑣了

java:

String appUrl = mVersionUpdate.appUrl.trim();

kotlin:

val appUrl = mVersionUpdate!!.appUrl.trim { it <= ' ' }

調用字符串的trim方法,在java里面trim()就完了,而kotlin還要寫一段{it <= ' '}。


總括來講,瑕不掩瑜,kotlin還是一種比java更優秀的編程語言,而且更接近現代的編程語言,將是未來的趨勢。至于是否在項目中采用,相信各位看官心里自有考量。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,947評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,268評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,817評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,039評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,551評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,502評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,662評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,907評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,255評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,637評論 2 370