Biorąc pod uwagę następujący kod:
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
}
Wydaje się, oczywiście, na tej tablicy toString
, equals
sposoby są stosowane (zamiast metod statycznych Arrays::equals
, Arrays::deepEquals
bądź Array::toString
).
Sądzę więc, że Java 14 Records ( JEP 359 ) nie działa zbyt dobrze z tablicami, odpowiednie metody muszą zostać wygenerowane za pomocą IDE (które przynajmniej w IntelliJ domyślnie generuje „przydatne” metody, tj. Używają metod statycznych w Arrays
).
Czy jest jakieś inne rozwiązanie?
java
arrays
java-14
java-record
użytkownik140547
źródło
źródło
List
zamiast tablicy?toString()
,equals()
orazhashCode()
sposoby zapisu są realizowane przy użyciu invokedynamic odniesienia. . Gdyby tylko skompilowany ekwiwalent klasy mógł być bliższy temu, coArrays.deepToString
robi dziś metoda w swojej prywatnej, przeciążonej metodzie, mógłby rozwiązać problem pierwotnych przypadków.Object
, ponieważ może się to zdarzyć również w przypadku klas zdefiniowanych przez użytkownika. np. niepoprawny równa sięinvokedynamic
nie ma absolutnie nic wspólnego z wyborem semantyki; indy jest tutaj czystym szczegółem implementacji. Kompilator mógł wysłać kod bajtowy, aby zrobić to samo; był to po prostu bardziej wydajny i elastyczny sposób dotarcia do celu. Podczas projektowania rekordów szeroko dyskutowano, czy należy stosować bardziej szczegółową semantykę równości (np. Głęboką równość dla tablic), ale okazało się, że powoduje to o wiele więcej problemów, niż przypuszczalnie rozwiązano.Odpowiedzi:
Macierze Java stanowią kilka wyzwań dla rekordów, które dodały wiele ograniczeń w projekcie. Tablice są zmienne, a ich semantyka równości (dziedziczona z Object) jest oparta na tożsamości, a nie na zawartości.
Podstawowym problemem związanym z twoim przykładem jest to, że chciałbyś, aby
equals()
tablice oznaczały równość treści, a nie równość odniesienia. (Domyślna) semantyka dlaequals()
rekordów oparta jest na równości składników; w twoim przykładzie dwaFoo
rekordy zawierające odrębne tablice są różne, a rekord zachowuje się poprawnie. Problem polega na tym, że po prostu chciałbyś, aby porównanie równości było inne.To powiedziawszy, możesz zadeklarować rekord z pożądaną semantyką, to po prostu wymaga więcej pracy i możesz poczuć, że to zbyt wiele pracy. Oto rekord, który robi to, co chcesz:
To robi obronną kopię na wejściu (w konstruktorze) i na wyjściu (w akcesorium), a także dostosowuje semantykę równości do korzystania z zawartości tablicy. Wspiera to niezmiennik wymagany w nadklasie
java.lang.Record
, który „rozebranie rekordu na jego komponenty i przebudowanie komponentów na nowy rekord, daje taki sam rekord”.Można powiedzieć: „ale to za dużo pracy, chciałem używać płyt, więc nie musiałem wpisywać tych wszystkich rzeczy”. Ale rekordy nie są przede wszystkim narzędziem składniowym (chociaż są przyjemniejsze składniowo), są narzędziem semantycznym: rekordy są krotkami nominalnymi . Przez większość czasu zwarta składnia daje również pożądaną semantykę, ale jeśli chcesz mieć inną semantykę, musisz wykonać dodatkową pracę.
źródło
List< Integer >
obejścieRozwiązanie: Użyć
List
zInteger
przedmiotów (List< Integer >
) niż szereg pierwotnych (int[]
).W tym przykładzie tworzę instancję niemodyfikowalnej listy nieokreślonej klasy, używając
List.of
funkcji dodanej do Java 9. Równie dobrze możesz użyćArrayList
do modyfikowalnej listy wspieranej przez tablicę.źródło
Obejście: Utwórz
IntArray
klasę i zawińint[]
.Nie idealnie, bo teraz musisz dzwonić
foo.getInts()
zamiastfoo.ints()
, ale wszystko inne działa tak, jak chcesz.Wynik
źródło
record
może składać się z wielu pól, a tylko pola (pola) tablicy są opakowane w ten sposób.List
które zapewniają takie opakowanie, którego można szukać za pomocą proponowanego tutaj rozwiązania. A może uważasz, że takie przypadki użycia mogą być narzutem?int[]
, to aList<Integer>
nie jest takie samo, z co najmniej dwóch powodów: 1) Lista zajmuje dużo więcej pamięci i 2) Nie ma wbudowanej konwersji między nimi.Integer[]
🡘List<Integer>
jest dość łatwe (toArray(...)
iArrays.asList(...)
), aleint[]
🡘List<Integer>
wymaga więcej pracy, a konwersja wymaga czasu, więc jest to coś, czego nie chcesz cały czas robić. Jeśli maszint[]
i chcesz zapisać go w rekordzie (z innymi rzeczami, w przeciwnym razie dlaczego warto go użyć) i potrzebujesz go jakoint[]
, to konwersja za każdym razem, gdy go potrzebujesz, jest błędna.Arrays.equals
, wArrays.toString
porównaniu zList
zastosowaną implementacją, zastępującint[]
jak sugerowano w drugiej odpowiedzi. (Zakładając, że oba są i tak obejściami).