关于kotlin:开始切换到-Kotlin-谷歌工程师给初学者的知识点总结

32次阅读

共计 7167 个字符,预计需要花费 18 分钟才能阅读完成。

在 2019 年的 I/O 大会上,咱们曾发表 Kotlin 将会是 Android 利用开发的首选语言,然而,局部开发者们反馈仍不分明如何切换到 Kotlin,如果团队中没有人相熟 Kotlin,一开始间接应用 Kotlin 进行我的项目开发还是会令人生畏。

在 Android Studio Profiler 团队外部,咱们是通过几个步骤克服了这个问题,第一步是要求所有的单元测试应用 Kotlin 编写。这么做无效防止了咱们犯的任何渺小谬误间接影响到生产环境中的代码,因为单元测试与生产环境的代码是离开的。

我收集了咱们团队在历次 Code Review 中遇到过的常见问题并整顿出了这篇文章,心愿这篇文章对宽广 Android 社区的敌人们有所帮忙。

留神: 本文的指标读者是 Kotlin 的初学者,如果您的团队曾经纯熟应用 Kotlin 进行我的项目开发,本文对您的帮忙可能不大。但如果您感觉咱们脱漏了一些应该被提及的内容,请在本文留言区留言通知咱们。

IDE 性能: 把 Java 文件转换成 Kotlin 文件

如果您应用 Android Studio 开发程序,学习 Kotlin 的最简略办法是应用 Java 语言编写单元测试,而后在 Android Studio 的菜单栏中点击 Code -> Convert Java File to Kotlin File 按钮将 Java 文件转换成 Kotlin 文件。

这个操作可能会提醒您 “Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?”,它的意思是说我的项目中的其余代码可能会受到此次转换的影响,而且有可能会导致谬误,请问是否须要定位出错的代码,并对相干代码进行批改。我倡议抉择 “No”,这样您就能够将代码的批改集中在一个文件上。

尽管通过此操作生成了 Kotlin 代码,然而转换进去的代码还是有很多晋升空间,上面各个章节介绍针对主动生成代码的优化技巧,这是咱们通过几十次的 Code Review 总结进去的技巧。当然,Kotlin 的性能远不止上面探讨的内容,本文会聚焦在于咱们团队遇到过的问题,特地是可复现的问题上。

两种语言的高阶比照

Java 与 Kotlin 在高阶角度来看是十分类似的,上面是别离应用 Java 与 Kotlin 编写的根本单元测试代码。

/// Java
public class ExampleTest {
  @Test
  public void testMethod1() throws Exception {}
  @Test
  public void testMethod2() throws Exception {}
}
/// Kotlin
class ExampleTest {
  @Test
  fun testMethod1() {}
  @Test
  fun testMethod2() {}
}

在 Kotlin 里须要留神:

  • 办法与类的默认修饰符类型是 Public
  • 函数定义中如果返回值是空 (void) 的话能够疏忽
  • 没有查看性异样

分号是可选项

这个个性一开始可能会让您不适应。然而在实践中,您不须要有过多的放心。您能够依照以前的编程习惯应用分号,而且不会影响到代码的编译过程,但 IDE 会主动找出这些可删除的分号并提醒您。只须要在提交代码之前删掉就能够了。

无论您喜爱与否,Java 曾经在某些中央早就不应用分号了,如果跟 C++ 比照的话会更显著 (因为 C++ 应用分号的场景更多)。

/// C++
std::thread([this] {this->DoThreadWork(); });
/// Java
new Thread(() -> doThreadWork());
/// Kotlin
Thread {doThreadWork() }

在尾部申明类型

/// Java
int value = 10;
Entry add(String name, String description)
/// Kotlin
var value: Int = 10
fun add(name: String, description: String): Entry

和分号是可选项相似,如果您还没有习惯应用这个个性的话,可能会让您难以承受,它与很多人在他们编程生涯中遇到过的类型申明相比,这里的程序正好相同。

