java是什么玩意(从 Kotlin 开发者的角度来看,Java 缺少了什么?5年前,辞职写下“世界那么大,我想去看看”的老师,现状如何?)

wufei123 发布于 2023-05-10 阅读(709)

作者 | 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关键字,而很少有人这么做Null安全性在Java中,没有办法知道某个变量是否为null为了明确这一点,Java 8引入了Optional类型。

从Java 8以后,返回一个Optional意味着底层的值可能为null,而返回其他类型意味着不可能为null但是,Optional的开发者只用null作为返回值而方法参数和返回值在Null安全性方面并没有得到语法层面上的支持。

为了解决这个问题,许多库提供了编译时注释:

显然,一些库只能用于特定的IDE更糟糕的是,这些库之间很难相互兼容所以很多人都在Stack Overflow上问,这么多的库应该使用哪个最后,开发者必须主动使用支持Null安全性的库相反,Kotlin要求每个类型都必须是允许null或不允许null。

val nonNullable: String = computeNonNullableString()val nullable: String? = computeNullableString()扩展函数

在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 persons) {}int compute(List persons) {}}另一个问题是如何从值的容器中获取有类型的值下面是Spring中的一个例子:。

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

开发者添加了一个 Class ,以便在方法体中获知类型如果Java有真实泛型,只需像下面这样处理即可:public interface BeanFactory { T getBean();}想象一下,如果Kotlin有真实泛型,我们可以改变上述设计:。

interface BeanFactory {fun getBean(): T}函数调用可以改成:val factory = getBeanFactory()val anyBean = factory.getBean() ①

① 真实泛型!Kotlin依然需要遵守JVM规范,生成与Java编译器的字节码兼容的字节码但它可以通过“内联”的方式实现,即编译器用函数体替换内联函数调用下面是Kotlin代码:org/springframework/beans/factory/BeanFactoryExtensions.ktinline fun 。

总结本文介绍了四个我希望Java也有的Kotlin功能:不可变引用、null安全性、扩展函数,以及真实泛型Kotlin还有许多其他很好的功能,但这四个功能就足以提升Java例如,有了扩展函数和真是繁星,再加上一些语法糖,就可以很轻松地编写DSL,就像Kotlin Routes和Beans DSL一样:。

beans {bean {router {GET("/hello") { ServerResponse.ok().body("Hello world!") }}}}别误会:我知道Java作为一种语言,发展时需要考虑很多因素,而Kotlin的包袱更轻。

但是,有竞争是好事,两者可以互相学习同时,我只在必要时才会编写Java,因为Kotlin已成为了我的JVM首选原文地址:https://blog.frankel.ch/miss-in-java-kotlin-developer/。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。