[java] Java에서 일반 배열을 만드는 방법은 무엇입니까?

Java 제네릭의 구현으로 인해 다음과 같은 코드를 가질 수 없습니다.

public class GenSet<E> {
    private E a[];

    public GenSet() {
        a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
    }
}

타입 안전을 유지하면서 이것을 어떻게 구현할 수 있습니까?

Java 포럼에서 다음과 같은 솔루션을 보았습니다.

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

그러나 나는 정말로 무슨 일이 일어나고 있지 않습니다.



답변

당신의 GenSet“체크 된”또는 “체크되지 않은”입니까? 그게 무슨 뜻이야?

  • 확인 : 강력한 타이핑 . GenSet포함하는 객체의 유형을 명시 적으로 알고 있습니다 (즉, 생성자가 Class<E>인수 와 함께 명시 적으로 호출되었으며 , 메소드가 유형이 아닌 인수를 전달하면 예외가 발생합니다 E. 참조) Collections.checkedCollection.

    이 경우 다음과 같이 작성해야합니다.

    public class GenSet<E> {
    
        private E[] a;
    
        public GenSet(Class<E> c, int s) {
            // Use Array native method to create array
            // of a type only known at run time
            @SuppressWarnings("unchecked")
            final E[] a = (E[]) Array.newInstance(c, s);
            this.a = a;
        }
    
        E get(int i) {
            return a[i];
        }
    }
  • 선택하지 않은 경우 : 약한 입력 입니다. 인수로 전달 된 객체에 대해서는 실제로 유형 검사가 수행되지 않습니다.

    이 경우에는

    public class GenSet<E> {
    
        private Object[] a;
    
        public GenSet(int s) {
            a = new Object[s];
        }
    
        E get(int i) {
            @SuppressWarnings("unchecked")
            final E e = (E) a[i];
            return e;
        }
    }

    배열의 구성 요소 유형은 type 매개 변수 의 삭제 여야합니다 .

    public class GenSet<E extends Foo> { // E has an upper bound of Foo
    
        private Foo[] a; // E erases to Foo, so use Foo[]
    
        public GenSet(int s) {
            a = new Foo[s];
        }
    
        ...
    }

이 모든 것은 Java에서 알려진 고의적이며 제네릭의 약점에서 비롯됩니다. 삭제를 사용하여 구현되었으므로 “일반”클래스는 런타임에 어떤 유형 인수로 작성되었는지 알 수 없으므로 유형을 제공 할 수 없습니다. 명시 적 메커니즘 (유형 검사)이 구현되지 않는 한 안전.


답변

당신은 이것을 할 수 있습니다 :

E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];

이것은 효과적인 Java 에서 일반 컬렉션을 구현하는 제안 된 방법 중 하나입니다 . 항목 26 . 유형 오류가 없으며 배열을 반복적으로 캐스팅 할 필요가 없습니다. 그러나 이것은 잠재적으로 위험하므로주의해서 사용해야합니다. 주석에 자세히 설명되어 있듯이 이것은 Object[]현재 E[]유형으로 위장되어 ClassCastException안전하지 않은 경우 예기치 않은 오류나 s 가 발생할 수 있습니다 .

일반적으로이 동작은 캐스트 배열이 내부적으로 (예 : 데이터 구조를 백업하기 위해) 사용되고 클라이언트 코드에 반환되거나 노출되지 않는 한 안전합니다. 제네릭 형식의 배열을 다른 코드로 반환해야하는 경우 Array언급 한 리플렉션 클래스가 올바른 방법입니다.


가능한 List경우 제네릭을 사용하는 경우 배열보다는 s로 작업하는 것이 훨씬 행복 합니다. 확실히 선택의 여지가 없지만 컬렉션 프레임 워크를 사용하는 것이 훨씬 강력합니다.


답변

다음은 제네릭을 사용하여 형식 안전을 유지하면서 찾고있는 형식의 배열을 얻는 방법입니다 (다른 답변과 달리 Object배열 을 반환 하거나 컴파일 타임에 경고를 표시 함).

import java.lang.reflect.Array;

public class GenSet<E> {
    private E[] a;

    public GenSet(Class<E[]> clazz, int length) {
        a = clazz.cast(Array.newInstance(clazz.getComponentType(), length));
    }

    public static void main(String[] args) {
        GenSet<String> foo = new GenSet<String>(String[].class, 1);
        String[] bar = foo.a;
        foo.a[0] = "xyzzy";
        String baz = foo.a[0];
    }
}

당신이 볼 수있는 경고없이 컴파일하고, 같은 main당신의 인스턴스 선언 어떤 유형, GenSet등, 당신은 할당 할 수있는 a해당 유형의 배열에, 당신은에서 요소를 할당 할 수 a배열 즉, 해당 유형의 변수 배열의 값이 올바른 유형입니다.

