Java 的流毒

简介: 本文讲的是Java 的流毒,这是因为他们两个都有相同的 JVM 签名。这不是 Kotlin 的问题,而是将他们编译成 Java 字节码的结果。这只是 Java 的流毒影响 Kotlin 执行的一种方式。但是这里还有更大的问题。
本文讲的是Java 的流毒,

在 Kotlin 中,你不能定义这两个函数:

fun foo(strings: List<String>) {}
fun foo(ints: List<Int>) {}

这是因为他们两个都有相同的 JVM 签名。这不是 Kotlin 的问题,而是将他们编译成 Java 字节码的结果。这只是 Java 的流毒影响 Kotlin 执行的一种方式。但是这里还有更大的问题。例如,拓展是静态解析。这是一个大问题,我希望写一整篇文章来单独讨论这个问题。现在,它仅仅是个问题而且不直观。事实上,它就是这样设计的,因为这样拓展函数被简单地编译为一个接受第一个参数的静态函数。现在需要在 Kotlin/JavaScript 和 Kotlin/Native 中以相同的方式实现。可以,这很 Java。

减号运算符问题和其他不明确的操作结果

让我们来看一下这个操作:

println(listOf(2,2,2) - 2) // [2, 2]

结果是很直观的,我们从 list 中移除了该元素,因此我们得到一个没有该元素的 list。现在让我们来看一下这个表达式:

println(listOf(2,2,2) - listOf(2))

结果是什么?空的 list!非常不直观,并且我 一年前报告了该问题。但是得到的回复是“它就是这么设计的”。是的,函数说明如下:

/*
// 去除给定的集合中所包含的元素后,返回原集合。
*/

但是这并没有提高程序的可读性。这还只是不直观的一个例子。让我们来看一些更不直观的结果:

"1".toInt() // 1 - parsed to number
'1'.toInt() // 49 - its ASCII code

这是正确的,但同时奇怪的是,请注意以下表达式的结果是 true。

"1".toInt() != '1'.toInt()
"1".toInt() != "1"[0].toInt()

虽然 String 中任何非数字的字符都会导致 NumberFormatException,但对于返回 null 的 String 也有 toIntOrNull 函数。我认为这个函数首先应该命名为另外一种方式更好,或许是 parseInt?

让我们看一下另外一件事情,但这个更为复杂:(感谢 Maciej Górski 的展示)。

1.inc() // 2
1.dec() // 0
-1.inc() // -2
-1.dec() // 0

后面两个结果很奇怪,难道不是吗?原因是 - 不是数字的一部分,而是 Int 的一元拓展函数。这就是为什么后面两行和下面的是相同的:

1.inc().unaryMinus()
1.dec().unaryMinus()

这也是这么设计的,并且这也不会改变。另外一些人会讨论这该如何如何。让我们假设在 Int 后面加了空格:

- 1.inc() // -2
- 1.dec() // 0

现在这看起来就合理了。这应该怎么使用呢?数字应该和 - 一起在括号里面。

(-1).inc() // 0
(-1).dec() // -2

从理性的角度来看,这没问题,但是我认为每个人都会觉得 -2 应该是一个数字,而不是 2.unaryMinus()

孤立主义

Kotlin 有很多适用于任何对象的拓展(比如 let, apply, run, also, to, takeIf, …),我看到很多具有创造力的用法。在 Kotlin 中,你可以将以下定义:

val list = if(student != null) {
    getListForStudent(student)
} else {
    getStandardList()
}

替换为:

val list = student?.let { getListForStudent(student) } ?: getStandardList()

这样代码更少而且看起来更棒。另外,当添加其他条件时,我们依然可以使用:

val list = student?.takeIf { it.passing }?.let { getListForStudent(student) } ?: getStandardList()

但是这个真的比以前简单的 if 条件更好吗?

val list = if(student != null && student.passing) {
    getListForStudent(student)
} else {
    getStandardList()
}

我不置可否,但事实上,这种刻意使用 Kotlin 拓展实现的代码,对于没有使用过 Kotlin 的开发者来说是非常晦涩和抽象的。这种特性让 Kotlin 对于初学者来说变得很困难。Kotlin 的协程变化很大,这是一个很棒的特性。当我开始学习它的时候,我一整天都在重复“不可思议”和“哇”。Kotlin 协程(Coroutines)让多线程操作变得如此简单,非常棒。我觉得编程一开始就应该这么设计。不过,搞清楚 Kotlin 协程依然是一件很复杂的事情,并且它与其它技术的实现方式相去甚远。如果社区开始广泛使用协程,这又会是其它语言的开发者入门的另一个障碍。这就导致了孤立。并且我觉得这太早了。现在 Kotlin 在 Android 和 Web 方面变得越来越受欢迎,而且它刚刚开始在 JavaScript 和 native 中使用。我认为这种多样化对 Kotlin 来说愈发重要,并且 Kotlin 的具体功能介绍应该稍后开始。现在,Kotlin\JavaScript 和 Kotlin\Native 依然有很多工作要做。

元组 VS 单一抽象方法

