原文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,走起:
右键点击一个java文件,会出现以下Menu:
选择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更优秀的编程语言,而且更接近现代的编程语言,将是未来的趋势。至于是否在项目中采用,相信各位看官心里自有考量。