Java 14 记录和数组

use*_*547 15 java arrays java-14 java-record

鉴于以下代码:

public static void main(String[] args) {
    record Foo(int[] ints){}

    var ints = new int[]{1, 2};
    var foo = new Foo(ints);
    System.out.println(foo); // Foo[ints=[I@6433a2]
    System.out.println(new Foo(new int[]{1,2}).equals(new Foo(new int[]{1,2}))); // false
    System.out.println(new Foo(ints).equals(new Foo(ints))); //true
    System.out.println(foo.equals(foo)); // true
}
Run Code Online (Sandbox Code Playgroud)

显然,似乎使用了数组的toString,equals方法(而不是静态方法、Arrays::equalsArrays::deepEqualsArray::toString)。

所以我猜 Java 14 Records ( JEP 359 ) 不能很好地处理数组,必须使用 IDE 生成相应的方法(至少在 IntelliJ 中,默认情况下会生成“有用”的方法,即它们使用静态方法在Arrays)。

或者还有其他解决方案吗?

Bri*_*etz 26

Java 数组对记录提出了几个挑战,这些挑战为设计增加了许多限制。数组是可变的,它们的相等语义(从 Object 继承)是由身份决定的,而不是内容。

您的示例的基本问题是您希望equals()数组意味着内容相等,而不是引用相等。equals()for记录的(默认)语义基于组件的相等性;在例子中,两个Foo含有不同的阵列记录不同的,并且该记录被正确地表现。问题是你只是希望平等比较是不同的。

也就是说,你可以用你想要的语义声明一个记录,它只是需要更多的工作,你可能会觉得这是太多的工作。这是一个可以满足您要求的记录:

record Foo(String[] ss) {
    Foo { ss = ss.clone(); }
    String[] ss() { return ss.clone(); }
    boolean equals(Object o) { 
        return o instanceof Foo 
            && Arrays.equals(((Foo) o).ss, ss);
    }
    int hashCode() { return Objects.hash(Arrays.hashCode(ss)); }
}
Run Code Online (Sandbox Code Playgroud)

它所做的是在进入(在构造函数中)和退出(在访问器中)的过程中进行防御性复制,以及调整相等语义以使用数组的内容。这支持超类中所需的不变量,java.lang.Record即“将记录分解为其组件,然后将组件重建为新记录,产生相等的记录”。

您可能会说“但是工作量太大了,我想使用记录,所以我不必输入所有这些东西。” 但是,记录主要不是一种句法工具(尽管它们在句法上更令人愉快),它们是一种语义工具:记录是名义元组。大多数情况下,紧凑的语法也会产生所需的语义,但如果您想要不同的语义,则必须做一些额外的工作。

  • 此外,那些希望数组通过内容相等的人会犯一个常见的错误,那就是假设_没有人_希望通过引用实现数组相等。但事实并非如此。只是没有一个答案适用于所有情况。有时引用相等正是您想要的。 (8认同)
  • @jcsahnwaldtReinstateMonica 记录通常有多个组件;明智的做法是计算每个组件的哈希值,然后组合这些哈希值(例如使用“Objects.hash(Object...)”)。这看起来很奇怪,因为这里只有一个组件。 (4认同)

Bas*_*que 11

List< Integer > 解决方法

解决方法:使用一个ListInteger对象(List< Integer >),而不是基元的阵列(int[])。

在此示例中,我使用Java 9 中添加的功能实例化了一个指定类的不可修改列表List.of。您也可以将ArrayList, 用于由数组支持的可修改列表。

package work.basil.example;

import java.util.List;

public class RecordsDemo
{
    public static void main ( String[] args )
    {
        RecordsDemo app = new RecordsDemo();
        app.doIt();
    }

    private void doIt ( )
    {

        record Foo(List < Integer >integers)
        {
        }

        List< Integer > integers = List.of( 1 , 2 );
        var foo = new Foo( integers );

        System.out.println( foo ); // Foo[integers=[1, 2]]
        System.out.println( new Foo( List.of( 1 , 2 ) ).equals( new Foo( List.of( 1 , 2 ) ) ) ); // true
        System.out.println( new Foo( integers ).equals( new Foo( integers ) ) ); // true
        System.out.println( foo.equals( foo ) ); // true
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 为了确保不能使用可变列表创建记录,您应该在记录构造函数内执行“List.copyOf”。`copyOf` 只会根据需要创建一个不可变的副本,并且如果列表已经是不可变的,则它是一个无操作。 (4认同)
  • @Naman 我从未声称自己是一个“好主意”。这个问题实际上要求其他解决方案。我提供了一份。我在你链接的评论前一个小时这样做了。 (2认同)

And*_*eas 6

解决方法:创建一个IntArray类并将int[].

record Foo(IntArray ints) {
    public Foo(int... ints) { this(new IntArray(ints)); }
    public int[] getInts() { return this.ints.get(); }
}
Run Code Online (Sandbox Code Playgroud)

不完美,因为您现在必须调用foo.getInts()而不是foo.ints(),但其他一切都按您想要的方式工作。

public final class IntArray {
    private final int[] array;
    public IntArray(int[] array) {
        this.array = Objects.requireNonNull(array);
    }
    public int[] get() {
        return this.array;
    }
    @Override
    public int hashCode() {
        return Arrays.hashCode(this.array);
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || getClass() != obj.getClass())
            return false;
        IntArray that = (IntArray) obj;
        return Arrays.equals(this.array, that.array);
    }
    @Override
    public String toString() {
        return Arrays.toString(this.array);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出

record Foo(IntArray ints) {
    public Foo(int... ints) { this(new IntArray(ints)); }
    public int[] getInts() { return this.ints.get(); }
}
Run Code Online (Sandbox Code Playgroud)

  • @Naman如果你想存储一个`int[]`,那么一个`List&lt;Integer&gt;`是不一样的,至少有两个原因:1)列表使用更多的内存,2)没有构建- 它们之间的转换。`Integer[]` `List&lt;Integer&gt;` 相当简单(`toArray(...)` 和 `Arrays.asList(...)`),但是 `int[]` `List&lt;Integer&gt;` 需要更多工作,并且转换需要时间,因此您不想一直这样做。如果你有一个 `int[]` 并且想将它存储在一个记录中(与其他东西一起存储,否则为什么要使用记录),并且需要它作为一个 `int[]`,那么每次需要它时都进行转换是错误的。 (3认同)