来自Oracle 的Java核心库开发人员: Hotspot VM 以特殊方式信任 Java 记录,与常规 Java 类相比,这使得它们的速度在某些方面更加优越。
假设我们想要建模一个不可变点:
public interface Point { int x(); int y(); }
|
在 Java 中引入记录类之前 ,必须使用常规 Java 类“手动”编码数据类,如下所示:public final class RegularPoint implements Point {
private final int x; private final int y;
public RegularPoint(int x, int y) { this.x = x; this.y = y; }
@Override public int x() { return x; }
@Override public int y() { return y; }
// Implementations of toString(), hashCode() and equals() // omitted for brevity
}
|
有了记录,一切变得更加容易,而且我们还获得了方法 toString()、 hashCode() 和 的合理默认实现equals():public record RecordPoint(int x, int y) implements Point {}
|
基准测试:
这是一个基准,可用于衡量使用记录相对于常规类的效果:
@BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Benchmark) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) @Fork(value=3) public class Bench {
private static final RegularPoint REGULAR_ORIGIN = new RegularPoint(0, 0); private static final RecordPoint RECORD_ORIGIN = new RecordPoint(0, 0);
private List<RegularPoint> regularPoints; private List<RecordPoint> recordPoints;
@Setup public void setup() { regularPoints = IntStream.range(0, 16) .mapToObj(i -> new RegularPoint(i, i)) .toList();
recordPoints = IntStream.range(0, 16) .mapToObj(i -> new RecordPoint(i, i)) .toList(); }
@Benchmark public void regular(Blackhole bh) { for (RegularPoint point: regularPoints) { if (point.x() == REGULAR_ORIGIN.x() && point.y() == REGULAR_ORIGIN.y()) { bh.consume(1); } else { bh.consume(0); } } }
@Benchmark public void record(Blackhole bh) { for (RecordPoint point: recordPoints) { if (point.x() == RECORD_ORIGIN.x() && point.y() == RECORD_ORIGIN.y()) { bh.consume(1); } else { bh.consume(0); } } }
public static void main(String[] args) throws Exception { org.openjdk.jmh.Main.main(args); }
}
|
在 Mac M1 笔记本电脑上运行时,出现以下结果(越低越好):
Benchmark Mode Cnt Score Error Units Bench.regular avgt 15 10.424 ± 0.257 ns/op Bench.record avgt 15 9.412 ± 0.181 ns/op
|
可以看出,在此基准测试中,记录比常规类快约 10%。
为什么记录在上述情况下会更快?
类 ciField.cpp中有一条线索 ,它告诉 Hotspot 编译器在执行常量折叠时哪些实例字段应该“可信”。
一个例子是 外部函数和内存 API, 该 API 预计将在 Java 22 中最终确定,例如,实现各种 MemoryLayout 变体的所有类都有资格进行常量折叠优化。
上面的 C++ 类不适用于常规 Java 程序,但是通过切换到记录,我们可以直接获得实例字段常量折叠的好处。
记录提供了一种表达数据载体的便捷方式。作为一个额外的好处,与某些应用程序中的常规 Java 类相比,它们还提供了改进的性能。