作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Ivan Kušt的头像

Ivan Kušt

Ivan是一个移动爱好者,他完善了移动应用程序的开发过程和架构.

Previously At

Infinum
Share

Introduction

A while ago, Tomasz介绍了Kotlin在Android上的开发. 提醒您:Kotlin是由 Jetbrains该公司是最流行的Java ide之一的背后, IntelliJ IDEA. 与Java一样,Kotlin也是一种通用语言. 因为它符合Java虚拟机(JVM)字节码, 它可以与Java并行使用, 而且它不会带来性能开销.

在本文中,我将介绍促进Android开发的十大有用特性.

Note在撰写本文时,实际版本是Android Studio 2.1.1. and Kotlin 1.0.2.

Kotlin

厌倦了没完没了的Java代码? 试试Kotlin,节省你的时间和理智.

Kotlin Setup

由于Kotlin是由JetBrains开发的,所以它在Android Studio和IntelliJ中都得到了很好的支持.

第一步是 安装Kotlin插件. 成功这样做之后,将有新的操作可用于将Java转换为Kotlin. 两个新的选择是:

  1. 创建一个新的Android项目,并在项目中设置Kotlin.
  2. 为现有的Android项目添加Kotlin支持.

要了解如何创建一个新的Android项目,请查看 官方一步一步的指导. 要向新创建的或现有的项目添加Kotlin支持,请打开 查找动作对话框 using Command + Shift + A on Mac or Ctrl + Shift + A 在Windows/Linux上,调用 在Project中配置Kotlin action.

要创建一个新的Kotlin类,选择:

  • File > New > Kotlin file/class, or
  • File > New > Kotlin activity

或者,您可以创建一个Java类,并使用上面提到的操作将其转换为Kotlin. Remember, 您可以使用它来转换任何类, interface, 枚举或注释, 这可以用来比较Java和Kotlin代码.

另一个节省大量输入的有用元素是Kotlin扩展. 要使用它们,你必须在你的模块中应用另一个插件 build.gradle file:

应用插件:'kotlin-android-extensions'

Caveat:如果你正在使用Kotlin插件动作来设置你的项目, 它将把以下代码放在顶层 build.gradle file:

buildscript {
   ext.Kotlin_version = '1.0.2'
   repositories {
       jcenter()
   }
   dependencies {
       classpath "org.jetbrains.芬兰湾的科特林:kotlin-gradle-plugin: $ kotlin_version”

       // NOTE: Do not place your application dependencies here; they belong
       //在单独的模块构建中.gradle files
   }
}

这将导致扩展不工作. 要解决这个问题,只需将该代码复制到希望使用Kotlin的每个项目模块中.

如果你设置正确的话, 你应该能够像在标准Android项目中那样运行和测试你的应用程序, 但现在使用Kotlin.

用Kotlin节省时间

So, 让我们从描述Kotlin语言的一些关键方面开始,并提供一些如何使用Kotlin代替Java来节省时间的技巧.

特性#1:静态布局导入

Android中最常见的样板代码之一是使用 findViewById() 函数来获取对活动或片段中视图的引用.

有一些解决方案,比如 Butterknife library, 这样就节省了一些输入, 但是Kotlin又迈出了一步,允许您通过一次导入从布局中导入对视图的所有引用.

例如,考虑以下活动XML布局:




    

以及附带的活动代码:

package co.ikust.kotlintest

import android.support.v7.app.AppCompatActivity
import android.os.Bundle

import kotlinx.android.synthetic.main.activity_main.*

类MainActivity: AppCompatActivity() {

    重载onCreate(savedInstanceState: Bundle)?) {
        super.onCreate (savedInstanceState)
        setContentView(R.layout.activity_main)

        helloWorldTextView.text = "你好,世界!"
    }
}

获取具有已定义ID的布局中所有视图的引用, 使用Android Kotlin扩展 Anko. 记住输入这个import语句:

import kotlinx.android.synthetic.main.activity_main.*

注意,在Kotlin中不需要在行尾写分号,因为分号是可选的.

The TextView 从布局中导入 TextView 实例的名称等于视图的ID. 不要被语法弄糊涂了,它是用来设置标签的:

helloWorldTextView.text = "你好,世界!"

我们很快就会讲到.

Caveats:

  • 确保导入了正确的布局,否则导入的视图引用将具有 null value.
  • 当使用片段时,请确保使用了导入的视图引用 after the onCreateView() function call. 将布局导入 onCreateView() 函数,并使用View引用来设置UI onViewCreated(). 参考文献不会在 onCreateView() 方法已经完成.

