Java records是否打算最终成为值类型?

huangapple go评论69阅读模式
英文:

Are Java records intended to eventually become value types?

问题

The record preview feature (JEP 384) introduced in JDK 14 is a great innovation. They make it much easier to create simple immutable classes that are a pure collection of values without the loss of context inherent in the generic tuple classes in various libraries.

JEP 384中引入的record预览特性是一项伟大的创新。它们使得创建简单的不可变类变得更加容易,这些类是值的纯集合,没有各种库中的通用元组类中固有的上下文丢失问题。

The description of the JEP, written by Brian Goetz (https://openjdk.java.net/jeps/384) explains the intent very well. However I was expecting a closer association with the eventual introduction of value types. The original aims of value types were quite extensive: essentially allowing potentially significant performance improvements for objects whose value is all that matters by removing all the overhead not required for these types of object (e.g. reference indirection, synchronisation). Plus it could provide syntactical niceties such as myPosition != yourPosition instead of !myPosition.equals(yourPosition).

JEP的描述,由Brian Goetz编写(https://openjdk.java.net/jeps/384),非常清楚地解释了其意图。然而,我原本期望它与最终引入的值类型更密切相关。值类型的最初目标非常广泛:基本上允许对其是唯一重要的对象进行潜在的重大性能改进,通过删除不适用于这些类型的对象的所有不必要开销(例如引用间接性、同步)。此外,它可以提供语法上的便利,例如myPosition != yourPosition而不是!myPosition.equals(yourPosition)

It seems as though the restrictions of records are very close to the types of restrictions that would be required on a potential value type. Yet the JEP makes no reference to these aims in the motivation. I tried unsuccessfully to find any public records on these deliberations.

似乎记录的限制非常接近潜在值类型所需的限制类型。然而,JEP在动机方面没有提到这些目标。我尝试过没有成功地找到任何关于这些讨论的公开记录。

So my question is: are records intended to be part of a possible move towards value types or are these completely unrelated concepts and future value types may look completely different?

所以我的问题是:记录是否打算成为可能的迈向值类型的一部分,还是这些完全无关的概念,未来的值类型可能会完全不同?

My motivation for asking the question: if records become a permanent part of the language it would be an added incentive to adopt them in code if there is the possibility of significant performance benefits in a future release.

我提出这个问题的动机是:如果记录成为语言的永久部分,那么如果在未来的版本中可能存在显著的性能优势,这将是采用它们的一个额外动机。

英文:

The record preview feature (JEP 384) introduced in JDK 14 are a great innovation. They make it much easier to create simple immutable classes that are a pure collection of values without the loss of context inherent in the generic tuple classes in various libraries.

The description of the JEP, written by Brian Goetz (https://openjdk.java.net/jeps/384) explains the intent very well. However I was expecting a closer association with the eventual introduction of value types. The original aims of value types were quite extensive: essentially allowing potentially significant performance improvements for objects whose value is all that matters by removing all the overhead not required for these types of object (e.g. reference indirection, synchronisation). Plus it could provide syntactical niceties such as myPosition != yourPosition instead of !myPosition.equals(yourPosition).

It seems as though the restrictions of records are very close to the types of restrictions that would be required on a potential value type. Yet the JEP makes no reference to these aims in the motivation. I tried unsuccessfully to find any public records on these deliberations.

So my question is: are records intended to be part of a possible move towards value types or are these completely unrelated concepts and future value types may look completely different?

My motivation for asking the question: if records become a permanent part of the language it would be an added incentive to adopt them in code if there is the possibility of significant performance benefits in a future release.

答案1

得分: 100

Records 和原始类(值类型的新名称)有很多共同点 - 它们都是隐式的 final 和浅不可变的。因此,可以理解为它们可能被视为相同的事物。实际上,它们是不同的,它们可以共存,但它们也可以一起使用。

这两种新类型的类都涉及某种限制,以换取某些好处。(就像 enum 一样,您放弃了对实例化的控制,并得到了更简化的声明、在 switch 中的支持等等。)

record 要求您放弃扩展性、可变性以及将表示与 API 解耦的能力。作为回报,您将获得构造函数、访问器、equalshashCode 等的实现。

primitive class 要求您放弃身份,其中包括放弃扩展性和可变性,以及一些其他内容(例如同步)。作为回报,您将获得不同的一组好处 - 扁平化表示、优化的调用序列以及基于状态的 equalshashCode

如果您愿意做出这两种妥协,您可以获得 两种 套好处 - 这将是一个 primitive record。原始记录有许多用例,因此今天是记录的类明天可能是原始记录,并且只会变得更快。

但是,我们不想强制所有记录都是原始记录,也不想让所有原始类都成为记录。有一些原始类希望使用封装,而记录希望具有身份(以便它们可以组织成树形结构或图形),这是可以接受的。

英文:

Records and primitive classes (the new name for value types) have a lot in common -- they are implicitly final and shallowly immutable. So it is understandable that the two might be seen as the same thing. In reality, they are different, and there is room for both of them to co-exist, but they can also work together.

Both of these new kinds of classes involve some sort of restriction, in exchange for certain benefits. (Just like enum, where you give up control over instantiation, and are rewarded with a more streamlined declaration, support in switch, etc.)

A record requires you to give up on extension, mutability, and the ability to decouple the representation from the API. In return, you get implementations of constructors, accessors, equals, hashCode, and more.

A primitive class requires you to give up on identity, which includes giving up on extension and mutability, as well as some other things (e.g., synchronization). In return, you get a different set of benefits -- flattened representation, optimized calling sequences, and state-based equals and hashCode.

If you are willing to make both compromises, you can get both sets of benefits -- this would be a primitive record. There are lots of use cases for primitive records, so classes that are records today could be primitive records tomorrow, and would just get faster.

But, we don't want to force all records to be primitive or for all primitives to be records. There are primitive classes that want to use encapsulation, and records that want identity (so they can organize into trees or graphs), and this is fine.

答案2

得分: 7

免责声明:此答案仅通过总结一些含义并提供一些示例来扩展其他答案。您不应仅依赖这些信息做出任何决策,因为模式匹配和值类型仍然是变化的主题。

关于数据类别(records)与值类型(value types),有两个有趣的文档:

  1. 较早版本,出自2018年2月
    链接
  2. 较新版本,出自2019年2月
    链接

每个文档都包括关于数据类别(records)与值类型(value types)之间的差异的段落。较早版本中提到:

布局多态性的缺失意味着我们必须放弃另一些东西:自引用。值类型V不能直接或间接地引用另一个V。

此外,

与值类型不同,数据类别非常适合表示树和图节点。

然而,

但值类型无需放弃任何封装,事实上,封装对于某些值类型的应用是必不可少的。

让我们澄清一下:

您将无法使用值类型来实现基于节点的组合数据结构,例如链表或分层树。但是,您可以将值类型用于这些数据结构的元素。此外,值类型支持某些封装形式,与数据类别不同,后者根本不支持。这意味着您可以在值类型中拥有未在类头中定义的其他字段,这些字段对值类型的用户是隐藏的。数据类别无法做到这一点,因为它们的表示受到其API的限制,即所有字段都在类头中声明(仅在那里)。

让我们通过一些示例来说明这一点。

例如,您可以使用数据类别来定义Fraction类:

record Fraction(int numerator, int denominator) { 
    Fraction(int numerator, int denominator) {
        if (denominator == 0) {
            throw new IllegalArgumentException("Denominator cannot be 0!");
        }
    }
    public double asFloatingPoint() { return ((double) numerator) / denominator; }
    // add, sub, mult or div operations
}

如何计算该分数的浮点值?您可以将asFloatingPoint()方法添加到Fraction数据类别中。它将始终在调用时计算(和重新计算)相同的浮点值(数据类别默认是不可变的)。但是,您不能在对用户隐藏的方式中预先计算和存储浮点值。您也不希望在类头中明确声明浮点值作为第三个参数。幸运的是,值类型可以做到这一点:

inline class Fraction(int numerator, int denominator) { 
    private final double floatingPoint;
    Fraction(int numerator, int denominator) {
        if (denominator == 0) {
            throw new IllegalArgumentException("Denominator cannot be 0!");
        }
        floatingPoint = ((double) numerator) / denominator;
    }
    public double asFloatingPoint() { return floatingPoint; }
    // add, sub, mult or div operations
}

当然,隐藏字段可以是您想要使用值类型的原因之一。但这只是其中一个方面,可能是一个较小的方面。如果您创建许多Fraction实例并将它们存储在集合中,您将受益于扁平化的内存布局。这绝对是更喜欢值类型而不是数据类别的更重要原因之一。

有些情况下,您可能希望同时从数据类别和值类型中受益。例如,您可能希望开发一个游戏,在游戏中,您可以通过地图移动您的角色。一段时间前,您将每个移动存储在列表中,其中每个移动都存储了一个方向中的步数。现在,您想根据移动列表计算下一个位置。如果您的Move类是一个值类型,那么列表可以使用扁平化的内存布局。如果您的Move类同时也是一个数据类别,那么您可以使用模式匹配而不需要定义显式的解构模式。您的代码可能如下所示:

enum Direction { LEFT, RIGHT, UP, DOWN }
record Position(int x, int y) {  } 
inline record Move(int steps, Direction dir) {  }

public Position move(Position position, List<Move> moves) {
    int x = position.x();
    int y = position.y();

    for(Move move : moves) {
        x = x + switch(move) {
            case Move(var s, LEFT) -> -s;
            case Move(var s, RIGHT) -> +s;
            case Move(var s, UP) -> 0;
            case Move(var s, DOWN) -> 0;
        }
        y = y + switch(move) {
            case Move(var s, LEFT) -> 0;
            case Move(var s, RIGHT) -> 0;
            case Move(var s, UP) -> -s;
            case Move(var s, DOWN) -> +s;
        }
    }

    return new Position(x, y);
}

当然,还有许多其他实现相同行为的方法。但是,数据类别和值类型为您提供了更多的实现选项,这可能非常有用。

英文:

Disclaimer: This answer only extends the other answers by summarizing some implications and giving some examples. You should not make any decisions relying on this information because pattern matching and value types are still subjects of change.

There are two interesting documents about data classes aka records vs value types:
The older version from February 2018
http://cr.openjdk.java.net/~briangoetz/amber/datum_2.html#are-data-classes-the-same-as-value-types
and the newer version from February 2019
https://cr.openjdk.java.net/~briangoetz/amber/datum.html#are-records-the-same-as-value-types

Each document includes a paragraph about differences between records and value types.
The older version says

> The lack of layout polymorphism means we have to give up something else: self-reference. A value type V cannot refer, directly or indirectly, to another V.

Moreover,

> Unlike value types, data classes are well suited to representing tree and graph nodes.

However,

> But value classes need not give up any encapsulation, and in fact encapsulation is essential for some applications of value types

Let's clarify that:

You won't be able to implement node-based composited data structures like linked lists or hierarchical trees with value types. However, you can use the value types for the elements of those data structures.
Moreover, value types support some forms of encapsulation in opposite to records which don't at all. This means you can have additional fields in value types which you haven't defined in the class header and which are hidden to the user of the value type. Records can't do that because their representation is restricted to their API, i.e. all their fields are declared in the class header (and only there!).

Let's have some examples to illustrate all that.

E.g. you will be able to create composited logical expressions with records but not with value types:

sealed interface LogExpr { boolean eval(); } 

record Val(boolean value) implements LogExpr {}
record Not(LogExpr logExpr) implements LogExpr {}
record And(LogExpr left, LogExpr right) implements LogExpr {}
record Or(LogExpr left, LogExpr right) implements LogExpr {}

This will not work with value types because this demands the ability of self-references of the same value type.
You want to be able to create expressions like "Not(Not(Val(true)))".

E.g. you can also use records to define the class Fraction:

record Fraction(int numerator, int denominator) { 
    Fraction(int numerator, int denominator) {
        if (denominator == 0) {
            throw new IllegalArgumentException(&quot;Denominator cannot be 0!&quot;);
        }
    }
    public double asFloatingPoint() { return ((double) numerator) / denominator; }
    // operations like add, sub, mult or div
}

What about calculating the floating point value of that fraction?
You can add a method asFloatingPoint() to the record Fraction.
And it will always calculate (and recalculate) the same floating point value each time it is invoked.
(Records and value types are immutable by default).
However, you cannot precalculate and store the floating point value inside this record in a way hidden to the user.
And you won't like to explicitly declare the floating point value as a third parameter in the class header.
Luckily, value types can do that:

inline class Fraction(int numerator, int denominator) { 
    private final double floatingPoint;
    Fraction(int numerator, int denominator) {
        if (denominator == 0) {
            throw new IllegalArgumentException(&quot;Denominator cannot be 0!&quot;);
        }
        floatingPoint = ((double) numerator) / denominator;
    }
    public double asFloatingPoint() { return floatingPoint; }
    // operations like add, sub, mult or div
}

Of course, hidden fields can be one reason why you want to use value types.
They are only one aspect and probably a minor one though.
If you create many instances of Fraction and maybe store them in collections,
you will benefit a lot from the flattened memory layout.
That's definitely a more important reason to prefer value types to records.

There are some situations where you want to benefit from both records and value types.
E.g. you might want to develop a game in which you move your piece through a map.
You saved a history of moves in a list some time ago where every move stores a number of steps into one direction. And you want to compute the next position dependending on that list of moves now.
If your class Move is a value type, then the list can use the flattened memory layout.
And if your class Move also is a record at the same time you can use pattern matching without the need of defining an explicit deconstruction pattern.
Your code can look like that:

enum Direction { LEFT, RIGHT, UP, DOWN }&#180;
record Position(int x, int y) {  } 
inline record Move(int steps, Direction dir) {  }

public Position move(Position position, List&lt;Move&gt; moves) {
	int x = position.x();
	int y = position.y();

	for(Move move : moves) {
		x = x + switch(move) {
			case Move(var s, LEFT) -&gt; -s;
			case Move(var s, RIGHT) -&gt; +s;
			case Move(var s, UP) -&gt; 0;
			case Move(var s, DOWN) -&gt; 0;
		}
		y = y + switch(move) {
			case Move(var s, LEFT) -&gt; 0;
			case Move(var s, RIGHT) -&gt; 0;
			case Move(var s, UP) -&gt; -s;
			case Move(var s, DOWN) -&gt; +s;
		}
	}

	return new Position(x, y);
}

Of course, there are many other ways to implement the same behavior.
However, records and value types give you some more options for implementations which can be very useful.

答案3

得分: 3

以下是您要翻译的内容:

我们都知道Java社区如此庞大且成熟,以至于他们不会(也不可能)随意添加任何用于实验的随机功能,除非另有说明。记住这一点,我记得OpenJDK网站上有一篇文章,简要描述了Java中“值类型”的想法。需要注意的一件事是,这篇文章是在2014年4月编写/更新的,而“record”首次在2020年3月的Java 14中发布。

但在上述文章中,他们在解释值类型时确实提到了“record”的示例。大部分描述与当前的“record”也相符。

JVM类型系统几乎完全是名义的,而不是结构的。同样,值类型的组件应该通过名称而不仅仅是它们的元素编号来识别。(这使得值类型更像记录而不是元组。)

而且毫不奇怪,Brian Goetz也是这篇文章的共同作者。

但在宇宙的其他地方,“record”也被表示为“数据类”。参见此文章,由Brian编写/更新。有趣的部分在这里。

Values Victor将说“数据类实际上只是一个更透明的值类型。”

现在,考虑所有这些步骤,看起来“record”似乎是受元组、数据类、值类型等等启发(或为其而设计)的一项功能,但它们并不互相替代。

就像Brian在评论中提到的:

解释这里引用的文档的更好方法是元组样式的类型是值类型的一种可能用法,但绝不是唯一的用法。并且可能有需要标识的记录类型。因此,这两者通常会一起工作,但彼此不包容——每个都为表格带来了独特的东西。

至于您对性能增加的担忧,这里有一篇文章,比较了Java 14记录(预览版)与传统类的性能。您可能会觉得这很有趣。从上述链接的结果中,我没有看到性能方面的显著改进。

据我所知,堆栈比堆的速度要快得多。因此,由于“record”实际上只是一个特殊的类,然后进入堆而不是堆栈(值类型/原始类型应该位于堆栈中,就像“int”一样,记住Brian的话“像类一样编码,像int一样工作!”)。顺便说一句,这是我的个人观点,关于堆栈和堆的说法可能是错误的。如果有人纠正我或支持我,我将非常高兴。

英文:

Note: I might not be so correct as this is about future motivations in Java or the intent of the community about value types. The answer is based on my personal knowledge and the information available openly on the internet.

We all know that the Java Community is so big and mature enough that they do not (and could not) add any random feature for experiments until & unless stated otherwise. Keeping this in mind, I remember this article on the OpenJDK website which briefly describes the idea of value types in Java. One thing to notice here is that it is written/updated in April 2014 while the record first came out in Java 14 in March 2020.

But in the above article itself, they did give the example of record while explaining the value types. Most of its description does match to the current record as well.

> The JVM type system is almost entirely nominal as opposed to structural. Likewise, components of value types should be identified by names, not just their element number. (This makes value types more like records than tuples.)

And with no surprise, Brian Goetz was also a co-author of this article.

But there are other places in the universe where the record is also represented as data classes. See this article, it's also written/updated by Brain. The interesting part is here.

> Values Victor will say "a data class is really just a more transparent value type."

Now, considering all these steps together, it does look like record is a feature motivated by (or for) tuples, data classes, value types, etc... etc... but they are not a substitute for each other.

As Brain mentioned in the comments:-

> A better way to interpret the documents cited here that tuple-like types are one possible use for value types, but by far not the only one. And it is possible to have record types that need an identity. So the two will commonly work together, but neither subsumes the other -- each brings something unique to the table.

Coming to your concern about performance increase, here is an article that compared the performance of Java 14 records (preview) vs traditional class. You might find it interesting. I did not see any significant improvements in the performance from the results of the above link.

As far as I know, the stack is significantly faster than that of the heap. So due to the fact that record is actually a special class only, which then goes to the heap than to the stack (value type/primitive type should live in the stack like the int, remember Brian “Codes like a class, works like an int!” ). By the way, this is my personal view, I might be wrong for my statements on stack and heap here. I'll be more than happy to see if anyone corrects me or supports me on this.

huangapple
  • 本文由 发表于 2020年8月11日 13:44:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/63352151.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定