Записи и массивы Java 14
Учитывая следующий код:
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, по умолчанию генерирует "полезные" методы, т.е. они используют статические методы. вArrays
).
Или есть другое решение?
3 ответа
Массивы Java создают несколько проблем для записей, и это добавило ряд ограничений к конструкции. Массивы изменяемы, и их семантика равенства (унаследованная от Object) определяется идентичностью, а не содержимым.
Основная проблема с вашим примером заключается в том, что вы хотите, чтобы equals()
для массивов означало равенство содержимого, а не равенство ссылок. Семантика (по умолчанию) для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
, что "разделение записи на компоненты и реконструкция компонентов в новую запись дает одинаковую запись".
Вы могли бы сказать: "Но это слишком много работы, я хотел использовать записи, чтобы не печатать все это". Но записи - это в первую очередь не синтаксический инструмент (хотя они синтаксически более приятны), они являются семантическим инструментом: записи представляют собой номинальные кортежи. В большинстве случаев компактный синтаксис также дает желаемую семантику, но если вам нужна другая семантика, вам придется проделать некоторую дополнительную работу.
List< Integer >
обходной путь
Решение: используйте List
из Integer
объекты (List< Integer >
), а не массив примитивов (int[]
).
В этом примере я создаю неизменяемый список неуказанного класса с помощью List.of
в Java 9 добавлена функция. С таким же успехом можно использовать 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
}
}
Решение: создайте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