spr*_*ter 45 java java-14 java-record
recordJDK 14 中引入的预览功能 (JEP 384) 是一项伟大的创新。它们使创建简单的不可变类变得更加容易,这些类是纯粹的值集合,而不会丢失各种库中通用元组类中固有的上下文。
由 Brian Goetz ( https://openjdk.java.net/jeps/384 )编写的 JEP 描述很好地解释了意图。然而,我期待与最终引入的值类型有更密切的联系。值类型的最初目标是相当广泛的:通过消除这些类型的对象不需要的所有开销(例如引用间接、同步),本质上允许对那些值最重要的对象进行潜在的显着性能改进。此外,它还可以提供句法细节,例如myPosition != yourPosition代替!myPosition.equals(yourPosition).
似乎记录的限制与潜在值类型所需的限制类型非常接近。然而,JEP 在动机中没有提及这些目标。我试图找到有关这些审议的任何公开记录,但没有成功。
所以我的问题是:记录是否旨在成为可能向值类型转变的一部分,还是这些完全不相关的概念和未来的值类型可能看起来完全不同?
我提出这个问题的动机是:如果记录成为语言的永久部分,那么如果在未来的版本中可能会带来显着的性能优势,那么在代码中采用它们将是一个额外的动力。
Bri*_*etz 53
记录和原始类(值类型的新名称)有很多共同点——它们是隐式 final 和浅不可变的。所以可以理解,这两者可能被视为同一件事。实际上,它们是不同的,它们有共存的空间,但它们也可以一起工作。
这两种新类别都涉及某种限制,以换取某些好处。(就像enum,在那里您放弃对实例化的控制,并获得更精简的声明、在 中的支持switch等)
Arecord要求您放弃扩展、可变性以及将表示与 API 分离的能力。作为回报,您可以获得构造函数、访问器equals、hashCode、 等的实现。
Aprimitive class要求您放弃身份,包括放弃扩展和可变性,以及其他一些事情(例如,同步)。作为回报,您会获得一组不同的好处——扁平化的表示、优化的调用序列以及基于状态的equals和hashCode.
如果你愿意做出妥协两者,就可以得到这两个台的好处-这将是一个primitive record。原始记录有很多用例,所以今天是记录的类明天可能是原始记录,并且会变得更快。
但是,我们不想强制所有记录都是原始记录或所有原始记录都是记录。有些原始类需要使用封装,而记录需要标识(以便它们可以组织成树或图形),这很好。
免责声明:这个答案只是通过总结一些含义并给出一些例子来扩展其他答案。您不应依赖此信息做出任何决定,因为模式匹配和值类型仍然会发生变化。
\n有两个关于数据类(又名记录与值类型)的有趣文档:\n2018 年 2 月的旧版本
\n http://cr.openjdk.java.net/~briangoetz/amber/datum_2.html#are-data-classes- the-same-as-value-types
\n以及 2019 年 2 月起的新版本
\n https://cr.openjdk.java.net/~briangoetz/amber/datum.html#are-records-the-same-as-值类型
每个文档都包含一段有关记录和值类型之间差异的段落。\n旧版本说
\n\n\n缺乏布局多态性意味着我们必须放弃其他东西:自引用。值类型 V 不能直接或间接引用另一个 V。
\n
而且,
\n\n\n与值类型不同,数据类非常适合表示树和图节点。
\n
然而,
\n\n\n但值类不需要放弃任何封装,事实上封装对于值类型的某些应用是必不可少的
\n
让我们澄清一下:
\n您将无法实现基于节点的复合数据结构,例如具有值类型的链表或分层树。但是,您可以对这些数据结构的元素使用值类型。\n此外,值类型支持某些形式的封装,与根本不支持的记录相反。这意味着您可以在值类型中拥有未在类标头中定义的附加字段,并且这些字段对值类型的用户隐藏。记录不能这样做,因为它们的表示仅限于它们的 API,即它们的所有字段都在类标头中声明(并且仅在那里!)。
\n让我们用一些例子来说明这一切。
\n例如,您将能够使用记录创建复合逻辑表达式,但不能使用值类型:
\nsealed interface LogExpr { boolean eval(); } \n\nrecord Val(boolean value) implements LogExpr {}\nrecord Not(LogExpr logExpr) implements LogExpr {}\nrecord And(LogExpr left, LogExpr right) implements LogExpr {}\nrecord Or(LogExpr left, LogExpr right) implements LogExpr {}\nRun Code Online (Sandbox Code Playgroud)\n这不适用于值类型,因为这需要具有相同值类型的自引用能力。\n您希望能够创建类似“Not(Not(Val(true)))”的表达式。
\n例如,您还可以使用记录来定义Fraction类:
\nrecord Fraction(int numerator, int denominator) { \n Fraction(int numerator, int denominator) {\n if (denominator == 0) {\n throw new IllegalArgumentException("Denominator cannot be 0!");\n }\n }\n public double asFloatingPoint() { return ((double) numerator) / denominator; }\n // operations like add, sub, mult or div\n}\nRun Code Online (Sandbox Code Playgroud)\n计算该分数的浮点值怎么样?\n您可以将方法asFloatingPoint()添加到记录Fraction。\n并且每次调用它时,它总是会计算(并重新计算)相同的浮点值。\n(Records并且值类型默认是不可变的)。\n但是,您无法以对用户隐藏的方式预先计算并存储该记录中的浮点值。\n并且您不希望将浮点值显式声明为第三个浮点值类头中的参数。\n幸运的是,值类型可以做到这一点:
\ninline class Fraction(int numerator, int denominator) { \n private final double floatingPoint;\n Fraction(int numerator, int denominator) {\n if (denominator == 0) {\n throw new IllegalArgumentException("Denominator cannot be 0!");\n }\n floatingPoint = ((double) numerator) / denominator;\n }\n public double asFloatingPoint() { return floatingPoint; }\n // operations like add, sub, mult or div\n}\nRun Code Online (Sandbox Code Playgroud)\n当然,隐藏字段可能是您想要使用值类型的原因之一。\n它们只是一个方面,而且可能是一个次要的方面。\n如果您创建Fraction的许多实例并可能将它们存储在集合中,\n您将受益于扁平化内存布局中的很多内容。\n这绝对是更喜欢值类型而不是记录的更重要原因。
\n在某些情况下,您希望同时受益于记录和值类型。
\n例如 您可能想要开发一款游戏,在其中您可以在地图上移动棋子。\n您不久前在列表中保存了移动历史记录,其中每个移动都存储了朝一个方向的多个步骤。您想要根据现在的移动列表来计算下一个位置。
\n如果您的Move类是值类型,则列表可以使用扁平化内存布局。
\n如果你的Move类同时也是一条记录,你可以使用模式匹配,而不需要定义显式的解构模式。
\n你的代码可以是这样的:
enum Direction { LEFT, RIGHT, UP, DOWN }\xc2\xb4\nrecord Position(int x, int y) { } \ninline record Move(int steps, Direction dir) { }\n\npublic Position move(Position position, List<Move> moves) {\n int x = position.x();\n int y = position.y();\n\n for(Move move : moves) {\n x = x + switch(move) {\n case Move(var s, LEFT) -> -s;\n case Move(var s, RIGHT) -> +s;\n case Move(var s, UP) -> 0;\n case Move(var s, DOWN) -> 0;\n }\n y = y + switch(move) {\n case Move(var s, LEFT) -> 0;\n case Move(var s, RIGHT) -> 0;\n case Move(var s, UP) -> -s;\n case Move(var s, DOWN) -> +s;\n }\n }\n\n return new Position(x, y);\n}\nRun Code Online (Sandbox Code Playgroud)\n当然,还有许多其他方法可以实现相同的行为。\n但是,记录和值类型为您提供了更多非常有用的实现选项。
\n| 归档时间: |
|
| 查看次数: |
2411 次 |
| 最近记录: |