Java 的 Kotlin 特性愿望清单

毫无疑问,当谈到编程语言时,Java 已占领上风,并被认为是最重要的开发语言之一。然而,在基于 JVM 的基础上还开发了一些语言,比如说 Kotlin

Kotlin 对于现代化的多平台应用是一种静态类的编程语言。尽管我已经从事 Java 开发有相当长的时间,但是在做一个数据-匿名化的项目时使我感觉到 Java 需要从 Kotlin 引入一些东西。

下面是一些我愿意看到的在 Java 中占有一席之地的 Kotlin 特性。

提倡不可变

Java 9 引入了工厂方法创建集合,以此提倡不可变。但能在语言级别集成不可变,而非通过包装生成不可变集合将是非常不错的方式。existingDepartments() 是一个 Kotlin 的返回一个不可变字符串列表的函数。

//Kotlinfun existingDepartments(): List<String> = 
   listOf("Human Resources", "Learning & Development", "Research")

Java 9 在尝试添加、删除一个不可变列表中的元素时将会抛出一个 UnsupportedOperationException 异常。但如可变与不可变的接口完全隔离,避免给任何不可变集合暴露任何添加、删除的接口将是不错的。

//pre Java 8    public List<String> existingDepartments() {   return new ArrayList<String>(){{
            add("Human Resources");
            add("Learning & Development");
            add("Research");
        }};
}//Java 8public List<String> existingDepartments() {   return Stream.of("Human Resources", "Learning & Development", "Research")
               .collect(Collectors.toList());
}//Java 9public List<String> existingDepartments() {   return List.of("Human Resources", "Learning & Development", "Research");
}

清晰对待不可变集合,坚决反对暴露接口然后抛出 UnsupportedOperationExceptions 异常。

方法参数默认为 final

为提升不可变性及避免由于变化导致的错误,至少可以考虑使缺省的方法参数为 final 。

//Kotlinfun add (augend: Int, addend: Int) = augend + addend

add() 函数的参数是 val ,其默认是不能改变的,这意味着作为任何函数的客户端,我都可以放心:函数不会改变传递给它的参数(不要与 object mutation 混淆)。

使方法参数默认为 final 可能并且很大可能会破坏 Java 升级后的现存代码基础,但值得一提。

在编译时处理NULL

所有的java开发者一定知道臭名昭著的NullPointerException。Kotlin迈出了重要的一步在编译时处理NULLs。在显式声明之前,一切都是非空的。

Java 8不是出于同样的原因引入了Optional吗?让我们看一个例子:

//Kotlinclass Employee(private val id: Int, private val department: Department?) {
  fun departmentName() = department?.name ?: "Unassigned"}
class Department(val name: String)/**
    Employee needs a non-nullable "id" and an optional department to be constructed.
    val employee = Employee(null, null) => <b> Compile Time Error </b>
**/

这个Employee 类具有非空id和可选(可空)部门的主构造函数。为id传递null将导致编译时错误。

departmentName()函数使用可选操作符访问Department的name属性?在可空的字段上,如果department为null,name将会不被访问并且左手边的表达式[department?.name]将会返回null。三元操作符?:如果在表达式的左边为null将会返回右手边的 (“Unassigned”)。

//Java 8class Employee {    private Integer id;    private Optional<Department> department    Employee(Integer id, Optional<Department> department){        this.id = id;        this.department = department;
    }    public String departmentName() {        return department.orElse("Unassigned");
    }
}/**
    Employee needs a non-nullable "id" and an optional department to be constructed.
    Employee employee = new Employee(null, null); <b>NPE !!!</b>
**/

Optional不会保护NPE的代码,但是也有它的优点:

它使域模型变得清晰。Employee类有可选的department,这就足以得出结论,每个员工都可能没有被分配到一个部门。

它在departmentName()方法中促进可组合性。

在编译时处理NULLs应该可以通过删除if语句、对象的形式中的不必要的NULL检查来实现更干净的代码。Objects.requireNonNull, Preconditions.checkNotNull等其他任何形式(的不必要的NULL检查)。

为了保持简单,department被传递给构造函数,尽管这是一个可选属性。

改进Lambda表达式

Java 8引入的lambda表达式是建立在函数式接口和函数描述符的基础上的,这就意味着每一个lambda表达式都会映射到一个定义在函数式接口中的抽象方法。这是一种高效的方法,他授权给一个只有一个抽象方法(即函数描述符)的接口(即函数式接口)可以创建一个lambda表达式。

