有了Records,Java 14欢迎使用紧凑的语法来声明数据类。 它使您仅使用一行代码即可轻松地对数据建模。 编译器将根据使类与您一起工作所需的隐式方法进行繁重的工作。
在本文中,我将介绍什么是记录,它们解决的问题以及如何使用它们。 让我们开始吧。
为什么我们需要Java记录?
很少看到没有与数据存储交互的应用程序。 通常,您的应用程序需要从数据源读取数据或对其进行写入。 作为开发人员,我们习惯于定义数据类,并封装变量以存储其状态。 但是,对于常规类,规则是定义私有实例变量以存储数据对象的状态,添加公共构造函数,访问器或变种方法以及从Object
覆盖诸如toString()
, equals()
和hashCode()
之类的方法类。
有时,开发人员会减少工作量–通过不定义这些方法,或使用模糊的现有类(类似于他们所需的类)来减少工作量。 两种情况都可能在以后引发意外。
以下是您可以用来表示Rectangle并将其存储到数据存储中的类的示例:
package com.jetbrains;
import java.util.Objects;
public class Rectangle {
private int length;
private int breadth;
public Rectangle(int length, int breadth) {
this.length = length;
this.breadth = breadth;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getBreadth() {
return breadth;
}
public void setBreadth(int breadth) {
this.breadth = breadth;
}
@Override
public String toString() {
return "Rectangle{" + "length=" + length + ", breadth=" + breadth + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Rectangle = (Rectangle) o;
return length == rectangle.length && breadth == rectangle.breadth;
}
@Override
public int hashCode() {
return Objects.hash(length, breadth);
}
}
虽然我们可以用类似的IDE IntelliJ IDEA的生成所有这些代码(构造函数,getter方法,setter方法和覆盖方法Object
),一类不能被标记只是作为一个数据类。 没有这种语言的帮助,开发人员仍然需要通读所有代码以意识到它只是一个数据类。 让我们看看记录如何提供帮助。
什么是记录?
记录在Java中引入了一种新的类型声明,从而简化了将数据建模为数据的任务。 它还大大减少了样板代码(但是,这不是其引入的主要原因)。
让我们将类Rectangle(来自上一节)定义为Record:
record Rectangle(int length, int breadth) {}
前面的示例仅用一行代码定义了一个记录,该记录的成分为length
和breadth
。 默认情况下,编译器为记录创建一堆方法,例如用于存储其状态的实例方法,构造函数,getter(无设置方法-数据的意图是不变的),并覆盖toString()
, equals()
和hashCode()
从该方法Object
类。
在IntelliJ IDEA 2020.1中,“新建Java类”对话框允许您将类型选择为“记录(预览功能)”:
有趣的是,当您编译记录以及由编译过程添加到记录中的隐式成员时会发生什么。
隐式成员已添加到记录
记录被定义为最终类,它从核心Java API隐式扩展了java.lang.Record
类。 对于其定义的每个组件(数据变量),编译器都定义一个final
实例变量。 有趣的是,getter方法的名称与数据变量的名称相同(并且不要以“ get
”开头)。 由于记录是不可变的,因此未定义setter方法。
方法toString()
, hashCode()
和equals()
也会自动为记录生成。
您可以使用Java类文件反汇编程序(来自JDK的javap.exe,带有选项-p)来显示反编译类的变量和方法。
您也可以在IntelliJ IDEA中打开.class文件以查看其反编译版本。 只需在“ out”文件夹中单击其.class文件:
还请参见:
如何使用记录
您可以像其他任何类一样初始化记录,方法是使用new运算符,将值传递给其构造函数,定义其状态。 让我们实例化上一节中定义的Rectangle记录:
Rectangle smallRectangle = new Rectangle(2, 5);
Rectangle aBigRectangle = new Rectangle(100, 150);
让我们在这些引用上调用几个(隐式)方法,这些方法是在编译过程中添加到其中的:
System.out.println("Let's see what toString() returns: " + smallRectangle);
System.out.println(smallRectangle.equals(aBigRectangle));
System.out.println("Let's getTheHashCode() :" + smallRectangle.hashCode());
System.out.println("Seriously? no getLength() method: " + smallRectangle.length());
这是前面代码的输出:
Let's see what toString() returns: Rectangle[length=2, breadth=5]
false
Let's getTheHashCode() :67
Seriously? no getLength() method: 2
从前面的输出中可以明显看出, toString()
方法包括记录的名称,然后是其状态变量的名称及其值。 另一个值得注意的一点是,记录属性的获取器不包含单词“ get”。 它们与属性名称相同。 同样,没有设置方法(因为记录被认为是不可变的)。
在IntelliJ IDEA中支持Java 14功能
IntelliJ IDEA – 2020.1的最新版本完全支持Java 14的所有新语言功能。您可以立即下载Beta版本并自己检查一下。
配置IntelliJ IDEA 2020.1以将JDK 14用于Project SDK,并为您的Project和Modules设置选择Project语言级别为“ 14(预览)–记录,样式,文本块”。
Ultimate版本可在其抢先体验计划(EAP)中免费使用。
修改记录的默认行为
您可以修改编译器生成的方法的默认实现。 但是,除非必要,否则您不能这样做。
由编译器生成的构造函数的默认实现将初始化记录的状态。 您可以修改此默认实现,以验证参数值或添加业务逻辑。
IntelliJ IDEA允许您在调用操作“生成”时通过在Windows或Linux系统上使用Alt + Insert /或在macOS上使用^ N来插入紧凑,规范或自定义构造函数:
紧凑的构造函数没有定义参数列表,甚至没有括号。 这是一个例子:
package com.jetbrains;
public record Rectangle { // no parameter list here!
public Rectangle {
if (length < 0 || breadth < 0) throw new IllegalArgumentException("Length & breadth must be > 0");
}
}
这是一个规范构造函数的示例,用于验证传递给其构造函数的参数值。 此构造函数还添加了一些业务逻辑–在构造函数中增加静态字段:
public record Rectangle(int length, int breadth) {
public Rectangle {
if (length < 0 || breadth < 0) {
throw new IllegalArgumentException("-ve dimensions. Seriously?");
}
totalRectanglesCreated++;
}
private static int totalRectanglesCreated;
static int getTotalRectanglesCreated() {
return totalRectanglesCreated;
}
}
如您在前面的示例中所注意到的,记录可以添加静态字段或实例以及静态方法(如果需要)。
通过在IntelliJ IDEA中插入自定义构造函数,可以选择要在构造函数中显式接受的参数。 剩余的状态变量将被分配一个默认值(例如,参考变量为null,整数值为0)。
您不能像常规类一样向记录添加多个构造函数。
还请参见:
定义通用记录
您可以使用泛型定义记录。 这是一个记录示例,例如“ Parcel
,它可以存储任何对象作为其内容,并捕获包裹的尺寸和重量:
public record Parcel<T>(T contents,
double length,
double breadth,
double height,
double weight) {}
// You can instantiate this record as follows :
class Table{ /* class code */ }
public class Java14 {
public static void main(String[] args) {
Parcel
<Table> parcel = new Parcel<>(new Table(), 200, 100, 55, 136.88);
System.out.println(parcel);
}
}
添加注释以记录组件
您可以将注释添加到记录的组件,这适用于它们。 例如,您可以定义记录Car,将@NotNull注释应用于其组件模型:
package com.jetbrains;
import org.jetbrains.annotations.NotNull;
import java.time.LocalDateTime;
public record Car(@NotNull String model, LocalDateTime purchased){}
如果在实例化记录Car
时将null
值作为第一个参数传递,则会收到警告。
还请参见:
读写记录
将记录写入文件并使用Java File I / O API读取记录很简单。 让您的记录实现Serializable接口,您一切顺利。 这是记录Rectangle的修改后的版本,您可以对其进行写入和读取操作:
package com.jetbrains;
public record Rectangle(int length, int breadth) implements Serializable {}
public class Java14 {
public static void main(String[] args) {
Rectangle smallRectangle = new Rectangle(2, 5);
writeToFile(smallRectangle, "Java14-records");
System.out.println(readFromFile("Java14-records"));
}
static void writeToFile(Rectangle obj, String path) {
try (ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(path))){
oos.writeObject(obj);
} catch (IOException e) {
e.printStackTrace();
}
}
static Rectangle readFromFile(String path) {
Rectangle result = null;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path))){
result = (Rectangle) ois.readObject();
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
return result;
}
}
硬币总是有两个方面。 记录有很多优点,让我们看看您无法使用它。
记录有限制吗?
由于记录是最终类,因此您不能将其定义为抽象类。 而且,它不能扩展另一个类(因为它隐式扩展了java.lang.Record
类)。 但是对其实现接口没有任何限制。
即使可以将静态成员添加到记录,也不能向其添加实例变量。 这是因为记录应该将实例成员限制为它在记录定义中定义的组件。 另外,您可以覆盖编译器为记录生成的方法的默认实现,例如其构造函数,方法equals()
, hashCode()
和toString
。
受限标识符
' record
'是一个受限制的标识符(例如' var
'),并且不是关键字(尚未)。 因此,以下代码有效:
int record = 10;
void record() {}
但是,您应该避免将记录用作标识符,因为将来的Java版本中可能会将它作为关键字包含在内。
预览语言功能
记录已在Java 14中作为预览语言功能发布。随着Java六个月的新发布节奏,新的语言功能作为预览功能发布了,这些功能既不完整也不是半生半熟。 预览语言功能从本质上讲意味着即使开发人员可以使用此功能,但在将来的Java版本中,其更详细的信息可能会更改-取决于开发人员对此功能所收到的反馈。 与API不同,将来无法弃用语言功能。 因此,如果您对文本块有任何反馈,请在JDK邮件列表中共享(需要成员身份)。
如果使用命令提示符,则必须在编译和运行时启用它们。 要编译定义记录的Rectangle类,请使用以下命令:
javac --enable-preview --release 14 Java14.java
编译器会警告您正在使用预览语言功能。
要执行使用记录的类(例如Java14)(或Java 14中的另一种预览语言功能),请使用以下命令:
java --enable-preview Java14
展望未来
正在开展工作以导出记录的解构模式。 它应该展示带有“密封类型”和“模式匹配”的有趣用例。 Java的未来版本还应该在Object类中添加一些方法,以使反射能够与Records一起使用。
同时,为什么不看看我关于Java 14语言功能的截屏视频:
翻译自: https://jaxenter.com/java-14-records-deep-dive-169879.html