Java 14 레코드 및 배열


11

다음 코드가 주어진다 :

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
List배열 대신 사용 하는 것은 어떻습니까?
올렉

왜 그러한 메소드가 IDE로 생성되어야하는지 이해하지 못합니까? 모든 방법은 손으로 생성 할 수 있어야합니다.
NomadMaker

1
toString(), equals()hashCode()기록 방법이 구현 invokedynamic 참조를 사용. . 컴파일 된 클래스에 해당하는 클래스 만 Arrays.deepToString오늘날 개인 오버로드 된 메소드에서 메소드 가 수행하는 작업에 더 근접 할 있었다면 원시 사례에 대해 해결 되었을 수 있습니다.
Naman

1
구현을위한 디자인 선택의 두 번째로, 이러한 메소드의 재정의 된 구현을 제공하지 않는 무언가에 대해서는 Object사용자 정의 클래스에서도 발생할 수 있기 때문에 나쁜 생각이 아닙니다 . 예 : 부정확 함
Naman

1
@Naman 사용 invokedynamic하는 선택은 의미론의 선택과 전혀 관련이 없습니다. indy는 순수한 구현 세부 사항입니다. 컴파일러는 동일한 작업을 수행하기 위해 바이트 코드를 생성했을 수 있습니다. 이것은 더 효율적이고 유연한 방법이었습니다. 레코드를 디자인하는 동안 더 뉘앙스 드 평등 시맨틱 (예 : 배열에 대한 깊은 평등)을 사용할지 여부에 대해 광범위하게 논의되었지만, 이것은 해결 된 것보다 더 많은 문제를 일으키는 것으로 나타났습니다.
Brian Goetz

답변:


18

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"레코드를 구성 요소로 분리하고 구성 요소를 새 레코드로 재구성하면 동일한 레코드를 생성합니다."

"하지만 그것은 너무 많은 일입니다. 레코드를 사용하고 싶었 기 때문에 모든 것을 입력 할 필요가 없었습니다." 그러나 레코드는 기본적으로 구문 도구가 아니고 (구문 적으로 더 유쾌하지만) 의미 도구입니다. 레코드는 명목상의 튜플 입니다. 대부분의 경우 간결한 구문은 원하는 의미를 생성하지만 다른 의미를 원하면 추가 작업을 수행해야합니다.


6
또한 배열 평등이 내용에 의한 것이기를 원하는 사람들은 일반적으로 아무도 참조로 배열 평등을 원하지 않는다고 가정합니다 . 그러나 그것은 사실이 아닙니다. 모든 경우에 적용되는 답이 하나도 없다는 것입니다. 때로는 참조 평등이 원하는 것입니다.
Brian Goetz

9

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
    }
}

배열보다 List를 선택하는 것이 항상 좋은 생각은 아니며 어쨌든 이것을 논의했습니다.
Naman

@Naman 저는“좋은 아이디어”라는 주장을 한 적이 없습니다. 질문은 말 그대로 다른 해결책을 요구했습니다. 나는 하나를 제공했다. 그리고 당신이 연결 한 의견이 있기 한 시간 전에 그렇게했습니다.
Basil Bourque

5

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

1
그러한 경우에 레코드를 사용하지 말고 클래스를 사용하도록 말하는 것과 같지 않습니까?
Naman

1
@Naman 전혀 아닙니다. record많은 필드로 구성 될 수 있고 배열 필드 만 이와 같이 줄 바꿈 되기 때문 입니다.
Andreas

나는 재사용 성 측면에서 만들려고하는 요점을 얻었지만 List여기에 제안 된대로 솔루션을 찾을 수있는 래핑 클래스 를 제공하는 내장 클래스 가 있습니다. 아니면 그러한 사용 사례에 대한 오버 헤드가 될 수 있다고 생각합니까?
나만

3
@Naman을 저장하고 싶다면 int[]a List<Integer>는 적어도 두 가지 이유 때문에 동일하지 않습니다. Integer[]🡘 List<Integer>쉬운 공정이다 ( toArray(...)Arrays.asList(...)), 그러나 int[]🡘는 List<Integer>더 많은 작업이 소요되며, 변환이 모든 시간을 싶지 않아 그것의 무언가, 그래서 시간이 걸립니다. 를 가지고 있고 int[]다른 물건과 함께 레코드에 저장하고 싶다면 왜 레코드를 사용합니까? int[]를 필요로 할 때마다 변환해야합니다.
Andreas

참 좋은 지적입니다. 당신이 허용하는 경우 비록 하찮은 일에 속 태우고 혜택은 우리가 여전히 함께 참조 할 것을 요청 수 Arrays.equals, Arrays.toString오버 List를 교체하는 동안 사용되는 구현 int[]다른 대답 제안. (둘 다 해결 방법이 있다고 가정합니다.)
Naman
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.