2025上半年,最新 AI实践都在这!20+ 应用案例,任听一场议题就值回票价 了解详情
写点什么

从 Kotlin 开发者角度看 Java 缺失的特性

  • 2022-06-17
  • 本文字数:2851 字

    阅读完需:约 9 分钟

从Kotlin开发者角度看Java缺失的特性

近二十年来,Java 一直是我的谋生工具,直到几年前我开始学习 Kotlin。


虽然 Kotlin 也被编译为 JVM 字节码,但有时候我还是不得不写一些Java代码。每次写 Java 代码时,我都不禁想,为什么 Java 代码看起来没有 Kotlin 那么好。我很想念那些可以提高代码可读性、表现力和可维护性的特性。


这篇文章并不是要抨击 Java,而是要列出一些我希望也能在 Java 中找到的特性。

不可变引用


Java 从一开始就有不可变引用:


  • 类的属性;

  • 方法的参数;

  • 局部变量。


class Foo {
final Object bar = new Object();
void baz(final Object qux) { final var corge = new Object(); }}
复制代码


  1. bar 不能重新赋值;

  2. qux 不能重新赋值;

  3. corge 不能重新赋值。


不可变引用在避免恶心的 Bug 方面起到很大作用。有趣的是,对 final 关键字的使用并不是很普遍,即使是在流行的项目中也是如此。例如,Spring 的 GenericBean 使用了不可变属性,但没有使用不可变方法参数或局部变量;slf4j 的 DefaultLoggingEventBuilder 没有使用这三个东西。


Java 允许你定义不可变引用,但不是强制的。默认情况下,引用是可变的。大多数 Java 代码没有使用不可变引用。


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


Java 中的 var 关键字完全不同。首先,它只能用于局部变量。更重要的是,它没有提供与之对应的不可变的 val 关键字,你仍然需要添加 final 关键字,但几乎没有人使用它。

空安全(Null Safety)


在 Java 中,我们无法知道变量是否为空。为此,Java 8 引入了 Optional 类型。从 Java 8 开始,如果返回 Optional 意味着实际的值可以为 null,如果返回其他类型则意味着值不能为 null。


但是,Optional 只针对返回值,不能用于方法的参数。为了解决这个问题,一些库提供了编译时注解:



显然,有些主要针对特定的 IDE。此外,库之间很难兼容。因为库太多了,以至于有人在 StackOverflow 上问该使用哪一个。这些现象很能说明问题。


是否使用这些库是可选择的,而在 Kotlin 中,每种类型要么为空,要么为非空。


val nonNullable: String = computeNonNullableString()val nullable: String? = computeNullableString()
复制代码

扩展函数


在 Java 中,扩展一个类是通过继承来实现的:


继承类有两个主要问题。第一个问题是有些类不允许继承:它们使用了 final 修饰符。有几个被广泛使用的 JDK 类就是 final 类,例如 String。第二个问题是,如果我们无法控制的方法返回了一个类型,那么不管它是否包含我们想要的行为,都只能使用这个类型。


为了解决上述问题,Java 开发者发明了辅助类的概念,比如 XYZ 类对应的辅助类叫作 XYZUtils。辅助类提供了一系列静态方法,并带有私有构造函数,因此不能被实例化。这是不得已而为之,因为 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);
复制代码


  1. 辅助类;

  2. 防止实例化这个类;

  3. 静态方法;

  4. 简单的首字母大写转换,不考虑极端情况;

  5. String 类型不提供首字母大写转换函数;

  6. 使用辅助类来实现这种行为。


之前,开发人员需要在项目内部创建这样的类。现在,Java 生态系统提供了开源库,如 Apache Commons Lang 或 Guava。所以不要重新发明轮子了!


Kotlin 提供了扩展函数来解决同样的问题。


Kotlin 提供了不通过类继承或使用装饰器等设计模式来实现扩展类或接口的能力。这可以通过一种叫作扩展的特殊声明来实现。


例如,你可以为你无法修改的第三方库中的类或接口添加新函数。这些函数可以按照通常的方式进行调用,就好像它们就是原始类的方法一样。这种机制叫作扩展函数。


要声明扩展函数,需要用被扩展的类名作为前缀。


有了扩展函数,可以将上面的代码重写为:


fun String.capitalize2(): String {                                return substring(0, 1).uppercase() + substring(1);}
val string = randomString()val capitalizedString = string.capitalize2()
复制代码


  1. 自由的函数,不需要类;

  2. Kotlin 的标准库中已经有 capitalize()函数;

  3. 调用扩展函数,就好像它属于 String 类一样。


需要注意的是,扩展函数是“静态”解析的。它们不会在现有的类上添加新的行为,只是假装会这样。生成的字节码与 Java 静态方法非常相似。它的语法要清晰得多,并且允许函数链接,这在 Java 中是不可能做到的。

具体化的泛型


Java 5 中引入了泛型。然而,语言设计者热衷于保持向后兼容性:Java 5 的字节码需要与 Java 5 之前的字节码完美地交互。这就是为什么泛型类型没有被写入生成的字节码中:这就是所谓的类型擦除。与之相反的是具体化的泛型,也就是说,泛型类型将被写入字节码中。