但这个语法带来的益处是,如果变量类型是能够主动被揣测进去的话,此时能够间接跳过类型申明。这个个性在前面的 “ 省略变量类型 ” 章节里有介绍。

还有个益处是能够把更多的注意力放在变量自身而不是它的类型上。而且我发现在探讨代码的时候,类型在后的程序听起来更天然 (从英文语言角度)。

/// Java
int result;     // 整数型的变量,名字叫 "result"
/// Kotlin
var result: Int // 变量名字叫 "result",是整数型 

对此语法我想说的最初一件事件是,尽管一开始您可能感觉不适,然而随着应用频率的减少,您缓缓会习惯。

没有 new 关键字的构造函数

Kotlin 中不须要应用 new 关键字调用构造函数。

/// Java
... = new SomeClass();
/// Kotlin
... = SomeClass()

起初这会让您感觉漏掉了要害信息 (指创立内存的操作),但不须要放心。因为在 Java 中,有些函数会在您不知情的状况下创立内存。对此,您素来也没有关怀过 (也不须要关怀)。很多函数库甚至还有创立内存的静态方法,比方:

/// Java
Lists.newArrayList();

Kotlin 只是对立了函数的这种行为。因为对一个函数来说,无论分配内存与否,这只是它的附加成果而已,并不影响函数调用。

而且它还简化了创建对象只是为了调用其办法的写法。

/// Java
new Thread(...).start(); // 有点奇怪然而是能够这样写的
/// Kotlin
Thread(...).start()

可变性与不可变性

在 Java 中变量默认是可变的,应用 final 关键字能够使变量为不可变。与之相同,在 Kotlin 中是没有 final 关键字。您须要应用 val 关键字批示变量是不可变的,应用 var 关键字批示变量是可变的。

/// Java
private final Project project; // 初始化之后无奈再赋值
private Module activeModule;   // 初始化之后能够再赋值
/// Kotlin
private val project: Project     // 初始化之后无奈再赋值
private var activeModule: Module // 初始化之后能够再赋值 

在 Java 中您可能会常常遇到很多成员变量应该是常数,然而却没有应用 final 关键字 (遗记加上 final 是容易犯的谬误)。在 Kotlin 中您必须显式地申明每个成员变量的类型。如果您一开始不确定该抉择哪种类型,那就默认应用 val 类型,前面有需要变动时再改为 var。

顺便说一句,在 Java 中函数参数类型是可变的,然而能够应用 final 关键字批改为不可变。在 Kotlin 中,函数参数始终是不可变的,它们是被 val 关键字隐式地标记为不可变。

/// Java
public void log(final String message) {…}
/// Kotlin
fun log(message: String) {…} // "message" is immutable

可空性

Kotlin 中勾销了 @NotNull 跟 @Nullable 的注解办法。如果变量能够赋值为 null,您只须要在变量类型前面加上一个 “?” 就能够了:

/// Java
@Nullable Project project;
@NotNull String title;
/// Kotlin
val project: Project?
val title: String

在某些状况下,当您确定某些能够被赋值为 null 的变量不可能是 null,您能够应用 !! 操作符设置一个断言。

/// Kotlin
// 'parse' 能够返回 null,但这条用例总是可能运行
val result = parse("123")!!
// 上面这行是多余的,因为 !! 曾经触发断言了
❌ assertThat(result).isNotNull()

如果您谬误地应用了 !!,它有可能会抛出 NullPointerException 的异样。在单元测试中,这只会造成测试用例的失败,然而在生产环境中,可能会使程序解体,所以要十分小心。事实上,在生产环境的代码中有太多的 !! 操作符,可能意味着此处有 “ 代码异味 ” (“ 代码异味 ” 代表这部分代码可能须要审查或重构)。

在单元测试中,测试用例里应用 !! 操作符是可承受的,起因是当假如不成立的时候测试用例会失败,并且您还能够修复它。

