Java 14 записей и массивов - PullRequest
11 голосов
/ 17 апреля 2020

Учитывая следующий код:

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 используются (вместо методов stati c, Arrays::equals, Arrays::deepEquals или Array::toString).

Итак, я думаю, Java 14 записей ( JEP 359 ) не очень хорошо работают с массивами, соответствующие методы должны быть сгенерированы с помощью IDE ( который, по крайней мере в IntelliJ, по умолчанию генерирует «полезные» методы, т.е. они используют методы stati c в Arrays).

Или есть другое решение?

Ответы [ 3 ]

18 голосов
/ 17 апреля 2020

Java массивы создают несколько проблем для записей, и это добавляет ряд ограничений к дизайну. Массивы являются изменяемыми, и их семантика равенства (унаследованная от Object) является идентичностью, а не содержимым.

Основная проблема c в вашем примере заключается в том, что sh означает, что 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, который «разбивая запись на ее компоненты и восстанавливая компоненты в новую запись, дает равную запись».

Вы вполне могли бы сказать: «Но это слишком много работы, я хотел использовать записи, чтобы мне не приходилось печатать все эти вещи». Но записи не являются в первую очередь инструментом syntacti c (хотя они синтаксически более приятны), они являются инструментом semanti c: записи являются именными кортежами . В большинстве случаев компактный синтаксис также дает желаемую семантику, но если вам нужна другая семантика, вам придется проделать дополнительную работу.

9 голосов
/ 17 апреля 2020

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
    }
}
5 голосов
/ 17 апреля 2020

Обходной путь: Создать 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...