官方吐槽最为致命
早在 2019 年 2 月份, Java 语言架构师 Brian Goetz, 曾经写过一篇文章 (http://cr.openjdk.java.net/~briangoetz/amber/datum.html ), 详尽的说明了并吐槽了 Java 语言, 他和很多程序员一样抱怨 "Java 太啰嗦" 或有太多的 "繁文缛节", 他提到: 开发人员想要创建纯数据载体类(plain data carriers) 通常都必须编写大量低价值, 重复的, 容易出错的代码. 如: 构造函数, getter/setter,equals(),hashCode()以及 toString()等.
以至于很多人选择使用 IDE 的功能来自动生成这些代码. 还有一些开发会选择使用一些第三方类库, 如 Lombok 等来生成这些方法, 从而会导致了令人吃惊的表现 (surprising behavior) 和糟糕的可调试性(poor debuggability).
那么, Brian Goetz 大神提到的纯数据载体到底指的是什么呢. 他举了一个简单的例子:
- final class Point {
- public final int x;
- public final int y;
- public Point(int x, int y) {
- this.x = x;
- this.y = y;
- }
- // state-based implementations of equals, hashCode, toString
- // nothing else
- }
这里面的 Piont 其实就是一个纯数据载体, 他表示一个 "点" 中包含 x 坐标和 y 坐标, 并且只提供了构造函数, 以及一些 equals,hashCode 等方法.
于是, BrianGoetz 大神提出一种想法, 他提到, Java 完全可以对于这种纯数据载体通过另外一种方式表示.
其实在其他的面向对象语言中, 早就针对这种纯数据载体有单独的定义了, 如 Scala 中的 case,Kotlin 中的 data 以及 C# 中的 record. 这些定义, 尽管在语义上有所不同, 但是它们的共同点是类的部分或全部状态可以直接在类头中描述, 并且这个类中只包含了纯数据而已.
于是, 他提出 Java 中是不是也可以通过如下方式定义一个纯数据载体呢?
record Point(int x, int y) { }
神说要用 record, 于是就有了
就像大神吐槽的那样, 我们通常需要编写大量代码才能使类变得有用. 如以下内容:
toString()方法
hashCode() and equals()方法
Getter 方法
一个共有的构造函数
对于这种简单的类, 这些方法通常是无聊的, 重复的, 而且是可以很容易地机械地生成的那种东西(ide 通常提供这种功能).
当你阅读别人的代码时, 可能会更加头大. 例如, 别人可能使用 IDE 生成的 hashCode()和 equals()来处理类的所有字段, 但是如何才能在不检查实现的每一行的情况下确定他写的对呢? 如果在重构过程中添加了字段而没有重新生成方法, 会发生什么情况呢?
大神 Brian Goetz 提出了使用 record 定义一个纯数据载体的想法, 于是, Java 14 中便包含了一个新特性: EP 359: Records , 作者正是 Brian Goetz
Records 的目标是扩展 Java 语言语法, Records 为声明类提供了一种紧凑的语法, 用于创建一种类中是 "字段, 只是字段, 除了字段什么都没有" 的类. 通过对类做这样的声明, 编译器可以通过自动创建所有方法并让所有字段参与 hashCode()等方法. 这是 JDK 14 中的一个预览特性.
一言不合反编译
Records 的用法比较简单, 和定义 Java 类一样:
record Person (String firstName, String lastName) {}
如上, 我们定义了一个 Person 记录, 其中包含两个组件: firstName 和 lastName, 以及一个空的类体.
那么, 这个东西看上去也是个语法糖, 那他到底是怎么实现的那?
我们先尝试对他进行编译, 记得使用 --enable-preview 参数, 因为 records 功能目前在 JDK 14 中还是一个预览 (preview) 功能.
- javac --enable-preview --release 14 Person.java
- Note: Person.java uses preview language features.
- Note: Recompile with -Xlint:preview for details.
如前所述, Record 只是一个类, 其目的是保存和公开数据. 让我们看看用 javap 进行反编译, 将会得到以下代码:
- public final class Person extends java.lang.Record {
- private final String firstName;
- private final String lastName;
- public Person(java.lang.String, java.lang.String);
- public java.lang.String toString();
- public final int hashCode();
- public final boolean equals(java.lang.Object);
- public java.lang.String firstName();
- public java.lang.String lastName();
- }
通过反编译得到的类, 我们可以得到以下信息:
1, 生成了一个 final 类型的 Person 类(class), 说明这个类不能再有子类了.
2, 这个类继承了 java.lang.Record 类, 这个我们使用 enum 创建出来的枚举都默认继承 java.lang.Enum 有点类似
3, 类中有两个 private final 类型的属性. 所以, record 定义的类中的属性都应该是 private final 类型的.
4, 有一个 public 的构造函数, 入参就是两个主要的属性. 如果通过字节码查看其方法体的话, 其内容就是以下代码, 你一定很熟悉:
- public Person(String firstName, String lastName) {
- this.firstName = firstName;
- this.lastName = lastName;
- }
5, 有两个 getter 方法, 分别叫做 firstName 和 lastName. 这和 JavaBean 中定义的命名方式有区别, 或许大神想通过这种方式告诉我们 record 定义出来的并不是一个 JavaBean 吧.
6, 还帮我们自动生成了 toString(), hashCode() 和 equals()方法. 值得一提的是, 这三个方法依赖 invokedynamic 来动态调用包含隐式实现的适当方法.
还可以这样玩
前面的例子中, 我们简单的创建了一个 record, 那么, record 中还能有其他的成员变量和方法吗? 我们来看下.
1, 我们不能将实例字段添加到 record 中. 但是, 我们可以添加静态字段.
- record Person (String firstName, String lastName) {
- static int x;
- }
2, 我们可以定义静态方法和实例方法, 可以操作对象的状态.
- record Person (String firstName, String lastName) {
- static int x;
- public static void doX(){
- x++;
- }
- public String getFullName(){
- return firstName + " " + lastName;
- }
- }
3, 我们还可以添加构造函数.
- record Person (String firstName, String lastName) {
- static int x;
- public Person{
- if(firstName == null){
- throw new IllegalArgumentException( "firstName can not be null !");
- }
- }
- public Person(String fullName){
- this(fullName.split("")[0],this(fullName.split(" ")[1])
- }
- }
所以, 我们是可以在 record 中添加静态字段 / 方法的, 但是问题是, 我们应该这么做吗?
请记住, record 推出背后的目标是使开发人员能够将相关字段作为单个不可变数据项组合在一起, 而不需要编写冗长的代码. 这意味着, 每当您想要向您的记录添加更多的字段 / 方法时, 请考虑是否应该使用完整的类来代替它.
总结
record 解决了使用类作为数据包装器的一个常见问题. 纯数据类从几行代码显著地简化为一行代码.
但是, record 目前是一种预览语言特性, 这意味着, 尽管它已经完全实现, 但在 JDK 中还没有标准化.
那么问题来了, 如果你用上了 Java 14 之后, 你还会使用 Lombok 吗? 哦不, 你可能短时间内都用不上, 因为你可能 Java 8 都还没用熟~
来源: http://developer.51cto.com/art/202003/613190.htm