特性#2:用Kotlin编写POJO类

使用Kotlin可以节省大部分时间的是编写用于保存数据的POJO (Plain Old Java Object)类. 例如,在RESTful API的请求和响应体中. 在依赖RESTful API的应用程序中,会有很多这样的类.

在Kotlin中,为您做了很多工作,语法也很简洁. 例如,考虑下面的Java类:

公共类用户{
   private String firstName;
  
   私有字符串lastName;

   公共字符串getFirstName() {
       return firstName;
   }

   public void setFirstName(String firstName) {
       this.firstName = firstName;
   }

   公共字符串getLastName() {
       return lastName;
   }

   public void setLastName(String lastName) {
       this.lastName = lastName;
   }
}

当使用Kotlin时,您不必再次编写public关键字. 默认情况下,所有内容都属于公共作用域. 例如,如果你想声明一个类,你只需这样写:

class MyClass {
}

在Kotlin中相当于上面的Java代码:

class User {
   var firstName:字符串? = null

   var lastName:字符串? = null
}

这样就省了不少打字的工夫,不是吗? 让我们浏览一下Kotlin代码.

Kotlin节省了大量的输入

在Kotlin中定义变量时,有两种选择:

  • 定义的可变变量 var keyword.
  • 定义的不可变变量 val keyword.

The next thing to note is the syntax differs a bit from Java; first, 先声明变量名,然后再声明类型. 此外,默认情况下,属性是非空类型,这意味着它们不能接受 null value. 定义一个变量来接受 null 值时,必须在类型后面加上问号. 稍后我们将在Kotlin中讨论这个和null-safety.

Another important thing to note is that Kotlin doesn’t have the ability to declare fields for the class; only properties can be defined. So, in this case, firstName and lastName 属性是否被指定为默认的getter/setter方法. 如前所述,在Kotlin中,它们在默认情况下都是公共的.

可以编写自定义访问器,例如:

class User {
   var firstName:字符串? = null

   var lastName:字符串? = null

   val fullName:字符串?
        get() firstName + " " + lastName
}

从外部看,当涉及到语法时,属性的行为就像Java中的公共字段:

val userName = user.firstName
user.firstName = "John"

注意这个新属性 fullName 是只读的(定义为 val keyword) and has a custom getter; it simply appends first and last name.

Kotlin中的所有属性都必须在声明时或在构造函数中赋值. There are some cases when that isn’t convenient; for example, 对于将通过依赖注入初始化的属性. In that case, a lateinit 可以使用修饰语. 下面是一个例子:

class MyClass {
    lateinit var firstName:字符串;

    fun inject() {
        firstName = "John";
    }
}

有关属性的更多详细信息,请参见 官方文档.

特性#3:类继承和构造函数

Kotlin在构造函数方面也有更简洁的语法.

Constructors

Kotlin类有一个主构造函数和一个或多个辅助构造函数. 定义主构造函数的一个例子:

类用户构造函数(firstName: String, lastName: String) {
}

主构造函数位于类定义中的类名之后. 如果主构造函数没有任何注释或可见性修饰符, 可以省略constructor关键字:

类Person(firstName: String) {
}

Note that a primary constructor cannot have any code; any initialization must be done in the init code block:

类Person(firstName: String) {
    init {
         //在这里执行主构造函数初始化
    }
}

此外,主构造函数可用于定义和初始化属性:

类用户(var firstName:字符串,var lastName:字符串){
  // ...
}

与普通构造函数一样,从主构造函数定义的属性可以是不可变的(val) or mutable (var).

Classes may have secondary constructors as well; the syntax for defining one is as follows:

类用户(var firstName:字符串, var lastName) {
    构造函数(name: String, parent: Person): this(name) {
        parent.children.add(this)
    }
}

注意,每个辅助构造函数都必须委托给主构造函数. 这与Java类似,Java使用 this keyword:

类用户(val firstName: String, val lastName: String) {
    构造函数(firstName: String): this(firstName, "") {
       //...
    }
}

在实例化类时,请注意Kotlin没有 new 关键字,Java也是如此. 实例化前面提到的 User class, use:

val user = user ("John", "Doe ")

引入继承

在Kotlin中,所有类都从 Any,类似于 Object in Java. 默认情况下,类是封闭的,就像Java中的final类一样. 因此,为了扩展一个类,必须将它声明为 open or abstract:

