什么是科特林? Java 替代方案说明
Kotlin 是一种通用、免费、开源、静态类型的“实用”编程语言,最初是为 JVM(Java 虚拟机)和 Android 设计的,并结合了面向对象和函数式编程的特性。 它专注于互操作性、安全性、清晰度和工具支持。 针对许多处理器的 JavaScript ES5.1 和本机代码(使用 LLVM)的 Kotlin 版本也在生产中。
Kotlin 于 2010 年起源于 IntelliJ IDEA 背后的公司 JetBrains,并从 2012 年开始开源。GitHub 上的 Kotlin 项目有超过 770 名贡献者; 虽然团队的大部分成员都在 JetBrains 工作,但 Kotlin 项目有近 100 名外部贡献者。 JetBrains 在其许多产品中使用 Kotlin,包括其旗舰产品 IntelliJ IDEA。
国际数据集团
Kotlin 作为一种更简洁的 Java 语言
乍一看,Kotlin 更像是 Java 的更简洁精简版。 考虑上面的屏幕截图,其中我已将 Java 代码示例(左侧)自动转换为 Kotlin。 请注意,实例化 Java 变量时固有的盲目重复已经消失。 Java 惯用语
StringBuilder sb = new StringBuilder();
在 Kotlin 中成为
val sb = StringBuilder()
您可以看到函数是用 fun
关键字,并且当存在换行符时,分号现在是可选的。 这 val
关键字声明只读属性或局部变量。 同样, var
关键字声明可变属性或局部变量。
然而,Kotlin 是强类型的。 这 val
和 var
仅当可以推断类型时才可以使用关键字。 否则你需要声明类型。 类型推断似乎随着 Kotlin 的每个版本都在改进。
查看两个窗格顶部附近的函数声明。 Java 中的返回类型在原型之前,但在 Kotlin 中它在原型之后,与 Pascal 中一样用冒号分隔。
从这个例子中并不完全明显,但 Kotlin 放宽了 Java 对函数是类成员的要求。 在 Kotlin 中,函数可以在文件的顶层声明,在其他函数内部本地声明,作为类或对象内的成员函数,以及作为扩展函数。 扩展函数提供了类似于 C# 的能力,可以使用新功能扩展类,而无需从类继承或使用任何类型的设计模式(如 Decorator)。
对于 Groovy 粉丝,Kotlin 实现了构建器; 事实上,Kotlin 构建器可以进行类型检查。 Kotlin 支持委托属性,可以用来实现惰性属性、可观察属性、可否决属性和映射属性。
许多在其他语言中可用的异步机制可以使用 Kotlin 协程作为库来实现。 这包括 async
/await
来自 C# 和 ECMAScript,通道并从 Go 中选择,以及 generators
/yield
来自 C# 和 Python。
Kotlin 中的函数式编程
允许使用顶级函数只是 Kotlin 函数式编程故事的开始。 该语言还支持高阶函数、匿名函数、lambda、内联函数、闭包、尾递归和泛型。 换句话说,Kotlin 具有函数式语言的所有特性和优势。 例如,考虑以下功能性 Kotlin 习语。
在 Kotlin 中过滤列表
val positives = list.filter { x -> x > 0 }
对于更短的表达式,使用 it
当 lambda 函数中只有一个参数时:
val positives = list.filter { it > 0 }
遍历 Kotlin 中的映射/对列表
for ((k, v) in map) { println(“$k -> $v”) }
k
和 v
可以叫任何东西。
在 Kotlin 中使用范围
for (i in 1..100) { ... } // closed range: includes 100for (i in 1 until 100) { ... } // half-open range: does not include 100for (x in 2..10 step 2) { ... }for (x in 10 downTo 1) { ... }if (x in 1..10) { ... }
上面的例子表明 for
关键字以及范围的使用。
尽管 Kotlin 是一种成熟的函数式编程语言,但它作为一种替代编程风格保留了 Java 的大部分面向对象特性,这在转换现有 Java 代码时非常得心应手。 Kotlin 拥有带构造函数的类,以及嵌套的、内部的和匿名的内部类,并且它具有类似于 Java 8 的接口。Kotlin 没有 new
关键词。 要创建类实例,就像调用常规函数一样调用构造函数。 我们在上面的屏幕截图中看到了这一点。
Kotlin 从一个命名的超类中继承,所有的 Kotlin 类都有一个默认的超类 Any
这与 Java 基类不同 java.lang.Object
. Any
仅包含三个预定义的成员函数: equals()
, hashCode()
, 和 toString()
.
Kotlin 类必须标有 open
关键字以允许其他类继承它们; Java 类有点相反,因为它们是可继承的,除非标有 final
关键词。 要覆盖超类方法,必须标记方法本身 open
子类方法必须标明 override
. 这完全符合 Kotlin 使事情明确而不是依赖默认值的理念。 在这种特殊情况下,我可以看到 Kotlin 显式将基类成员标记为对继承开放并将派生类成员标记为覆盖的方式避免了几种常见的 Java 错误。
Kotlin 中的安全特性
说到避免常见错误,Kotlin 旨在消除空指针引用的危险并简化空值的处理。 它通过制作一个 null
对于标准类型是非法的,添加可为 null 的类型,并实现快捷符号来处理 null 测试。
例如,类型的常规变量 String
保持不住 null
:
var a: String = "abc" a = null // compilation error
如果您需要允许空值,例如保存 SQL 查询结果,您可以通过在类型后面附加一个问号来声明一个可为空的类型,例如 String?
.
var b: String? ="abc"b = null // ok
保护措施更进一步。 您可以使用不可为 null 的类型而不受惩罚,但您必须在使用它之前测试可空类型的 null 值。
为了避免 null 测试通常需要的冗长语法,Kotlin 引入了一个安全调用,写成 ?.
. 例如, b?.length
回报 b.length
如果 b
不是 null
, 和 null
否则。 这个表达式的类型是 Int?
.
换句话说, b?.length
是一个捷径 if (b != null) b.length else null
. 这种语法很好地链接起来,消除了相当多的冗长逻辑,尤其是当一个对象是从一系列数据库查询中填充时,其中任何一个都可能失败。 例如, bob?.department?.head?.name
如果 Bob、部门和部门负责人都为非空,将返回 Bob 的部门负责人的姓名。
要仅对非空值执行特定操作,可以使用安全调用运算符 ?.
和…一起 let
:
val listWithNulls: List<String?> = listOf("A", null) for (item in listWithNulls) { item?.let { println(it) } // prints A and ignores null }
通常,您希望从可空表达式返回一个有效但特殊的值,通常是为了将其保存为不可空类型。 有一种特殊的语法,称为 Elvis 运算符(我没骗你),写成 ?:
.
val l = b?.length ?: -1
相当于
val l: Int = if (b != null) b.length else -1
同样,Kotlin 省略了 Java 的已检查异常,这些异常是必须捕获的可抛出条件。 例如 JDK 签名
Appendable append(CharSequence csq) throws IOException;
需要你抓住 IOException
每次你打电话 append
方法:
try { log.append(message)}catch (IOException e) { // Do something with the exception}
Java 的设计者认为这是一个好主意,对于玩具程序来说这是一个净赢,只要程序员在 catch
条款。 然而,在大型 Java 程序中,您经常会看到强制性的代码 catch
子句仅包含评论: //todo: handle this
. 这对任何人都没有帮助,而且已检查的异常结果是大型程序的净损失。
Kotlin 协程
Kotlin 中的协程本质上是轻量级线程。 你开始他们 launch
某些上下文中的协程构建器 CoroutineScope
. 最有用的协程作用域之一是 runBlocking{}
,适用于其代码块的范围。
import kotlinx.coroutines.*fun main() = runBlocking { // this: CoroutineScope launch { // launch a new coroutine in the scope of runBlocking delay(1000L) // non-blocking delay for 1 second println("World!") } println("Hello,")}
此代码产生以下输出,每行之间有一秒的延迟:
Hello,World!
Android 版科特林
直到 2017 年 5 月,Android 官方唯一支持的编程语言是 Java 和 C++。 谷歌在 Google I/O 2017 上宣布正式支持 Android 上的 Kotlin,并从 Android Studio 3.0 开始将 Kotlin 内置到 Android 开发工具集中。 Kotlin 可以通过插件添加到早期版本的 Android Studio 中。
Kotlin 编译成与 Java 相同的字节码,以自然的方式与 Java 类进行互操作,并与 Java 共享其工具。 因为在 Kotlin 和 Java 之间来回调用没有开销,所以将 Kotlin 增量添加到当前使用 Java 的 Android 应用程序非常有意义。 Kotlin 和 Java 代码之间的互操作性缺乏优雅的少数情况,例如 Java set-only properties,很少遇到并且很容易修复。
早在 2016 年 11 月,Pinterest 就是用 Kotlin 编写的 Android 应用程序的典型代表,并且作为 Kotlin 公告的一部分在 Google I/O 2017 上被突出提及。 此外,Kotlin 团队喜欢引用适用于 Android 的 Evernote、Trello、Gradle、Corda、Spring 和 Coursera 应用程序。
Kotlin 与 Java
自从谷歌 I/O 宣布以来,Android 社区一直在讨论是选择 Kotlin 还是 Java 进行新开发的问题,尽管人们在 2016 年 2 月 Kotlin 1.0 发布时就已经在问这个问题了。 简短的回答是,Kotlin 代码比 Java 代码更安全、更简洁,而且 Kotlin 和 Java 文件可以在 Android 应用程序中共存,因此 Kotlin 不仅对新应用程序有用,而且对扩展现有的 Java 应用程序也很有用。
我看到的关于选择 Java 而不是 Kotlin 的唯一有说服力的论点是针对完整的 Android 开发新手的情况。 对于他们来说,鉴于历史上大多数 Android 文档和示例都是使用 Java 编写的,因此可能存在需要克服的障碍。 另一方面,在 Android Studio 中将 Java 转换为 Kotlin 只需将 Java 代码粘贴到 Kotlin 文件中即可。 在 2022 年,也就是 Kotlin 1.0 发布六年后,我不确定这个文档或示例障碍是否仍然存在。
对于几乎所有从事 Android 开发的人来说,Kotlin 的优势都是引人注目的。 Java 开发人员学习 Kotlin 的典型时间是几个小时——这是消除空引用错误、启用扩展功能、支持函数式编程和添加协程所付出的很小的代价。 典型的粗略估计表明,从 Java 到 Kotlin 的代码行数减少了大约 40%。
科特林与斯卡拉
在 Android 社区中,选择 Kotlin 还是 Scala 的问题并不常见。 如果您查看 GitHub(截至 2022 年 10 月)并搜索 Android 存储库,您会发现大约有 50,000 个使用 Java,24,000 个使用 Kotlin,以及(咳咳)73 个使用 Scala。 是的,用 Scala 编写 Android 应用程序是可能的,但很少有开发人员愿意这样做。
在其他环境中,情况有所不同。 例如,Apache Spark 大部分是用 Scala 编写的,Spark 的大数据应用程序通常是用 Scala 编写的。
在许多方面,Scala 和 Kotlin 都代表了面向对象编程(以 Java 为例)与函数式编程的融合。 这两种语言共享许多概念和符号,例如不可变声明使用 val
和可变声明使用 var
,但在其他方面略有不同,例如在声明 lambda 函数时将箭头放在哪里,以及是使用单箭头还是双箭头。 科特林 data class
映射到 Scala case class
.
Kotlin 以类似于 Groovy、C# 和 F# 的方式定义可空变量; 大多数人很快就明白了。 另一方面,Scala 使用 Option
monad,它可能是如此令人生畏,以至于一些作者似乎认为 Scala 没有空安全。
Scala 的一个明显缺陷是它的编译时间往往很长,当您从源代码构建大量 Scala 时,例如 Spark 存储库,这一点最为明显。 另一方面,Kotlin 旨在在最频繁的软件开发场景中进行快速编译,事实上,它的编译速度通常比 Java 代码快。