Kotlin 放弃了元组,并且只留下 Pair 和 Triple。因为应该使用数据类(date class)代替元组。这有什么不同呢?数据类包含其命名,以及其所有的属性的命名。除此之外,它可以像元组一样使用:

data class Student(
        val name: String,
        val surname: String,
        val passing: Boolean,
        val grade: Double
)

val (name, surname, passing, grade) = getSomeStudent()

同时,Kotlin 通过生成包含 lambda 方法而不是 Java 单一抽象方法(SAM:Simple Abstract Method)的 lambda 构造函数和方法来添加对 Java 单一抽象方法的支持:

view.setOnClickListener { toast("Foo") }

但是在 Kotlin 中定义的单一抽象方法不起作用,因为它建议使用函数类型。单一抽象方法和函数类型有什么不同呢?单一抽象方法包含名称,并且其所有参数的命名。从 Kotlin 1.1 开始,函数类型可以通过 typealias(类型别名)实现:

typealias OnClick = (view: View)->Unit

但我仍然觉得这缺乏对称性。如果强烈建议使用数据类,并禁止元组,那么为什么建议使用函数类型而不是单一抽象方法,并且 Kotlin 不支持单一抽象方法。可能的答案是元组会在现实生活的项目产生更多的问题。JetBrains 有很多关于语言用法的数据,他们知道如何分析它。他们非常了解 lambda 语言特性如何影响开发,并且我猜他们知道他们在做什么。我只是基于我的直觉,如果程序员可以决定是否要使用元组或数据类,那将会更好。而且这样显得不孤立,因为大多数现代语言都引入了元组。

总结

事实上,这只是一些小事情。与 JavaScript,PHP 或 Ruby 中存在的问题相比根本不算什么。Kotlin 从一开始就精心设计,是很多问题的解决方案。只有一些小东西不够好。 至少这几年,Kotlin 仍然是,也将是,我最喜欢的语言。






原文发布时间为:2017年6月16日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。
目录
相关文章
|
2月前
|
Java
java
e是java运行时的环境,包含jvm和运行时所需要的类库 jdk是java开的程序包,包含jre和开发人员使用的工具 jvm就是我们常说的java虚拟机,他是整个java实现跨平台的最核心 的部分,所有的java程序会首先被编译为.class的类文件,这种类文 件可以在虚拟机上执行。也就是说class并不直接与机器的操作系统 相对应,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释 给本地系统执行。 只有jvm还不能成class的执行,因为再解释class的时候jvm需要调用 解释所需要的类库lib,而jre包含lib类库。jvm屏蔽了与具体操作系 统平台相关的信息,使得java程序
11 0
|
2月前
|
Java 程序员 C++
【Java】Java与C++:比较与对比
【Java】Java与C++:比较与对比
25 0
|
9月前
|
存储 安全 Java
Java 总结
Java 总结
53 0
|
缓存 负载均衡 Java
JAVA问答5
JAVA问答5
92 0
1071 小赌怡情(JAVA)
常言道“小赌怡情”。这是一个很简单的小游戏:首先由计算机给出第一个整数;然后玩家下注赌第二个整数将会比第一个数大还是小;玩家下注 t 个筹码后,计算机给出第二个数。若玩家猜对了,则系统奖励玩家 t 个筹码;否则扣除玩家 t 个筹码。
1071 小赌怡情(JAVA)
|
Java
Java常见的坑(二)
你猜上述程序输出的是什么? 是 ABC easy as 123 吗? 你执行了输出操作,你才发现输出的是 ABC easy as [C@6e8cf4c6 ,这么一串丑陋的数字是什么鬼? 实际上我们知道字符串与任何数值的相加都会变为字符串,上述事例也不例外, numbers输出其实实际上是调用了Object.toString()方法,让numbers转变为'[c' + '@' + 无符号的十六进制数。
41 0
|
Cloud Native Oracle Java
一篇文章和你从 Java1 聊到 Java18
002-2022 年的 20 年里 Java 始终保持在前三的水平,其中在 2005 年、2013-2015 年间、2021 年等时间还多次登顶过第一,这么一个已经发布了 27 年的语言在这些年是怎么始终保持在编程语言前三的呢?这么多年 Java 各个版本间又有什么变化?Java 语言在未来还会继续保持成为语言的常青树吗?这篇文章就来和大家回顾一下 Java 的历史。
238 0
一篇文章和你从 Java1 聊到 Java18
|
人工智能 Java
Java i++ 与 ++i
Java i++ 与 ++i
117 0
Java i++ 与 ++i
|
资源调度 Java C++
聊聊java中的二进制问题
java中的进制也算是面试中经常会遇到的一个知识点,不管是计算问题,还是涉及到的基础知识。因此这篇文章对其进行一个整理。主要参考了慕课网上的视频,特在此说明。不管是你初学者还是工作中,又或者是找工作中。本文都能对你有所帮助。 本篇文章主要解决以下几个问题: 1、二进制的历史 2、java中的进制转换 3、java中的移位运算 4、数据大小端问题 5、进制在java中的使用 下面我们就针对这些问题,来分析一下java中的进制。
164 0
聊聊java中的二进制问题