龙空技术网

从 Kotlin 开发者的角度来看,Java 缺少了什么?

CSDN 266

前言:

此时兄弟们对“缺省java”可能比较讲究,咱们都想要剖析一些“缺省java”的相关资讯。那么小编在网摘上网罗了一些关于“缺省java””的相关内容,希望兄弟们能喜欢,看官们一起来学习一下吧!

作者 | Nicolas Fränkel译者 | 弯月
出品 | CSDN(ID:CSDNnews)

我使用Java已近二十年了。几年前,我开始学习Kotlin。

虽然Kotlin也会编译成JVM字节码,但有时候我还是要写Java。每当这时,我就会想,为什么Java代码不能像Kotlin那样漂亮。Java缺少一些关键特性,因此代码的可读性、表达性和可维护性都差强人意。

这篇文章并不是要攻击Java,只是列出了一些我希望Java拥有的功能。

不可变引用

Java有不可变引用:

类的属性

方法的参数

局部变量

class Foo { final Object bar = new Object(); ① void baz(final Object qux) { ②final var corge = new Object(); ③}}

① 不能给bar重新赋值

② 不能给qux重新赋值

③ 不能给corge重新赋值

不可变引用非常有利于避免尴尬的bug。有意思的是,final关键字并没有被广泛使用,即使是广为人知的项目也并没有使用太多final。例如,Spring的GenericBean使用了不可变属性,但没有使用不可变方法参数,也没有使用不可变局部变量;slf4j的DefaultLoggingEventBuilder没有使用上述任何一种。

尽管Java允许定义不可变引用,但并没有强制要求。默认情况下,引用是可变的。大部分Java代码都没有采用不可变引用。

而Kotlin并没有给你选择:每个属性和局部变量都要定义为val或var。而且,方法参数不能重新赋值。

Java的var关键字则有很大的不同。首先,它只能用于局部变量。更重要的是,Java并没有相应的不可变关键字val。你依然需要使用final关键字,而很少有人这么做。

安全性

在Java中,没有办法知道某个变量是否为。为了明确这一点,Java 8引入了Optional类型。从Java 8以后,返回一个Optional意味着底层的值可能为,而返回其他类型意味着不可能为。

但是,Optional的开发者只用作为返回值。而方法参数和返回值在安全性方面并没有得到语法层面上的支持。为了解决这个问题,许多库提供了编译时注释:

显然,一些库只能用于特定的IDE。更糟糕的是,这些库之间很难相互兼容。所以很多人都在Stack Overflow上问,这么多的库应该使用哪个。

最后,开发者必须主动使用支持安全性的库。相反,Kotlin要求每个类型都必须是允许或不允许。

val nonable: String = computeNonableString()val able: String? = computeableString()

扩展函数

在Java中,扩展类的方法是编写子类:

class Foo {}class Bar extends Bar {}

子类有两个主要问题。首先,标记了final的类不允许继承。许多广泛应用的JDK类都是final的,比如String。其次,如果一个不属于方法返回了某个类型,那么就只能返回那个类型,不论其行为是否符合你的要求。

为了解决这个问题,Java开发者发明了工具类的概念,例如类型XYZ的工具类通常写作XYZUtils。工具类就是一堆static方法的集合,并且构造函数是private的,因此无法创建示例。这就相当于一个命名空间,因为Java不允许在类外创建方法。

这样,如果一个类型不包含某个方法,那么工具类可以提供该方法,接受类型作为一个参数,并执行指定的行为。

class StringUtils { ① private StringUtils() {} ② static String capitalize(String string) { ③return string.substring(0, 1).toUpperCase()+ string.substring(1); ④}}String string = randomString(); ⑤String capitalizedString = StringUtils.capitalize(string); ⑥

① 工具类

② 防止工具类实例化

③ static方法

④ 一个简单的大写函数,没有考虑边界情况

⑤ String类型没有提供大写功能

⑥ 使用工具类来实现该功能

而Kotlin提供了扩展函数功能来解决这个问题。

Kotlin提供了一种方法,可以扩展类或接口,而无需从类进行集成,也无需使用诸如修饰器等设计模式。只需通过一种叫做“扩展”的特殊定义来实现。

例如,你可以给一个来自第三方库的类或接口编写新的函数,即使你无法修改该库。这种函数可以正常调用,就像它本来就属于该类一样。这种机制叫做“函数扩展”。

要定义函数扩展,只需在其名称前加上一个接收者类型,指示被扩展的类。

有了函数扩展,上述代码就可以写成:

fun String.capitalize2(): String { ①②return substring(0, 1).uppercase() + substring(1);}val string = randomString()val capitalizedString = string.capitalize2() ③

① 孤立的函数,不需要类封装。

② Kotlin的stdlib已经有了capitalize()函数。

③ 就像调用String自带的函数一样调用扩展函数。

注意扩展函数会被“静态地”解析。扩展函数并不会给已有类型添加新的行为,只是假装而已。它们生成的字节码非常类似于Java的静态方法。但是其语法要简洁得多,而且支持函数链式调用,这在Java中时无法做到的。

真实泛型

Java版本5加入了泛型支持。但是,语言设计师太执着于向下兼容性,Java 5的字节码必须能与Java 5之前的字节码完全兼容。这就是为什么生成的字节码中不包含泛型的原因。这种方式称为“泛型擦除”。与之相对的叫做“真实泛型”(reified generics),即泛型会出现在字节码中。

仅在编译期间采用泛型,会导致一系列问题。例如,下面的方法签名会生成完全相同的字节码,因此这段代码是不正确的:

class Bag {int compute(List<Foo> persons) {}int compute(List<Bar> persons) {}}

另一个问题是如何从值的容器中获取有类型的值。下面是Spring中的一个例子:

org/springframework/beans/factory/BeanFactory.javapublic interface BeanFactory {<T> T getBean(Class<T> requiredType);}

开发者添加了一个 Class<T> ,以便在方法体中获知类型。如果Java有真实泛型,只需像下面这样处理即可:

public interface BeanFactory {<T> T getBean();}

想象一下,如果Kotlin有真实泛型,我们可以改变上述设计:

interface BeanFactory {fun <T> getBean(): T}

函数调用可以改成:

val factory = getBeanFactory()val anyBean = factory.getBean<Any>() ①

① 真实泛型!

Kotlin依然需要遵守JVM规范,生成与Java编译器的字节码兼容的字节码。但它可以通过“内联”的方式实现,即编译器用函数体替换内联函数调用。

下面是Kotlin代码:

org/springframework/beans/factory/BeanFactoryExtensions.ktinline fun <reified T : Any> BeanFactory.getBean(): T = getBean(T::class.java)

总结

本文介绍了四个我希望Java也有的Kotlin功能:不可变引用、安全性、扩展函数,以及真实泛型。Kotlin还有许多其他很好的功能,但这四个功能就足以提升Java。

例如,有了扩展函数和真是繁星,再加上一些语法糖,就可以很轻松地编写DSL,就像Kotlin Routes和Beans DSL一样:

beans {bean {router {GET("/hello") { ServerResponse.ok().body("Hello world!") }}}}

别误会:我知道Java作为一种语言,发展时需要考虑很多因素,而Kotlin的包袱更轻。但是,有竞争是好事,两者可以互相学习。

同时,我只在必要时才会编写Java,因为Kotlin已成为了我的JVM首选。

原文地址:

标签: #缺省java