如果您确定应用 !! 操作符是有意义的,那尽量靠前应用,如上面的用法:

/// Kotlin
val result = parse("...")!!
result.doSomething()
result.doSomethingElse()

上面是谬误用法:

/// Kotlin (主动生成)
val result = parse("...")
❌ result!!.doSomething()
❌ result!!.doSomethingElse()

可省略变量的类型

在 Java 中会看到如下写法的代码:

/// Java
SomeClass instance1 = new SomeClass();
SomeGeneric<List<String>> instance2 = new SomeGeneric<>();

在 Kotlin 中,类型申明被认为是冗余的操作,不须要写两次:

/// Kotlin
val instance1 = SomeClass()
val instance2 = SomeGeneric<List<String>>()

然而,咱们在应用动静绑定的时候可能会须要申明这些类型:

/// Java
BaseClass instance = new ChildClass(); // 如:List = new ArrayList

在 Kotlin 中应用上面语法达到同样目标:

/// Kotlin
val instance: BaseClass = ChildClass()

没有查看性异样

不像 Java 那样,Kotlin 中的类办法不须要申明本人的异样类型。因为在 Kotlin 中查看性异样 (Checked Exception) 跟运行时异样 (Runtime Exception) 之间是没有区别的。

/// Java
public void readFile() throws IOException { …}
/// Kotlin
fun readFile() { …}

然而为了使 Java 能够调用 Kotlin 的代码,Kotlin 还是提供 @Throws 注解性能,用于隐式的申明异样类型。当执行 Java → Kotlin 转换时,IDE 会保障安全性并且始终蕴含这类信息。

/// Kotlin (从 Java 主动转换而来)
@Throws(Exception::class)

fun testSomethingImportant() { …}

但您不须要放心您的单元测试会被 Java 类调用。因而,您能够平安地删除这些类型申明而且还能够缩小代码行数:

/// Kotlin
fun testSomethingImportant() { …}

Lambda 调用时能够省略括号

在 Kotlin 中,如果您想要把一个闭包赋值给变量,您须要显式地申明它的类型:

val sumFunc: (Int, Int) -> Int = {x, y -> x + y}

如果所有类型是能够被揣测的,则能够简写成:

{x, y -> x + y}

举个例子:

val intList = listOf(1, 2, 3, 4, 5, 6)
val sum = intList.fold(0, { x, y -> x + y})

须要留神的是,如果一个函数调用的最初一个参数是 lambda 调用时,这时候能够把闭包写在函数括号的里面。

下面代码等同于如下:

val sum = intList.fold(0) {x, y -> x + y}

有能力做,但并不意味着您应该这么做。有些人会感觉下面应用 fold 的办法比拟奇怪。某些场景下这种语法缩小了视觉烦扰,特地是函数的参数只有一个闭包时。如果咱们想统计偶数的数量时,比照如下两个用法:

用法一:

intList.filter({x -> x % 2 == 0}).count()

用法二:

intList.filter {x -> x % 2 == 0}.count()

或者比照 Thread 函数应用,如下两个用法:

用法一:

Thread({doThreadWork() })

用法二:

Thread {doThreadWork() }

无论您喜爱与否,当您在 Kotlin 中看到这类用法时您应该晓得它是怎么工作的,Java → Kotlin 转换中也会用到这种语法。

equals() 办法、== 与 === 运算符

Kotlin 在相等性测试方面不同于 Java。

在 Java 中,== 运算符是用于比拟两个对象的援用是否雷同,它是有别于 equals() 办法。只管从实践上听起来不错,在实践中开发者常常会在须要应用 equals 的中央应用了 == 运算符。这可能会引入不易觉察的 Bug,须要破费数小时来定位问题。

在 Kotlin 中 == 运算符等同于 equals 办法,惟一的区别是它还能正确地解决与 null 间的比拟。举个例子,null==x 是非法的操作,然而 null.equals(x) 会抛出 NullPointerException 异样。

