将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,走起:

将Kotlin引入Android项目_第1张图片
image

右键点击一个java文件,会出现以下Menu:

将Kotlin引入Android项目_第2张图片
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:

                                                                                            

它的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() {                    ...                });

改动也不算大。

强大的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 adList;    }}

kotlin:

data class BannerItemData(val data: Data) : CommonJson()data class Data(val adList: List)

这里有两个地方要注意,继承一个类的时候默认调用他的无参构造方法,如果没有要加上;如果需要嵌套类,可以直接在下面写一个,如例子中的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 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 profiles = loadProfiles(context);Collections.sort(profiles, new Comparator() {    @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更优秀的编程语言,而且更接近现代的编程语言,将是未来的趋势。至于是否在项目中采用,相信各位看官心里自有考量。

你可能感兴趣的:(将Kotlin引入Android项目)