Java Tutorials 에서 설명한 것처럼 클래스 리터럴을 런타임 유형 토큰으로 사용하여 작동합니다 . 클래스 리터럴은 컴파일러에서의 인스턴스로 처리됩니다 java.lang.Class. 하나를 사용하려면으로 클래스 이름을 따르십시오 .class. 따라서 클래스를 나타내는 객체 String.class로 작동 Class합니다 String. 또한 인터페이스, 열거 형, 모든 차원 배열 (예 🙂 String[].class, 프리미티브 (예 🙂 int.class및 키워드 void(예 :)에서도 작동합니다 void.class.

Class자체는 일반적입니다 (로 선언 Class<T>, 여기서 객체가 나타내는 T유형을 Class나타냄) . 의 유형은 String.class입니다 Class<String>.

따라서에 대한 생성자를 호출 할 때마다 인스턴스의 선언 된 유형의 GenSet배열을 나타내는 첫 번째 인수에 대해 클래스 리터럴을 전달 GenSet합니다 (예 : String[].classfor GenSet<String>). 프리미티브는 유형 변수에 사용할 수 없기 때문에 프리미티브 배열을 얻을 수 없습니다.

생성자 내에서 메서드를 호출하면 메서드 cast가 호출 ObjectClass객체가 나타내는 클래스로 전달 된 인수가 반환 됩니다. 정적 메소드 newInstance를 호출하면 첫 번째 인수로 전달 된 객체와 두 번째 인수로 전달 된 길이로 표시되는 유형의 배열이 java.lang.reflect.Array반환됩니다 . 메소드 호출 복귀 어레이의 구성 형태를 나타내는 객체에 의해 표현 (예를 들어 메소드가 호출되는 객체 에 대해 , 경우 생성 객체 배열을 나타내지 않음).ObjectClassintgetComponentTypeClassClassString.classString[].classnullClass

마지막 문장은 완전히 정확하지 않습니다. 호출 String[].class.getComponentType()반환 Class클래스를 나타내는 객체 String, 그러나 그것의 유형입니다 Class<?>하지, Class<String>당신은 다음과 같은 일을 할 수없는 이유이다.

String foo = String[].class.getComponentType().cast("bar"); // won't compile

객체 Class를 반환하는 모든 메소드에 동일하게 적용됩니다 Class.

이 답변에 대한 Joachim Sauer의 의견에 대해 (나는 스스로 의견을 제시 할만큼 평판이 충분하지 않습니다), 캐스트를 사용하는 예제 T[]는 컴파일러가 유형 안전을 보장 할 수 없기 때문에 경고를 표시합니다.


Ingo의 의견에 관한 편집 :

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}


답변

이것은 안전한 유형의 유일한 답변입니다

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}


답변

더 많은 차원으로 확장하려면에 []및 차원 매개 변수를 추가하십시오 newInstance()( T유형 매개 변수, cls는이며 Class<T>, d1관통 d5하는 정수임).

T[] array = (T[])Array.newInstance(cls, d1);
T[][] array = (T[][])Array.newInstance(cls, d1, d2);
T[][][] array = (T[][][])Array.newInstance(cls, d1, d2, d3);
T[][][][] array = (T[][][][])Array.newInstance(cls, d1, d2, d3, d4);
T[][][][][] array = (T[][][][][])Array.newInstance(cls, d1, d2, d3, d4, d5);

자세한 내용 Array.newInstance()을 참조하십시오.


답변

Java 8에서는 람다 또는 메서드 참조를 사용하여 일종의 일반 배열을 만들 수 있습니다. 이것은 (을 전달하는 Class) 반사 방식과 유사 하지만 여기서는 반사를 사용하지 않습니다.

@FunctionalInterface
interface ArraySupplier<E> {
    E[] get(int length);
}

class GenericSet<E> {
    private final ArraySupplier<E> supplier;
    private E[] array;

    GenericSet(ArraySupplier<E> supplier) {
        this.supplier = supplier;
        this.array    = supplier.get(10);
    }

    public static void main(String[] args) {
        GenericSet<String> ofString =
            new GenericSet<>(String[]::new);
        GenericSet<Double> ofDouble =
            new GenericSet<>(Double[]::new);
    }
}

예를 들어 이것은에 의해 사용됩니다 <A> A[] Stream.toArray(IntFunction<A[]>).

익명 클래스를 사용하여 사전에 자바 (8)을 수행 할 수 있지만, 더 복잡합니다.


답변

이것은 효과적인 Java, 제 2 판 , 항목 25 의 5 장 (Generics)에서 다룹니다 . 배열 목록 선호

코드는 확인되지 않은 경고를 생성하지만 다음 주석으로 억제 할 수는 있지만 작동합니다.

@SuppressWarnings({"unchecked"})

그러나 Array 대신 List를 사용하는 것이 좋습니다.

OpenJDK 프로젝트 사이트 에서이 버그 / 기능에 대한 흥미로운 토론이 있습니다 .