做者介绍:刘高轩,美团点评Android工程师,2年Android开发经验,如今是美团点评点餐团队的一员。java
本文主要介绍了新晋Android官方开发语言Kotlin的语法基础和实用特性,并加以简单的快速实践,给出了Kotlin相比Java的开发效率优点,很是适合对Kotlin感兴趣的Android开发人员进行阅读。
本文较长(6000字左右),建议阅读时间: 30min+android
今年的Google I/O 2017,Kotlin正式成为Android的官方开发语言。这门被称为“The Swift of Android”的语言(具体对比可参考Swift is like Kotlin),自Jetbrains推出至今已有六年时间,而首个官方稳定版本倒是去年2月才发布的。也正是从去年开始,Kotlin的使用度迎来了爆炸式的增加,这里有一张官方的GIF图能够看出其2016年的发展速度。git
Kotlin好处都有啥?谁说对了都给他。做为一个旨在取代Android开发人员基数众多的Java语言的新兴语言,彻底与Java老大哥割裂开,那往后的推广确定是寸步难行。Kotlin巧妙地做为一个静态JVM语言诞生,与Java彻底兼容,又比Java要更加简洁,同时也处理了让无数开发者头疼的NullPointerException问题,显得颇有吸引力。对于一个有经验的Android开发者来讲,学习Kotlin的曲线至关平缓,能够几乎零成本去改造已有Android项目的部分代码。面对这充满了新特性的语言,谁会不动心呢?github
Kotlin语言主要有如下几个特色:编程
java中存在int,float,boolean等基础类型,这些基础类型在Kotlin里将所有以对象的形式继续存在。随之而来使用上也发生一些变化,有几点须要注意下:swift
// Int没法自动转换为Double, 须要本身先作类型转换(as Double, toDouble(), 方式不少)
var a: Int = 2
var b: Double = a.toDouble()
// Char不能直接等值于其对应的ASCII码值,须要类型转换
var c: Char = 'a'
var ca: Int = c.toInt()复制代码
Kotlin中使用var定义变量(和js很像),使用val定义常量(至关于java当中的final)。定义变量时既能够指定类型,也能够不指定,Kotlin支持类型推断。设计模式
var name = "me" // 类型为String
var name: String = "me" // 类型为String
val TAG: String = "KotClassA"复制代码
须要注意的是,你不能直接写一个var num丢在代码里不初始化值,编译器会没法推断它的类型android-studio
var name // 编译器报错
var name: String // 类型为String复制代码
与Java中以“;”符号区分每行的结尾不一样,Kotlin中再也不须要在每行代码的结束位置写“;”。固然若是你一时改不了写Java时的习惯,IDE也并不会报错,只会友好地把“;”置灰并下划线,提示你这是多余之举。安全
Java语言中,“:”符号主要出现于三元运算符“A ? B : C”中,for循环时遍历列表项和switch的每种情形分支。而在Kotlin语言中,“:”被普遍用于变量类型的定义。dom
// 定义变量类型
fun common() {
var ka: KClassA
var kb: KClassB
}
// 定义函数的参数和返回值
fun helloStr(str: String): String {
var strHello: String = "hello"
strHello += str
return strHello
}复制代码
“:”还被用于声明类继承或接口实现。
interface KInterfaceA {
...
}
open class KClassA(n: Int): KInterfaceA {
...
}
class KClassB(n: Int): KClassA(n) {
...
}复制代码
此外,若是你想在Kotlin代码中使用Java类,也须要用到“:”符号。连续两个“:”代表对Java类的调用。
val intent = Intent(this, MainActivity::class.java)复制代码
Java语言中使用instanceof来判断某变量是否为某类型,而Kotlin中使用更短小更直观的is来进行类型检测。
if (num instanceof Double) { ... } // Java代码
if (num is Double) { ... } // Kotlin代码复制代码
Java中使用字符串模板会比较麻烦,并且不太直观。而Kotlin里使用则异常便捷好用。
// Java中字符串模板两种经常使用形式
String name = "我";
int age = 25;
String.format("%s今年%d岁", name, age);
MessageFormat.format("{0}今年{1}岁", name, age);
// Kotlin中更直观的字符串模板形式
var name = "我"
var age = 25
"${name}今年${age}岁"复制代码
Kotlin中的函数经过关键字fun定义的,具体的参数和返回值定义结构以下。
fun test(para1: Int, para2: String): String { ... }复制代码
Kotlin中的函数能够是全局函数,成员函数或者局部函数,甚至还能够做为某个对象的扩展函数临时添加,这个做为Kotlin的一大实用特性,下文会有具体讲解。
Kotlin函数参数还有默认值和可变参数的特性,分别来看一下:
对Kotlin函数中的某个参数能够用“=”号指定其默认值,调用函数方法时可不不传这个参数,但其余参数须要用“=”号指定。下文例子中没有传递参数para2,其实际值为默认值"para2"
fun test(para1: Int, para2: String = "para2", para3: String): String { ... }
test(22, para3 = "hello")复制代码
可变参数值的话,须要用关键字vararg来定义。这里须要注意的是,一个函数仅能有一个可变参数。该可变参数不必定得是最后一个参数,但当这种状况下时,调用该方法,须要给其余未指明具体值的参数传值。
fun test(vararg para1: String, para2: String): String { ... }
test("para1", "para4", "para5", para2 = "hello")复制代码
Kotlin中也使用class关键字定义类,全部类都继承于Any类,相似于Java中Object类的概念。类实例化的形式也与Java同样,可是去掉了new关键字。
类的构造函数分为primary constructor和secondary constructor,前者只能有一个,然后者能够有多个。若是二者都未指定,则默认为无参数的primary constructor。
primary constructor是属于类头的一部分,用constructor关键字定义,无可见性字段定义时可省略。初始化代码须要单独写在init代码块中,方法参数只能在init代码块和变量初始化时使用。
secondary constructor也是用constructor关键字定义,必需要直接或间接代理primary constructor。
class Student(name: String) { // primary constructor
var mName: String = name
init {
println("Student is called " + name)
}
constructor(age: Int, name: String):this(name) {
println("Student is ${age} years old")
}
}复制代码
类继承使用符号“:”表示,接口实现也同样,不作本来Java中的extends和implement关键字区分。Kotlin有一点与Java大为不一样,即Java中类默承认被继承,只有被final关键字修饰的类才不能被继承。而Kotlin中直接取消了final关键字,全部类均默认不可被继承。神马?这还怎么面向对象编程?先别急,Kotlin中新增了open关键字,仅有被open修饰的类才能够被继承。
平常开发中写一个单例类是很常见的行为,Kotlin中直接将这种设计模式提高到语言级别,使用关键词object定义单例类。这里须要注意,是全小写。Kotlin中区分大小写,Java中本来指全部类的父类Object已弃用。单例类访问直接使用类名,无构造函数。
object Shop(name: String) {
fun buySomething() {
println("Bought it")
}
}
Shop.buysomething()复制代码
Java中使用static标识一个类里的静态属性或方法,能够被这个类的因此实现使用。Kotlin改成使用伴随对象,用companion修饰单例类object,来实现静态属性或方法功能。
class Mall(name: String) {
companion object Shop {
val SHOP_NAME: String = "McDonald" // 等同于Java中写public static String
fun buySomething() { // 等同于Java中写public static void
println("Bought it")
}
}
}
Mall.buySomething()复制代码
Kotlin中的if-else语句与Java一致,结构上都是if (条件A) { 条件A为真要执行的操做 } else { 条件A为假要执行的操做 }
这里主要要介绍的是Kotlin的一个特色,即if-else语句能够做为一个逻辑表达式使用。不只如此,逻辑表达式还能够以代码块的形式出现,代码块最后的表达式做为该块的值。
// 逻辑表达式的使用
fun maxNum(x: Int, y: Int): Int {
var max = if (x > y) x else y
return max
}
// 代码块形式的逻辑表达式
fun maxNumPlus(x: Int, y: Int): Int {
var max = if (x > y) {
println("Max number is x")
x
} else {
println("Max number is y")
y
}
return max
}复制代码
Kotlin中的when语句取代了Java中的switch-case语句,功能上要强大许多,能够有多种形式的条件表达。与if-else同样,Kotlin中的when也能够做为逻辑表达式使用。
// 逻辑表达式的使用
fun judge(obj: Any) {
when (obj) {
1 -> println("是数字1")
-1, 0 -> println("是数字0或-1")
in 1..10 -> println("是不大于10的正整数")
"abc" -> println("是字符串abc")
is Double -> println("类型是双精度浮点数")
else -> println("其余操做")
}
}复制代码
Kotlin中能够对任意表达式进行标签标记,形式为abc@,xyz@。而这些标签,能够搭配return、break、continue等跳转行为来使用。
fun labelTest() {
la@ for (i in 1..10) {
println("outer index " + i)
for (j in 1..10) {
println("inner index " + j )
if ( inner % 2 == 0) {
break@la
}
}
}
}复制代码
for语句、while语句、continue语句和break语句等逻辑都与Java基本一致,这里再也不赘述。
在写Java代码时,最常出如今线上的crash问题大概就是NullPointerException了。技术上来讲这样的问题修复起来很快,没什么难度,但每每因为平常开发中没有写足够多的防护性代码,致使此类问题一直困扰着咱们。Java做为一个古老的语言并无空指针安全功能,这使得当对象层级套用的时候,想要获取最里面的某个属性,须要从外到内依次作一遍非空判断来避免NPE。类似的场景太多,致使这在代码成本上是很大的。
Kotlin中,当咱们定义一个变量时,其默认就是非空类型。若是你直接尝试给他赋值为null,编译器会直接报错。Kotlin中将符号“?”定义为安全调用操做符。变量类型后面跟?号定义,代表这是一个可空类型。一样的,在调用子属性和方法时,也能够用字符?进行安全调用。Kotlin的编译器会在写代码时就检查非空状况,所以下文例子中,当s2有前置条件判断为非空后,即使其自己是可空类型,也能够安全调用子属性或方法。对于ifelse结构的逻辑,Kotlin还提供了“?:”操做符,极大了简化了代码量又不失可读性。Kotlin还提供“!!”双感叹号操做符来强制调用对象的属性和方法,无视其是否非空。这是一个挺危险的操做符,除非有特殊需求,不然为了远离NPE,仍是少用为妙。
var s1: String = "abc"
s1 = null // 这里编译器会报错
var s2: String? = "abc"
s2 = null // 编译器不会报错
var l1 = s1.length // 可正常编译
var l2 = s2.length // 没有作非空判断,编译器检查报错
if (s2 != null) s2.length // Java式的判空方案
s2?.length // Kotlin的安全调用操做符?。当s2为null时,s2?.length也为null
if (s2 != null) s2.length else -1 // Java式判空的ifelse逻辑
s2?.length ?: -1 // Kotlin的elvis操做符
s2!!.length // 可能会致使NPE复制代码
相信做为一个Java/Android开发者,你们都写过不少Base类,继承原生父类的同时,封装一些通用方法,供子类使用。亦或是把这类通用方法,专门放置到一个XXUtils类里,做为工具类出现。这样作是为了代码结构的清晰,但也是一种无奈。因为没法修改原生类的内容,咱们只能借助继承或者以面向方法的思惟来写工具类。这点在Kotlin里获得了完美解决。Kotlin支持在包范围内对已存在的类进行方法和属性扩展。
咱们以Android最经常使用的showToast方法举个扩展方法的例子,以lastIndex属性举个扩展属性的例子,以下。在这样强大的特性下,写代码都成了一种享受。
// 扩展方法
fun Context.showLongToast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
}
// 扩展属性
val <T> ArrayList<T>.lastIndex: Int get() = size -1
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
showLongToast("hello") // 给Context类扩展了showToast方法,能够在任何有context的地方直接调用了
var mList = ArrayList<String>()
mList.lastIndex // 任何ArrayList都可以调用该属性
...
}
...
}复制代码
这里须要注意两点:1.扩展须要在包级范围内进行,若是写在class内是无效的。2.已经存在的方法或属性,是没法被扩展的,依旧会调用已有的方法。
相信你们都写过数据类,或者自动化生成的数据model,一般都是由多个属性和对应的getter、setter组成。当有大量多属性的数据类时,不只这些类会由于大量的getter和setter方法而行数爆炸,也使整个工程方法数骤增。Kotlin中也作了这层特性优化,提供了数据类的简单实现。不用再写get和set方法,这些都由编译器背后去作,你获得的是一个清爽干净的数据类。具体使用参考下面的例子。
data class Student (
var name: String,
var age: Int,
var hobby: String?,
var university: String = "NJU"
)
fun printInfo() {
var s: Student = Student("Ricky", 25, "playing Gwent")
println("${s.name} is from ${s.university}, ${s.age} years old, and likes ${s.hobby}")
}复制代码
Anko是Jetbrains官方提供的一个让Kotlin开发更快速简单的类库,旨在使代码书写更加清晰易懂,形式上为DSL编程。
Android开发过程必定都写过大量的findViewById。这自己就是个消耗资源的方法,编码时还极为不便,须要强制转换为具体的View类型才能调用其方法。经过引入支持注解的库,可使这个过程略微简单化一些。
// 传统Android中的View内容初始化
TextView tvName = (TextView) this.findViewById(R.id.tv_name);
tvName.setText("Ricky");
// 注解方式
@BindView(R.id.tv_name)
TextView tvName;
tvName.setText("Ricky");复制代码
这样仍是显得不够简洁,而Kotlin给出了一种最为简便的方式。
import kotlinx.android.synthetic.main.activity_main.* // activity_main为具体的布局xml文件名
...
tvName.text = "Ricky";复制代码
你只须要在具体的页面中按照上面的格式import下,就能够在整个页面里很方便的使用xml里的view操做了。不须要类型转换,不须要新建变量,不须要findViewById,孰优孰劣,相信你们心中都有了答案。
一般,在Android里,当我想打开一个新页面,并给它传递一些参数时,我须要按照以下的方式编码。
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.putExtra("userid", 10001);
intent.putExtra("username", "ricky");
startActivity(intent);复制代码
而强大的Anko给咱们提供了一种极为方便的写法。不管是无参仍是有参,仍是须要RequestCode,都很是简洁易懂,是否是一看就特别心动?
startActivity<MainActivity>()
startActivity<MainActivity>("userid" to 10001, "username" to "ricky")
startActivityForResult<MainActivity>(101, "userid" to 10001, "username" to "ricky")复制代码
上面说到再也不使用findViewById,仍是基于使用xml来编写Android页面这个基础。假若你想完全革新写法,换一种更直观的DSL方式来写Android页面呢?Anko就提供了这样的方案。相比之下,除了可读性增长以外,也节约了解析xml文件消耗的资源。
inner class LoginAnkoUI : AnkoComponent<LoginActivity> {
override fun createView(loginAnkoUI: AnkoContext<LoginActivity>): View {
return with(loginAnkoUI) {
verticalLayout {
val textView = textView("用户名") {
textSize = sp(15).toFloat()
textColor = context.resources.getColor(R.color.black)
}.lparams {
margin = dip(10)
height = dip(40)
width username= matchParent
}
val username = editText("输入...")
button("登陆") {
onClick { view ->
toast("Hello, ${username.text}!")
}
}
}
}
}
}复制代码
只要最后在Activity里加一句调用,即可以使用Anko写的页面了。是否是看上去更直观了呢?
LoginAnkoView().setContentView(this@LoginActivity)复制代码
做为官方开发语言,在Android Studio 3.0版本中,已经内嵌了对Kotlin的支持。和笔者同样还在使用2.x版本开发的小伙伴们,也不用担忧须要花时间升级AS才能体验到Kotlin。JetBrains提供了完善的插件支持,直接打开Preferences去配置插件,找到Kotlin下载安装下就好啦。
插件安装完毕,还须要build.gradle里添加下依赖。
若是你不清楚当前最新的kotlin版本是什么的话,这里也有一个更简便的方法来给项目添加kotlin依赖。
首先你的项目中要有一份java代码,而后在插件安装正常的状况下,你能够在Code菜单看到一键把Java代码转换为Kotlin的功能。
转换完毕后,当你尝试修改这个.kt代码文件时,Android Studio便会提醒你Kotlin还没有配置。
这个自动化配置过程,能够看到目前插件支持到的最新的Kotlin版本。换言之插件及时最新的话,你这里就能够选择到最新的Kotlin版本。这里也能够选择只将某个模块配置为支持Kotlin,或者全局支持。
配置完毕后,咱们能够看到build.gradle文件里已经自动添加了依赖代码,Sync一下就ok了。
因为要充分发挥Kotlin的特性,将Anko的相关代码依赖也引入进来
下面会给你们展现一个小Demo的核心代码,完整代码能够查看KotlinDemo。Demo将分别用Java写法和Kotlin写法编写一样的页面,以此对比出优劣。
Demo包含一个Java实现的公共首页,首页中有两个按钮入口,分别跳转到用Java编写的列表页和用Kotlin编写的列表页。各自的列表页都会实现一个Item点击跳转到详情页的功能,并在详情页显示具体信息。
Java实现效果:Kotlin实现效果:
// Java实现
public class JListActivity extends AppCompatActivity implements View.OnClickListener, AdapterView.OnItemClickListener {
// View
private ListView listView;
private Button btnClearData;
private Button btnUpdateData;
// Data
private ArrayList<JStudent> studentList = new ArrayList<>();
private JListAdapter listAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_java);
initView();
initData();
}
private void initData() {
listAdapter = new JListAdapter(studentList);
listView.setAdapter(listAdapter);
}
private void initView() {
listView = (ListView) findViewById(R.id.java_list_view);
btnClearData = (Button) findViewById(R.id.java_clear_btn);
btnUpdateData = (Button) findViewById(R.id.java_update_btn);
listView.setDividerHeight(0);
listView.setOnItemClickListener(this);
btnClearData.setOnClickListener(this);
btnUpdateData.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (view == btnClearData) {
studentList.clear();
listAdapter.notifyDataSetChanged();
showShortToast("已清空");
} else if (view == btnUpdateData) {
studentList.addAll(FakeServer.randomJavaResponse());
listAdapter.notifyDataSetChanged();
showShortToast("已更新10条数据");
}
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Intent intent = new Intent(JListActivity.this, JInfoActivity.class);
intent.putExtra("stu_name", studentList.get(i).getName());
intent.putExtra("stu_age", studentList.get(i).getAge());
intent.putExtra("stu_hobby", studentList.get(i).getHobby());
intent.putExtra("stu_university", studentList.get(i).getUniversity());
startActivity(intent);
}
private void showShortToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}复制代码
// Kotlin实现
class KListActivity : AppCompatActivity() {
var studentList: ArrayList<KStudent> = ArrayList()
var listAdapter: KListAdapter = KListAdapter(studentList)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ListUI().setContentView(this@KListActivity)
}
fun clearList() {
studentList.clear()
listAdapter.notifyDataSetChanged()
showShortToast("已清空")
}
fun updateList() {
studentList.addAll(FakeServer.randomKotlinResponse())
listAdapter.notifyDataSetChanged()
showShortToast("已更新10条数据")
}
fun gotoItemInfo(position: Int) {
var student = studentList[position]
startActivity<KInfoActivity>("stu_name" to student.name, "stu_age" to student.age, "stu_hobby" to student.hobby, "stu_university" to student.university)
}
}复制代码
// Java实现
public class JStudent {
private int id;
private String name;
private int age;
private String hobby;
private String university;
public JStudent(int id, String name, int age, String hobby, String university) {
setId(id);
setName(name);
setAge(age);
setHobby(hobby);
setUniversity(university);
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getHobby() {
return hobby;
}
public String getUniversity() {
return university;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
public void setUniversity(String university) {
this.university = university;
}
}复制代码
// Kotlin实现
data class KStudent(var id: Int, var name: String, var age: Int, var hobby: String, var university: String)复制代码
上面的代码对比已经能看出一些惊人的差距,咱们再统计一下用两种方式编写的具体代码行数,以评判二者的开发效率。
Java Part/LOC | Kotlin Part/LOC | |
---|---|---|
ListActivity | 85 | 38 |
ListUI | 52 | 62 |
InfoActivity | 62 | 29 |
InfoUI | 114 | 120 |
ListAdapter | 71 | 33 |
ListItemUI | 48 | 67 |
Student Data Class | 64 | 1 |
Total | 496 | 350 |
能够看到,使用了Anko编写的Kotlin页面UI,在代码行数上甚至比xml编写还要多一点。可是因为再也不须要findViewById,对应Activity中减小了不少逻辑,代码行也少了不少。同时,这种写法也节约了CPU去解析xml生成页面的资源和时间。
最终此Demo中使用Java和Kotlin编写相同的页面功能,Kotlin比Java少了30%的代码开发量。再加上空指针安全等特性,表现能够说是很是亮眼。
Kotlin做为一个JVM上的新语言,充分兼容了老大哥Java的诸多功能,又构建了不少自身优秀特性,提供了大量便捷易懂、结构清晰的开发形式。笔者做为一个Android开发,很是乐于在从此的开发中尝试彻底化的Kotlin项目。本文限于篇幅,仅能展示出其诸多特色的一点皮毛,更多新特性还须要各位看官本身在实际使用中挖掘。
套用刘禹锡的一句诗来结束本文吧。“芳林新叶催陈叶,流水前波让后波”,期待吸取了Java之长的Kotlin,能在将来有更好的表现。