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
}
似乎,显然,使用了数组的toString
、equals
方法(而不是静态方法,Arrays::equals
、Arrays::deepEquals
或Array::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
中要求的不变量,即“将记录分解为其组件,并将组件重建为新记录,产生相等的记录。”
您可能会说“但这太麻烦了,我想使用记录,所以我不必输入所有这些东西。”但是,记录主要不是一种句法工具(尽管它们在句法上更令人愉快),它们是一种语义工具:记录是 名义元组 。大多数时候,紧凑的语法也能产生所需的语义,但如果你想要不同的语义,你必须做一些额外的工作。
给定以下代码:
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::deepEquals
或Array::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
中要求的不变量,即“将记录分解为其组件,并将组件重建为新记录,产生相等的记录。”
您可能会说“但这太麻烦了,我想使用记录,所以我不必输入所有这些东西。”但是,记录主要不是一种句法工具(尽管它们在句法上更令人愉快),它们是一种语义工具:记录是 名义元组 。大多数时候,紧凑的语法也能产生所需的语义,但如果你想要不同的语义,你必须做一些额外的工作。