打开类User(val firstName, val lastName)
类管理员(val firstName, val lastName):用户(firstName, lastName)

注意,您必须委托给扩展类的默认构造函数, 哪个和打电话类似 super() 方法在Java的新类的构造函数中.

有关课程的更多详细信息,请查看 官方文件.

特性#4:Lambda表达式

Java 8引入的Lambda表达式是它最喜欢的特性之一. However, Android的情况就不那么乐观了, 因为它仍然只支持Java 7, 看起来Java 8短期内不会被支持. 所以,变通方法,比如 Retrolambda,将lambda表达式引入Android.

使用Kotlin,不需要额外的库或解决方案.

Kotlin中的函数

让我们先快速浏览一下Kotlin中的函数语法:

add(x: Int, y: Int): Int {
    return x + y
}

函数的返回值可以省略,在这种情况下,函数将返回 Int. 值得重复的是,Kotlin中的所有东西都是对象,从 Any,并且没有原始类型.

函数的参数可以有一个默认值,例如:

add(x: Int, y: Int = 1): Int {
    return x + y;
}

In that case, the add() 函数可以通过只传递 x argument. 等效的Java代码是:

int add(int x) {
   Return add(x, 1);
}

Int add(Int x, Int y) {
    return x + y;
}

调用函数的另一个好处是可以使用命名参数. For example:

加(y = 12, x = 5)

有关函数的更多详细信息,请查看 官方文档.

在Kotlin中使用Lambda表达式

Kotlin中的Lambda表达式可以看作是Java中的匿名函数, 但是用更简洁的语法. 作为示例,让我们展示如何在Java和Kotlin中实现单击侦听器.

In Java:

view.setOnClickListener(new OnClickListener()) {
    @Override
    public void onClick(View v) {
        Toast.makeText(v.getContext(), " click on view", Toast.LENGTH_SHORT).show();
    }
};

In Kotlin:

view.setOnClickListener({ view -> toast("Click") })

Wow! 只有一行代码! 我们可以看到,lambda表达式被花括号包围. 首先声明形参,然后声明体 -> sign. 对于click listener,视图参数的类型没有指定,因为它可以被推断出来. 身体只是一个调用 toast() Kotlin提供的显示toast的函数.

同样,如果不使用参数,我们可以省略它们:

view.setOnClickListener({toast("Click")})

Kotlin优化了Java库, 任何接收到带有一个方法作为参数的接口的函数都可以用函数参数调用(而不是interface)。.

此外,如果函数是最后一个参数,则可以将其移出括号:

view.setOnClickListener() {toast("Click")}

最后,如果函数只有一个函数形参,括号可以省略:

view.setOnClickListener {toast("Click")}

更多信息请查看 Kotlin Android开发者手册 安东尼奥·莱瓦和 官方文档.

扩展函数

Kotlin, similar to C#, 提供通过使用扩展函数扩展具有新功能的现有类的能力. 例如,一个扩展方法可以计算a的MD5哈希值 String:

fun String.md5(): ByteArray {
    val digester = messageddigest.getInstance("MD5")
    digester.update(this.toByteArray(字符集.defaultCharset()))
    return digester.digest()
}

请注意,函数名前面是扩展类的名称(在本例中, String),并且扩展类的实例可以通过 this keyword.

扩展函数相当于Java实用程序函数. Java中的示例函数是这样的:

public static int number (String instance) {
	return Integer.返回对象的值(实例);
}

示例函数必须放在Utility类中. 这意味着扩展函数不会修改原始的扩展类, 而是一种编写实用程序方法的方便方法.

特性5:零安全

在Java中你最忙的一件事可能是 NullPointerException. null安全是集成到Kotlin语言中的一个特性,它是隐式的,您通常不必担心. The 官方文档 说明唯一可能的原因 nullpointerexception are:

  • 对throw的显式调用 NullPointerException.
  • Using the !! 操作符(我将在后面解释).
  • 外部Java代码.
  • If the lateinit 属性在初始化之前在构造函数中访问UninitializedPropertyAccessException will be thrown.

默认情况下,会考虑Kotlin中的所有变量和属性 non-null (unable to hold a null 值),如果它们没有显式声明为可空的话. 如前所述,定义一个变量来接受 null 值时,必须在类型后面加上问号. For example:

val number: Int? = null

但是,请注意以下代码将无法编译:

val number: Int? = null
number.toString()

这是因为编译器执行 null checks. To compile, a null 必须添加Check:

val number: Int? = null

if(number != null) {
    number.toString();
}

