Java 14条记录和数组

Java 14 records and arrays

给定以下代码:

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
}

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

所以我猜 Java 14 条记录 (JEP 359) 不能很好地处理数组,必须使用 IDE 生成相应的方法(至少在IntelliJ,默认生成 "useful" 方法,即它们使用 Arrays 中的静态方法)。

或者有其他解决办法吗?

解决方法: 创建一个 IntArray class 并包装 int[].

record Foo(IntArray ints) {
    public Foo(int... ints) { this(new IntArray(ints)); }
    public int[] getInts() { return this.ints.get(); }
}

不完美,因为你现在必须调用 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);
    }
}

输出

Foo[ints=[1, 2]]
true
true
true

List< Integer > 解决方法

解决方法:使用 List of Integer 个对象 (List< Integer >) 而不是基元数组 (int[])。

在这个例子中,我为一个由数组支持的可修改列表实例化了一个 unmodifiable list of unspecified class by using the List.of feature added to Java 9. You could just as well use 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
    }
}

Java 数组对记录提出了一些挑战,这些给设计增加了一些限制。数组是可变的,它们的相等语义(从 Object 继承)是通过标识,而不是内容。

您的示例的基本问题是您希望数组上的 equals() 意味着内容相等,而不是引用相等。记录的 equals() 的(默认)语义基于组件的相等性;在您的示例中,包含不同数组 的两个 Foo 记录是 不同的,并且记录的行为正确。问题是您只是希望平等比较有所不同。

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

record Foo(String[] ss) {
    Foo { ss = ss.clone(); }
    String[] ss() { return ss.clone(); }
    public boolean equals(Object o) { 
        return o instanceof Foo f && Arrays.equals(f.ss, ss);
    }
    public int hashCode() { return Objects.hash(Arrays.hashCode(ss)); }
}

它所做的是在传入(在构造函数中)和传出(在访问器中)时的防御性副本,以及调整相等语义以使用数组的内容。这支持超类 java.lang.Record 中要求的不变量,即“将记录分解为其组件,并将组件重建为新记录,产生相等的记录。”

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