如果您想在 Kotlin 中判断对象援用的相等性,那您能够应用 === 运算符,这种语法不容易用错而且还更容易定位问题。

/// Java
Color first = new Color(255, 0, 255);
Color second = new Color(255, 0, 255);
assertThat(first.equals(second)).isTrue();
assertThat(first == second).isFalse();
/// Kotlin
val first = Color(255, 0, 255)
val second = Color(255, 0, 255)
assertThat(first.equals(second)).isTrue()
assertThat(first == second).isTrue()
assertThat(first === second).isFalse()

当您应用 Kotlin 的时候,大部分状况下会应用 == 运算符,因为 === 运算符的利用场景相对来说比拟少。须要指出的是,Java → Kotlin 转换器始终会把 Java 中的 == 运算符转换成 Kotlin 中的 === 运算符。出于代码可读性跟性能用意思考,在必要时您应该把 === 运算符复原成 == 运算符。比照枚举类型时常常会遇到下面所说的状况,如下所示:

/// Java
if (day == DayOfWeek.MONDAY) {…}

/// Kotlin (从 Java 主动转换而来)
❌ if (day === DayOfWeek.MONDAY) {…}

/// Kotlin
if (day == DayOfWeek.MONDAY) {…}

移除成员变量的前缀

在 Java 中,对公有变量编写成对的 getter 与 setter 办法是很常见的做法,而且很多 Java 代码给成员变量命名时加上了前缀,有点像是匈牙利命名法。

/// Java
private String myName;
// 或者 private String mName;
// 或者 private String _name;
public String getName() { …}
public void setName(String name) {…}

这种前缀适宜给变量做标记,代表着该变量只在类的外部可见。而且还容易辨别是类的外部成员变量还是通过函数参数传递进来的变量。

在 Kotlin 中,成员变量与 getter/setters 办法被整合成同一个概念。

/// Kotlin
class User {
 val id: String   // 代表成员变量与 getter 办法
 var name: String // 代表成员变量与 getter 和 setter 办法
}

当您应用主动转换性能时,Java 中的成员变量前缀有时候会被保留下来,带来的隐患是已经暗藏在内部类中的实现细节有可能会被 public 接口裸露进去。

/// Kotlin (从 Java 主动转换而来)
class User {
❌ val myId: String
❌ var myName: String
}

为了避免前缀带来的实现细节的裸露,建议您养成移除前缀的习惯。

有时候浏览没有前缀的成员变量代码时候会比拟吃力,尤其是应用网页版的 Code Review 工具 (比方在很长的类中浏览很长的函数)。不过当您应用 IDE 浏览代码时候,能够通过语法高亮性能很分明地晓得哪些是成员变量,哪些是函数参数。您能够通过勾销前缀来编写目标更为聚焦的函数与类,以便养成更好的编程习惯。

结束语

心愿本文章有助于您开始学习 Kotlin。您从编写 Java 开始,应用主动转换性能将 Java 转换成 Kotlin。这时候您会编写 Java 格调的 Kotlin 代码,随着练习,不久之后您将会像专家那样熟练地编写 Kotlin 代码了。

这篇文章只是简略介绍了 Kotlin 的应用。它的目标在于向那些没有工夫学习但须要将测试用例跑起来的开发者们介绍 Kotlin 的基本概念与语法。

当然,本文并没有涵盖您须要晓得的所有。为此,请参考学习官网的 Kotlin 文档:

  • 语言参考
  • 互动教程

语言参考教程十分有用,它涵盖了 Kotlin 的所有知识点,而且难度适中。

互动教程提供了学习编程语言的平台,还蕴含了一系列练习题用于验证您所学到的知识点是否正确。

最初,为了将您的代码重构到 Kotlin,请尝试咱们为您筹备的 Codelab —— “ 重构为 Kotlin”,它蕴含了本文中介绍过的内容和其余方面的更多内容。

正文完
 0