微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决Java记录是否打算最终成为值类型?

JDK 14中引入的record预览功能(JEP 384)是一项伟大的创新。它们使创建简单的不可变的类变得更加容易,这些类是纯值的集合,而不会丢失各种库中通用元组类所固有的上下文。

Brian Goetz(https://openjdk.java.net/jeps/384)对JEP的描述很好地解释了这个意图。但是,我期待与最终引入值类型之间的紧密联系。值类型的最初目标是非常广泛的:通过消除这些类型的对象不需要的所有开销(例如,引用间接,同步),本质上允许对 value 至关重要的对象进行潜在的重大性能改进。 。另外,它可以提供诸如myPosition != yourPosition之类的句法技巧,而不是!myPosition.equals(yourPosition)

似乎记录的限制与潜在值类型所需的限制类型非常接近。然而,JEP并未在动机中提及这些目标。我试图在这些审议中找不到任何公开记录。

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

我提出这个问题的动机是:如果记录成为语言的永久组成部分,那么如果将来的发行版中可能会产生显着的性能优势,那么在代码中采用它们将是一个额外的激励。

解决方法

记录和内联类(值类型的新名称)有很多共同点-它们是隐式最终的,并且是不可变的。因此可以理解的是,两者可能是同一件事。实际上,它们是不同的,并且两者都有共存的空间,但是它们也可以一起工作。

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

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

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

如果您愿意两者兼得,则可以同时获得两套好处-这将是inline record。内联记录有很多用例,因此今天记录的类明天可能成为内联记录,并且只会变得更快。

但是,我们不想强制所有记录内联或强制所有内联成为记录。有一些内联类想要使用封装,而一些记录则需要身份(以便它们可以组织成树或图),这很好。

,

注意:我可能不太正确,因为这与Java的未来动机或社区有关值类型的意图有关。答案是基于我的个人知识以及在Internet上公开可用的信息。

我们都知道Java社区是如此之大和成熟,以至于除非&另有说明,否则他们不会(也不能)为实验添加任何随机功能。记住这一点,我记得OpenJdk网站上的this article,简要描述了Java中value types的思想。这里要注意的一件事是,它是在2014年4月编写/更新的,而record于2020年3月在Java 14中首次出现。

但是在上面的文章中,他们在解释值类型时确实给出了record的示例。它的大多数描述也确实与当前的record相匹配。

JVM类型系统几乎完全是名义上的,而不是结构上的。同样,值类型的组成部分应通过名称标识,而不仅仅是其元素编号。 (这使值类型更像记录而不是元组。)

毫无疑问,Brian Goetz还是本文的合著者。

但是在宇宙中的其他地方,record也被表示为data classes。参见this article,它也是Brain编写/更新的。有趣的部分is here

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

现在,综合考虑所有这些步骤,看起来record确实是受(或针对)元组,数据类,值类型等...(在Java中)驱动的功能。只能拥有一个可以同时被感知到的功能。

出于对性能提升的关注,这里an article比较了Java 14记录(预览)与传统类的性能。您可能会发现它很有趣。从以上链接的结果来看,我没有看到性能上的任何显着改善。

据我所知,堆栈比堆快得多。因此,由于record实际上只是一个特殊的类,该类然后进入堆而不是栈(值类型/基元类型应像int一样存在于栈中,请记住Brian“像类一样的代码,像int一样工作!”)。顺便说一句,这是我的个人观点,我在这里对堆栈和堆的声明可能不对。我很高兴看到有人对此进行纠正或支持。

,

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

有两个关于数据类(即记录与值类型)的有趣文档: 2018 年 2 月的旧版本
http://cr.openjdk.java.net/~briangoetz/amber/datum_2.html#are-data-classes-the-same-as-value-types
以及 2019 年 2 月的更新版本
https://cr.openjdk.java.net/~briangoetz/amber/datum.html#are-records-the-same-as-value-types

每个文档都包含一段关于记录和值类型之间差异的段落。 旧版本说

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

此外,

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

然而,

但是值类不需要放弃任何封装,实际上封装对于值类型的一些应用来说是必不可少的

让我们澄清一下:

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

让我们举一些例子来说明这一切。

例如您将能够使用记录创建复合逻辑表达式,但不能使用值类型:

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 {}

这不适用于值类型,因为这需要具有相同值类型的自引用能力。 您希望能够创建诸如“Not(Not(Val(true)))”之类的表达式。

例如您还可以使用记录来定义 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; }
    // operations like add,sub,mult or div
}

如何计算那个分数的浮点值? 您可以将方法 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; }
    // operations like add,mult or div
}

当然,隐藏字段可能是您想要使用值类型的原因之一。 它们只是一方面,但可能是次要的。 如果您创建了多个 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);
}

当然,还有许多其他方法可以实现相同的行为。 但是,记录和值类型为您提供了一些非常有用的实现选项。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?