Kotlin是由IntelliJ IDE的开发者Jetbrains构建的静态类型JVM语言。 Kotlin基于Java构建,并提供有用的功能,例如空安全性,数据类,扩展,功能概念,智能强制转换,运算符重载等。
为什么我会关心Kotlin?
使我特别对Kotlin感兴趣的是,它可以与Java进行超级互操作,并得到Jetbrains及其流行的Java IDE IntelliJ的支持。 为什么这让我对Kotlin更加感兴趣?
嗯,与Java的互操作性非常重要,因为Java相当长一段时间以来一直是使用最广泛的编程语言之一。 从业务角度看,这意味着大多数现实世界代码都是用Java编写的,并且公司希望尽可能长地维护其Java代码库-通常,旧代码的价值为数百万美元。
使用Kotlin,组织可以以最小的风险尝试一种新的编程语言。 Java文件可以转换为等效的Kotlin文件,然后可以对其进行处理。 同样,可以在Java内部使用Kotlin中定义的所有类型(如类和枚举),就像其他任何Java类型一样。 从开发人员的角度来看,能够使用您习惯的Java库是很棒的。 您可以直接从Kotlin使用Java IO,JavaFX,Apache Commons,Guava和所有您自己的类。
同样,夸张地说, 编程语言仅与其工具支持一样好 。 这就是为什么对我来说支持Kotlin的第二点是IntelliJ提供内置语言支持。 它还包含上述的Java-to-Kotlin转换器以及Kotlin代码中的Java和JavaScript代码生成器。
这两点也将Kotlin与其他JVM语言(例如Scala,Ceylon,Clojure或Groovy)分开。
好吧,足够多的谈话。 让我们进入Kotlin的实际语言功能。
空安全
class Person {
val givenName: String = ""
val familyName: String = ""
val address: Address? = null
}
在此示例中,giveName和familyName不能为null-程序在编译时将失败 。 您必须明确地使变量为空,以便能够为其分配null。 这是通过“?”完成的 在变量类型之后。 因此,地址属性在给定代码中可能为null。
每当可能在运行时引发NullPointerException时,也就是说,当您尝试从可空类型调用方法或引用属性时,Kotlin在编译时也会失败:
val givenName: String? = null
val len = givenName.length
如果尝试对此进行编译,则Kotlin编译器会给您一个错误:“在kotlin.String类型的可为空的接收器上,仅允许安全(?。)或非null断言(!!。)调用”。 我们将看到如何处理这些情况,即您知道该变量在一秒钟内不能为null。
到目前为止,一切都很好,但是编译器正在谈论的那些安全且非null断言的调用是什么? 如果被叫方不为空,则安全调用仅照常返回该调用的值,否则返回null:
val givenName: String? = ""
val len = givenName?.length
在这种情况下,len将为零。 如果namedName为null,则len也将被分配为null。 因此,给定Name..length的返回类型为Int ?,它是一个可为空的整数。
使用非null的断言调用,您可以向编译器断言,您知道该变量在运行时在使用它的位置不能为null:
val givenName: String? = "Roger"
val len = givenName!!.length
为了有效处理可空类型, Elvis运算符非常有用。 如果不为null,则允许您使用nullable;否则,可以使用默认值:
val text: String? = null
val len = text?.length ?: -1
在此示例中,len将为-1,因为可为空的文本实际上为null,因此将使用定义的默认值。 您可能已经注意到,这基本上只是广为人知的三元运算符 ,其中第一个操作数等于表达式本身:
val len = text?.length ?: -1
val len = text?.length ? text?.length : -1
这两行在语义上是相同的。
资料类别
对于主要保存数据的简单类,与Java代码相比,可以避免很多样板。 考虑以下Java中的典型数据类:
class Book {
private String title;
private Author author;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}
试图找出类的实际作用时,您将跳过许多样板代码。 在Kotlin中,您可以在一行中简洁地定义相同的类:
data class Book(var title: String, var author: Author)
Kotlin还将生成有用的hashCode(),equals()和toString()实现。 打印一本书将创建类似Book(title = Effective Java,author = Author(name = Joshua Bloch))的输出。
挑战 : 编写一个Author类 ,它将导致这样的输出!
不仅如此,它还使您可以轻松制作数据类的副本:
val book = Book("Effective Java", Author("Joshua Bloch"))
val copy = book.copy()
val puzzlers = book.copy(title = "Java Puzzlers")
val gof = book.copy(title = "Design Patterns", author = Author("Gang of Four"))
您可以通过将命名参数添加到copy()方法来更改复制对象的任意属性。
另一个好处是可以在数据类上使用解构声明 ,以检索相应的属性值:
val book = Book("The Phoenix Project", Author("Kevin Behr"))
val (title, author) = book
3)扩展功能
Kotlin允许我们扩展现有类的功能而无需继承它们 。 这由扩展功能和扩展属性启用。 假设您想使用一种方法来从JavaFX GUI框架扩展GridPane类,以检索第i行和第j列中的元素:
fun GridPane.getElementAt(rowIndex: Int, columnIndex: Int): Node? {
this.children.forEach {
if (GridPane.getColumnIndex(it) == columnIndex && GridPane.getRowIndex(it) == rowIndex) {
return it;
}
}
return null;
}
这里有几件事要提到。 首先,在扩展函数内部,您可以使用“ this”引用在其上被调用的对象。 其次,在子节点列表上使用Kotlin的forEach()函数。 这等效于Java 8中包含的forEach()方法,并允许进行某些功能样式的编程。 第三,在forEach()内部,您可以使用隐式循环变量“ it”引用当前元素。
请注意,返回类型位于包含参数的括号之后,并且是可空节点,因为该方法可能返回空值。
有一件事情要注意:如果您尝试使用也适用于该类内部现有成员函数的参数调用扩展函数,则该成员将始终“获胜”,这意味着它将优先处理并掩盖您的扩展功能。
4)Smart Casts
您已经多少次在实际上是多余的地方投射对象了? 这样打赌的次数超出了您的想象,就像这样:
if (node instanceof Leaf) {
return ((Leaf) node).symbol;
}
另一方面,在转换时,Kotlin编译器确实很聪明。 含义: 它将为您处理所有这些多余的转换 。 这就是所谓的聪明型演员 。
上面的代码段的等效Kotlin代码如下所示:
if (node is Leaf) {
return node.symbol;
}
Kotlin中的instanceof运算符称为“是”。 而且,更重要的是,无需使用编译器实际上可以处理的强制转换来使代码混乱。
现在,这不仅限于此简单案例:
if (person !is Student)
return
person.immatriculationNumber
在这种情况下,如果某人不是学生,则控制流将永远不会到达第4行。因此,Kotlin编译器知道该人必须是Student对象,并执行智能转换。
让我们看一下一些延迟计算的条件表达式 :
if (document is Payable && document.pay()) { // Smart cast
println("Payable document ${document.title} was payed for.")
}
像Java这样的条件条件在Kotlin中使用惰性求值。 因此,如果单据不是应付账款,则第二部分将不会首先进行评估。 因此,如果经过评估,Kotlin知道该单据是应付账款,并使用智能投稿。
析取也是如此:
if (document !is Payable || document.pay() == false) { // Smart cast
println("Cannot pay document ${document.title}.")
}
当表达式是Kotlin将在任何可能的地方应用智能转换的另一个地方:
val result = when (expr) {
is Expr.Number -> expr.value
is Expr.Sum -> expr.first + expr.second
is Expr.Difference -> expr.first - expr.second
is Expr.Exp -> Math.pow(expr.base, expr.exponent)
}
根据对象的类型,您可以简单地在每个when块中使用各自的属性。
注意:对于上面的示例,Expr类必须是仅包含这四个确切子类的密封类。
5)类型推断
在Kotlin中,即使Kotlin 是强类型的,也不必显式指定每个变量的类型 。 您可以选择显式定义数据类型,例如,如果您不希望将一个小整数存储在Int变量中,而是存储一个Short甚至是一个Byte。 您可以使用冒号表示,其中数据类型位于变量名的后面:
val list: Iterable = arrayListOf(1.0, 0.0, 3.1415, 2.718) // Only need Iterable interface
val arrayList = arrayListOf("Kotlin", "Scala", "Groovy") // Type is ArrayList
您可以选择像Java中那样使用显式类型,但也可以自由编写更简洁的类似Python的变量声明。 显式类型对于引用最常规的接口很有用(您应该始终这样做)。
6)函数式编程
自Java 8以来,Java演变为合并了几个函数式编程概念,而Kotlin则直接引入了函数式编程。这包括高阶函数,lambda表达式,运算符重载,惰性计算以及许多有用的用于集合的方法。
lambda表达式和Kotlin库的结合确实使您在使用集合时更加轻松:
val numbers = arrayListOf(-42, 17, 13, -9, 12)
val nonNegative = numbers.filter { it >= 0 }
println(nonNegative)
请注意,当将lambda表达式与单个参数一起使用时,Kotlin会创建一个称为“ it”的隐式变量,该变量引用lambda表达式的唯一参数。 因此,上面的第二行等效于:
val nonNegative = numbers.filter { it -> it >= 0 }
Kotlin提供了所有必要的功能设施,如过滤器,地图和flatMap,采取与降,第一和最后一个,折叠和foldRight,的forEach,减少和别的务实的功能程序员的心脏渴望:
println(numbers.take(2)) // First two elements: [-42, 17]
println(numbers.drop(2)) // List without first two elements: [13, -9, 12]
println(numbers.foldRight(0, { a, b -> a + b })) // Sum of all elements: -9
numbers.forEach { print("${it * 2} ") } // -84 34 26 -18 24
---
val genres = listOf("Action", "Comedy", "Thriller")
val myKindOfMovies: Iterable<String> = genres.filter { it.length <= 6 }.map { it + " Movie" }
println(myKindOfMovies) // [Action Movie, Comedy Movie]
7)对象(又名轻松创建单例)
Kotlin有一个名为object的关键字,它使我们可以定义一个类似于类的对象。 但是,当然,该对象仅作为单个实例存在。 这是创建单例的有用方法,但该功能不仅限于单例。
创建对象是如此简单:
object CardFactory {
fun getCard(): Card {
// ...
}
}
然后,您可以将该对象像具有静态成员的类一样使用:
fun main(args: Array<String>) {
val card = CardFactory.getCard()
}
您甚至可以让您的对象具有超类 :
object SubmitButtonListener : ActionListener {
override fun actionPerformed(e: ActionEvent?) {
// Submit form...
}
}
这个概念是Java中可用类,接口和枚举的强大扩展,因为域模型的元素通常是固有的对象(它们仅存在一次,因此在运行时最多应具有实例)。
8)默认参数
缺省参数是我在Java中非常缺少的一项功能,因为它非常方便,它使您的代码更简洁,更具表现力,更可维护,更具可读性,并且提供了更多的东西-很好。
在Java中,通常必须重复代码才能定义方法或构造函数的不同变体 。 看看这个:
public class NutritionFacts {
private final String foodName;
private final int calories;
private final int protein;
private final int carbohydrates;
private final int fat;
private final String description;
public NutritionFacts(String foodName, int calories) {
this.foodName = foodName;
this.calories = calories;
this.protein = -1;
this.carbohydrates = -1;
this.fat = -1;
this.description = "";
}
public NutritionFacts(String foodName, int calories, int protein, int carbohydrates, int fat) {
this.foodName = foodName;
this.calories = calories;
this.protein = protein;
this.carbohydrates = carbohydrates;
this.fat = fat;
this.description = "";
}
public NutritionFacts(String foodName, int calories, int protein, int carbohydrates, int fat, String description) {
this.foodName = foodName;
this.calories = calories;
this.protein = protein;
this.carbohydrates = carbohydrates;
this.fat = fat;
this.description = description;
}
}
gh,非常糟糕。 但是,如果您想提供更多不同的构造函数或添加更多可能的属性,则情况甚至更糟。
上面的示例演示了Java中普遍存在的所谓伸缩反模式 。 您可以改用Builder模式来改进此设计。
但是,使用Kotlin,您可以使用默认值作为参数来简化此操作:
class NutritionFacts(val foodName: String,
val calories: Int,
val protein: Int = 0,
val carbohydrates: Int = 0,
val fat: Int = 0,
val description: String = "") {
}
这使具有默认值的每个参数成为可选参数 。 与上面的Java类相比,它实际上为您提供了更多的调用构造函数的可能性:
val pizza = NutritionFacts("Pizza", 442, 12, 27, 24, "Developer's best friend")
val pasta = NutritionFacts("Pasta", 371, 14, 25, 11)
val noodleSoup = NutritionFacts("Noodle Soup", 210)
请注意,您可能还想使此类成为数据类 ,以为您生成诸如equals()和toString()之类的方法。
简而言之:您花更少的钱就能得到更多。 并同时保持代码干净。
9)命名参数
结合命名参数,默认参数变得更加强大:
val burger = NutritionFacts("Hamburger", calories = 541, fat = 33, protein = 14)
val rice = NutritionFacts("Rice", 312, carbohydrates = 23, description = "Tasty, nutritious grains")
任何阅读代码的人都知道发生了什么,而不必查看参数的含义。 这可以提高可读性,并在正确使用时使您的工作效率更高。 例如,当您具有多个布尔参数时,这特别有用:
myString.transform(true, false, false, true, false)
除非你已经实现该功能10秒前,有没有办法,你知道是怎么回事(没有你甚至知道你是否 10秒前实现它的担保)。
通过使用命名参数使您(以及您的其他开发人员)的生活更轻松:
myString.transform(
toLowerCase = true,
toUpperCase = false,
toCamelCase = false,
ellipse = true,
normalizeSpacing = false
)
10)奖金:执行最佳做法
通常,Kotlin会强制您使用Java时应遵循的许多最佳实践。 您可以在Josh Bloch的书“ Effective Java”中阅读有关它们的信息。
首先,使用val vs. var可以促进使每个不应更改的变量final改变-同时还为其提供更简洁的语法。 例如,这在创建不可变对象时很有用。
这样,学习该语言的初学者也会从一开始就学习遵循这种做法,因为您倾向于每次都考虑使用val还是var,并且在可能的情况下学会使用val而不是var。
接下来,Kotlin还支持为继承进行设计或禁止继承的原则-因为在Kotlin中,必须从继承中显式声明一个类为open 。 这样,您必须记住允许继承,而不必记住禁止继承。
现在怎么办?
如果此概述使您想了解更多有关Kotlin的信息,则可以查看我的10篇有关Kotlin的入门教程视频,或直接进入完整课程 (以95%的读者折扣) 。
本课程对初学者友好,完全从头开始。 如果您已经知道Java或类似的语言,您仍然会发现它是了解Kotlin的宝贵资源。
翻译自: https://www.javacodegeeks.com/2016/05/kotlin-java-developers-10-features-will-love-kotlin.html