17

给定以下代码:

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
}

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

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

或者有没有其他解决方案?

4

3 回答 3

28

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

您的示例的基本问题是您希望equals()在数组上意味着内容相等,而不是引用相等。for 记录的(默认)语义equals()基于组件的相等性;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)); }
}

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

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

于 2020-04-17T13:48:13.703 回答
11

List< Integer >解决方法

解决方法:使用对象 ( )List而不是基元数组 ( )。IntegerList< 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
    }
}
于 2020-04-17T01:08:46.033 回答
6

解决方法:创建一个IntArray类并将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
于 2020-04-16T23:05:37.827 回答