//Kotlinval isPositive: (Int) -> Boolean = { it > 0 }
OR,
val isPositive: (Int) -> Boolean = { num > 0 }
OR,
val isPositive: (Int) -> Boolean = { num: Int > 0 }//UsageisPositive(10) returns trueisPositive(-1) returns false

上述代码中,变量isPositive就是一个函数,它接受一个整形变量为参数并且返回一个布尔类型值。这个变量的值就是一个函数的定义或者是定义在花括号中的一个lambda表达式,这个函数可以检查传进来的参数是否大于零。

然而,在下面的代码中,Predicate是包含一个抽象函数test()的函数式接口——他可以接受一个类型为T的参数并返回一个布尔类型值。

因此,isPositive可以接受一个整型变量作为参数并检查这个参数是否大于零。所以我们需要在使用isPositive时调用test()方法。

//Java 8
private Predicate<Integer> isPositive = (Integer arg) -> arg > 0;
//Usage
isPositive.test(10) returns true
isPositive.test(-1) returns false
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

lambda表达式不应该依赖于函数式接口和其中的函数描述符。

支持扩展函数

Kotlin 支持扩展函数,以在无需继承或装饰其他类的情况下给函数提供新功能。

下是一个扩展函数的案例,返回 String 最后的一个字符。

//Kotlinfun String.lastChar() = this.toCharArray()[this.length - 1]/**
    Extension functions are of the form -
    fun <ReceiverObject>.function_name() = body
    OR,
    fun <ReceiverObject>.function_name(arg1: Type1, ... argN: TypeN) = body
**/

lastChar() 是定义在 String 类上的一个拓展函数,这里 String 也被称为接收对象。通过 “Kotlin”.lastChar() 来触发函数。

拓展函数在没有继承和任何设计模式的情况下,可以拓展函数新特性。

尾部递归

Kotlin 支持 Tail-recursion(尾部递归)。尾部递归是递归的一种形式,其递归调用的是函数尾部最后的一条指令。这样一来,我们无须担心之前的值,一个栈桢满足所有的递归调用;尾部递归是优化递归算法的一种方式。

另外,尾部递归可以很容易的转换为迭代的方式。

//Kotlinfun factorialTco(val: Int): Int {
    tailrec fun factorial(n: Int, acc: Int): Int = 
         if ( n == 1 ) acc else factorial(n - 1, acc * n)  return  factorial(val, acc = 1)
}

当函数用 tailrec 修饰符标识并满足所需的方式时,编译器会对递归进行优化,并以快速高效的递归版本替换。

实际而言,尾部递归以固定的栈桢空间执行,它只是迭代过程的另一种表达式。

Java 并不在编译器级别直接支持尾部调用优化,不过可以通过 lambda 表达式实现,但是期待能在编译层面看到尾部递归算法。

其他

  • 移除固有的复制 [new,return,semicolon]:Kotlin 不需要 new 来创建一个实例。如果函数被视为语句而不是表达式,它仍然需要 return
//Kotlinclass Employee(private val id: Int, private val department: Department?) {    //no return
    fun departmentNameWithoutReturn() = department?.name ?: "Unassigned"
    //return is needed if a function is treated as a statmentrather than an expression
    fun departmentNameWithoutReturn() {
        val departmentName = department?.name ?: "Unassigned"
        return departmentName
    }
}
  • 单例类:在 Java 中创建单例类如果有更简单的方法将会很棒。Kotlin 中的等效语法如下所示。
//Kotlinobject DataProviderManager {
fun registerDataProvider(provider: DataProvider) {        // ...
    }
}
  • 不可变类: 如果能看到类似 readonly/immutable 修饰符来创建一个不可变类那真是极好的。下面提到的代码片段仅是一个想法(在 Kotlin 或 Java 中不可用)。
//Hypothetical [Not available so far]immutable class User(private val name: String, private val id: Int)

总之,作为开发人员,我们总会犯错误(漏掉 NULL 检查、改动集合数据等),但在语言级别上提供这些功能将使编程更轻松并尽可能避免出现错误。

本文文字及图片出自 OSchina

余下全文(1/3)
分享这篇文章:

请关注我们:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注