编译时泛型类型存在一些问题。例如,下面的方法签名将生成相同的字节码,因此,这些代码是无效的:


class Bag {    int compute(List<Foo> persons) {}    int compute(List<Bar> persons) {}}
复制代码


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

public 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>()   
复制代码


  1. 具体化的泛型。


Kotlin 仍然需要遵循 JVM 规范,并与 Java 编译器生成的字节码兼容。它可以通过内联来实现:编译器用函数体替换内联的方法调用。


下面是 Kotlin 代码示例:


inline fun <reified T : Any> BeanFactory.getBean(): T = getBean(T::class.java)
复制代码

结论


在这篇文章中,我描述了 Java 中缺失的 4 个 Kotlin 特性:不可变引用、空安全、扩展函数和具体化泛型。虽然 Kotlin 也提供了其他很棒的特性,但这 4 个对于 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/

2022-06-17 16:512531
用户头像
李冬梅 加V:busulishang4668

发布了 1086 篇内容, 共 704.6 次阅读, 收获喜欢 1242 次。

关注

评论

发布
暂无评论
发现更多内容

南阳蓝天燃气携手WeLink共创数字蓝天

科技云未来

Nacos 安装教程(史上最详细保姆级教程)

nacos SpringCloud 9月月更

maven入门

楠羽

maven 笔记 9月月更

新书上市 | 连载 5 年,千万读者追更,这本书讲透了通信背后的故事!

图灵社区

通信技术 科技史

# 靠谱:开源IM项目OpenIM压测程序介绍-自己动手压测性能和稳定性

Geek_1ef48b

k8s自定义controller三部曲之一:创建CRD(Custom Resource Definition)

程序员欣宸

Kubernetes k8s 9月月更

关于链路追踪所需要了解的知识

穿过生命散发芬芳

链路追踪 9月月更

博睿数据携手亚马逊云科技,助您开启全链路可观测之旅

博睿数据

可观测性 智能运维 博睿数据 全链路 亚马逊云科技

构筑校园  “云资环”助力精准防控

科技云未来

从零到一,教你搭建「CLIP 以文搜图」搜索服务(二):5 分钟实现原型原创

Zilliz

机器学习 深度学习 搜索引擎

从用户到开发者是一种思维进化过程 | 访 StarRocks Committer 周威

StarRocks

虚拟机内存管理之内存分配器

字节跳动终端技术

vm 内存 虚拟机 内存管理 内存分配

从任正非的内部信,看系统开发公司如何度过寒冬

CRMEB

基于 xbot 实现微信关键词自动回复

Hanson

微信 微信机器人 自动回复

测试需求平台3-登录打通和产品列表功能实现

MegaQi

测试平台开发教程 9月月更

设计模式的艺术 第二十章中介者模式练习(设计一套图形界面类库,包含若干预定义的窗格(Pane)对象,如TextPane、ListPane等,窗格之间不允许直接引用。基于该类库的应用由一个包含一组窗格的窗口(Window)组成,窗口协调窗格之间的行为)

代廉洁

设计模式的艺术

一文看懂Mysql锁

六月的雨在InfoQ

MySQL MySQL锁 9月月更 Mysql死锁 Mysql锁粒度

15款Python编辑器,你都使用过哪一款

千锋IT教育

万物皆可集成系列:低代码对接企企云实现数据集成

葡萄城技术团队

YOLOX-PAI:加速YOLOX,比YOLOV6更快更强

阿里云大数据AI技术

深度学习 模型优化 企业号九月金秋榜

直播预告 | PostgreSQL 内核解读系列第六讲:PostgreSQL 索引介绍(下)

阿里云数据库开源

数据库 postgresql 阿里云 开源 polarDB

深耕隐私计算技术,瓴羊DataTrust团队喜获殊荣

瓴羊企业智能服务

数据可视化系列教程之组件构成

云智慧AIOps社区

前端 低代码 开源项目 数据可视化 可视化大屏

玖章算术受邀参加红杉Talk「创新的复利」科技专场,共同探讨云计算的前世今生

数据库 数据复制 数据管理 数据备份 玖章算术

一线技术人应该关注的四种思维能力

阿里巴巴中间件

阿里云 技术文章

程序员的摸鱼加速器!

Liam

程序员 前端 测试 后端 Postman

Linux面试最高频的5个基本问题

千锋IT教育

Web3的流支付代表Zebec,熊市布局的价值逻辑

鳄鱼视界

高并发场景下,6种方案,保证缓存和数据库的最终一致性!

C++后台开发

数据库 缓存 高并发 后端开发 C++开发

助力企业成就好生意,华为云快成长直播

科技云未来

华为云WeLink直播助力高校毕业典礼:这届毕业生,我们云上嗨

科技云未来

从Kotlin开发者角度看Java缺失的特性_编程语言_Nicolas Fränkel_InfoQ精选文章