此代码将成功编译. 在本例中,Kotlin在后台所做的是 number becomes nun-null (Int instead of Int?)在if块中.

The null 检查可以简化使用 安全呼叫接线员 (?.):

val number: Int? = null
number?.toString()

只有当编号不是时才执行第二行 null. 你甚至可以用名人 Elvis operator (?:):

val number Int? = null
val stringNumber = number?.toString() ?: "Number is null"

如果左边的表达式 ?: is not null,它被求值并返回. 否则,返回右边表达式的结果. 你还可以使用 throw or return 在猫王运算符的右边,因为它们是Kotlin中的表达式. For example:

sendMailToUser(user: user) {
    val email = user?.email ?抛出新的IllegalArgumentException("User email is null")
    //...
}

The !! Operator

If you want a NullPointerException 抛出的方式与Java中相同,您可以使用 !! operator. 下面的代码将抛出 NullPointerException:

val number: Int? = null
number!!.toString()

Casting

cast是用an完成的 as keyword:

val x: String = y as String

这被认为是“不安全的”cast,因为它会抛出 ClassCastException 如果不可能强制转换,就像Java一样. 有一个“安全”强制转换操作符返回 null 值,而不是抛出异常:

val x: String = y as? String

有关铸造的更多细节,请查看 类型强制转换和强制类型转换 部分的官方文件,以及更多的详细信息 null safety check the Null-Safety section.

lateinit properties

有一种使用 lateinit 属性可能导致类似的异常 NullPointerException. 考虑下面的类:

class InitTest {
    lateinit var s: String;

    init {
        val len = this.s.length
    }

}

此代码将在没有警告的情况下编译. 然而,一旦一个实例 TestClass is created, an UninitializedPropertyAccessException 会因为财产被扔吗 s 是否在初始化之前被访问.

特性#6:功能 with()

Function with() 是有用的,并随Kotlin标准库一起提供的. 如果需要访问对象的许多属性,可以使用它来节省一些输入. For example:

(helloWorldTextView) {
    text = "你好,世界!"
    visibility = View.VISIBLE
}

它接收一个对象和一个扩展函数作为参数. 代码块(在花括号中)是一个lambda表达式,用于作为第一个参数指定的对象的扩展函数.

特性#7:操作符重载

使用Kotlin,可以为一组预定义的操作符提供自定义实现. 实现一个运算符, 必须提供具有给定名称的成员函数或扩展函数.

For example, 实现乘法运算符, 成员函数或扩展函数, with the name times(argument),必须提供:

操作乐趣串.times(b: Int): String {
    val buffer = StringBuffer()

    for (i in 1..b) {
        buffer.append(this)
    }

    return buffer.toString()
}

上面的示例显示了二进制的实现 * operator on the String. 例如,下面的表达式将值" TestTestTestTest "赋值给a newString variable:

val newString =“测试”* 4

因为可以使用扩展函数, 这意味着可以更改所有对象的操作符的默认行为. 这是一把双刃剑,应该谨慎使用. 有关可重载的所有操作符的函数名列表,请查看 官方文档.

与Java相比,另一个很大的不同是 == and != operators. Operator == translates to:

a?.equals(b) ?: b === null 

While operator != translates to:

!(a?.equals(b) ?:

这意味着使用 == 不像Java那样进行身份检查(比较对象的实例是否相同), 但是和 equals() method along with null checks.

要执行身份检查,操作符 === and !== 必须在Kotlin中使用.

特性#8:委托属性

某些属性共享一些共同的行为. For instance:

  • 在第一次访问时初始化的延迟初始化属性.
  • 在Observer模式中实现Observable的属性.
  • 属性存储在映射中,而不是作为单独的字段.

为了使这样的情况更容易实现,Kotlin支持 委托的属性:

class SomeClass {
    var p:字符串由委托()
}

这意味着属性的getter和setter函数 p 由另一个类的实例处理, Delegate.

的委托示例 String property:

class Delegate {
  操作符getValue(thisRef: Any)?, property: KProperty<*>): String {
    $thisRef,感谢您委托${属性.name}' to me!"
  }
 
  操作符getvalue (thisRef: Any)?, property: KProperty<*>, value: String) {
    Println ("$value已被赋值给${属性.name} in $thisRef.'")
  }
}

上面的示例在分配或读取属性时打印一条消息.

可以为可变的(var) and read-only (val) properties.

