[java] 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).

아니면 다른 해결책이 있습니까?



답변

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 > 해결 방법

해결 방법 : ListInteger객체 ( List< 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
    }
}


답변

해결 방법 : 만들기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


답변