将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阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270