对于只读属性, getValue 方法必须实现。. 它接受两个参数(取自 官方文档):

  • 接收者-必须是属性所有者的相同或超类型(对于扩展属性), 它是被扩展的类型).
  • 元数据-必须是类型 KProperty<*> or its supertype.

此函数必须返回与属性或其子类型相同的类型.

对于可变属性,委托必须另外提供一个名为 setValue 它接受以下参数:

  • 接收方——和for一样 getValue().
  • 元数据-与for相同 getValue().
  • 新值——必须与属性或其超类型具有相同的类型.

Kotlin提供了一些标准委托,涵盖了最常见的情况:

  • Lazy
  • Observable
  • Vetoable

Lazy

Lazy是一个接受lambda表达式作为参数的标准委托. 传递的lambda表达式第一次执行 getValue() method is called.

默认情况下,惰性属性的计算是同步的. 如果您不关心多线程,您可以使用 懒惰(LazyThreadSafetyMode.NONE) { … } 为了获得额外的表现.

Observable

The Delegates.observable() 是为属性,应该表现为观察者模式中的可观察对象. 它接受两个参数, 初始值和一个有三个参数的函数(属性, old value, and new value).

每次都会执行给定的lambda表达式 setValue() method is called:

class User {
    var email:由委托创建的字符串.observable("") {
        prop, old, new ->
        //处理从旧值到新值的更改
    }
}

Vetoable

这个标准委托是一种特殊的可观察对象,它让你决定是否存储分配给属性的新值. 它可以用来在赋值之前检查一些条件. As with Delegates.observable(),它接受两个参数:初始值和一个函数.

不同之处在于该函数返回一个布尔值. If it returns true,则分配给该属性的新值将被存储,否则将被丢弃.

var positiveNumber =代表.vetoable(0) {
    d, old, new ->
    new >= 0
}

给定的示例将只存储分配给该属性的正数.

欲了解更多详情,请查看 官方文档.

特性#9:将对象映射到映射

一个常见的用例是将属性的值存储在映射中. 这经常发生在使用RESTful api和解析JSON对象的应用程序中. 在这种情况下,映射实例可以用作委托属性的委托. 举个例子 官方文档:

class User(val map: Map) {
    val name:映射字符串
    val age:按map Int
}

In this example, User 有一个接受映射的主构造函数. 这两个属性将从映射中获取值,这些值映射在等于属性名称的键下:

val user = user (mapOf)
    "姓名"改为"无名氏"
    "age"  to 25
))

新用户实例的name属性将被赋值为“John Doe”,age属性的值为25.

这适用于var属性与 MutableMap as well:

class MutableUser(val map: MutableMap) {
    var name:映射字符串
    var age:按map值Int
}

特性#10:集合和函数操作

有了Kotlin中对lambdas的支持,集合可以被利用到一个新的高度.

首先,Kotlin区分了可变集合和不可变集合. 例如,有两个版本的 Iterable interface:

  • Iterable
  • MutableIterable

The same goes for Collection, List, Set and Map interfaces.

For example, this any operation returns true 如果至少有一个元素与给定谓词匹配:

val list = listOf(1,2,3,4,5,6)
assertTrue(list.任何{它% 2 == 0})

有关可以在集合上执行的函数操作的广泛列表,请查看此列表 blog post.

Conclusion

我们只是触及了Kotlin提供的服务的表面. 对于那些有兴趣进一步阅读和学习更多内容的人,请查看:

总而言之,Kotlin为您提供了在编写本机代码时节省时间的能力 Android 应用程序通过使用直观和简洁的语法. 它仍然是一种年轻的编程语言, but in my opinion, 它现在足够稳定,可以用于构建生产应用程序.

使用Kotlin的好处:

  • Android Studio的支持是无缝的和优秀的.
  • 将现有的Java项目转换为Kotlin很容易.
  • Java和Kotlin代码可以共存于同一个项目中.
  • 在应用程序中没有速度开销.

The downsides:

  • Kotlin将把它的库添加到生成的 .apk, so the final .apk 大小将增加约300KB.
  • 如果滥用,操作符重载可能导致代码不可读.
  • IDE和自动完成在使用Kotlin时比使用纯Java Android项目要慢一些.
  • 编译时间可能会稍长一些.
就这一主题咨询作者或专家.
Schedule a call
Ivan Kušt的头像
Ivan Kušt

Located in Zagreb, Croatia

Member since April 14, 2016

About the author

Ivan是一个移动爱好者,他完善了移动应用程序的开发过程和架构.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Previously At

Infinum

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.