AI实践哪家强?来 AICon, 解锁技术前沿,探寻产业新机! 了解详情
写点什么

从 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:512557
用户头像
李冬梅 加V:busulishang4668

发布了 1115 篇内容, 共 729.2 次阅读, 收获喜欢 1257 次。

关注

评论

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

云原生监控高可用集群 Thanos 架构剖析 | 内容合集

耳东@Erdong

内容合集 签约计划第二季

埃文科技荣获“郑州市企业技术中心”

郑州埃文科技

埃文科技 ip技术 企业技术中心

「Spark从精通到重新入门(二)」Spark中不可不知的动态资源分配

尔达Erda

大数据 spark 程序员 云原生 心得

TDengine典型用户案例|内容合集

TDengine

tdengine 时序数据库 技术专题合集

常见序列化算法学习笔记一

风翱

序列化 12月日更

公安合成作战平台建设解决方案,情指勤一体化合成作战系统搭建

电微13828808271

DotNet项目利用拦截器记录访问日志

为自己带盐

日志 dotnet 28天写作 12月日更

西南林业大学:用宜搭打造智慧校园,节省百万费用

一只大光圈

钉钉 低代码 数字化 高校 钉钉宜搭

跟小海一起看下雪——用HTML、CSS和JS实现简单的下雪特效

海拥(haiyong.site)

大前端 js 28天写作 签约计划第二季 12月日更

【量化】用数据验证巴菲特推荐的指数定投的收益真相

恒生LIGHT云社区

数据 金融科技 量化投资 量化

公安情报研判分析系统开发,情指勤一体化实战平台

电微13828808271

利用Java反射处理private变量

FunTester

Java 工具 反射 Groovy FunTester

从0开始学VUE - 运行第一个VUE项目

恒生LIGHT云社区

JavaScript node.js Vue 前端

CSS之选择器(七):empty

Augus

CSS 12月日更

低代码如何让中小型企业蓬勃发展?

低代码小观

低代码 企业管理系统 低代码平台 中小企业

低代码助力直播带货走进制造业,冲击工厂传统供应链模式

优秀

低代码 直播带货

百分点大数据技术团队:Elasticsearch多数据中心大规模集群的实战经验

百分点科技技术团队

Linux一学就会之Linux计划任务与日志的管理

学神来啦

Linux centos 运维 SSH linux云计算

使用kubernetes,你真的降本了吗

Geek_cd6rkj

Docker Kubernetes 云原生 成本治理 提效降本

Golang 问题排查指南

得物技术

Go golang 后端 root

14. 《重学JAVA》-- 内部类

杨鹏Geek

Java 25 周年 28天写作 12月日更

质量基础设施一站式服务平台建设,检验检测系统平台开发搭建

电微13828808271

聚势聚能,共擎共飞 PKS安全先进计算2021生态大会将启

InfoQ 天津

企业数字化的4个痛点,低代码平台如何解决?

J2PaaS低代码平台

低代码 数字化 低代码平台

黑客进行网络欺骗攻击的手段有哪些?

喀拉峻

黑客 网络安全 安全

开发者故事|朝九晚六大小周,我就是快乐的技术人

尔达Erda

程序员 云原生 经验分享 成长笔记 思考路径

RocketMQ和Kafka的差异对比

编程江湖

大数据

RESAR 性能工程:一个性能项目真正体现价值的方式

zuozewei

内容合集 签约计划第二季

哪些企业需要做等保测评?快速过等保测评用什么堡垒机好呢?

行云管家

网络安全 等保 等保测评 等保2.0

英特尔深耕元宇宙算力技术,剑指开“元”盛世

科技新消息

基于社交网络的客户智能 ( AI ) 推荐系统研究

索信达控股

推荐系统 推荐算法 个性化推荐

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