문제

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

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

    배열의 구성 요소 유형은 지워 없앰 유형 매개 변수 :

    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에서 알려진 의도적이고 고의적 인 제네릭의 약점에서 비롯된 것입니다. Erasure를 사용하여 구현되었으므로 "제네릭"클래스는 런타임에 어떤 유형의 인수가 생성되었는지 알지 못하므로 유형을 제공 할 수 없습니다. 안전한 메커니즘 (유형 확인)이 구현되지 않는 한 안전.

다른 팁

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

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

이것은 일반 컬렉션을 구현하는 제안 된 방법 중 하나입니다. 효과적인 자바; 항목 26. 유형 오류가없고 배열을 반복적으로 캐스팅 할 필요가 없습니다. 하지만 이것은 잠재적으로 위험하기 때문에 경고를 유발하며주의해서 사용해야합니다. 주석에 자세히 설명 된 바와 같이, 이것 Object[] 이제 우리처럼 가장하고 있습니다 E[] 입력하고 예상치 못한 오류를 일으킬 수 있습니다 ClassCastExceptionS 불필요하게 사용되는 경우.

일반적으로,이 동작은 내부적으로 캐스트 배열을 사용하는 한 (예 : 데이터 구조를 뒷받침하는 데), 클라이언트 코드에 반환하거나 노출되지 않는 한 안전합니다. 일반 유형의 배열을 다른 코드에 반환 해야하는 경우 반사 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.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[].class ~을 위한 GenSet<String>). 프리미티브는 유형 변수에 사용할 수 없으므로 프리미티브 배열을 얻을 수 없습니다.

생성자 내부에서 방법을 호출합니다 cast 통과 된 것을 반환합니다 Object 인수는 Class 메소드가 호출 된 객체. 정적 방법을 호출합니다 newInstance 안에 java.lang.reflect.Array 로 반환합니다 Object 로 표시된 유형의 배열 Class 객체는 첫 번째 인수와 int 두 번째 논쟁으로 통과되었습니다. 방법을 호출합니다 getComponentType 반환 a Class 배열의 구성 요소 유형을 나타내는 객체 Class 방법이 호출 된 물체 (예 : String.class ~을 위한 String[].class, null 만약 Class 객체는 배열을 나타내지 않습니다).

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

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

모든 방법에 대해서도 마찬가지입니다 Class a 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);
}

더 많은 치수로 확장하려면 추가하십시오 []S 및 치수 매개 변수로 newInstance() (T 유형 매개 변수입니다. cls a 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에서는 Lambda 또는 Method Reference를 사용하여 일종의 일반적인 배열 생성을 수행 할 수 있습니다. 이것은 반사 접근법과 유사합니다 (통과합니다. 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을 수행하지만 더 번거롭습니다.

이것은 5 장 (제네릭)으로 덮여 있습니다. 효과적인 Java, 2 판, 항목 25 ...배열에 목록을 선호합니다

코드는 확인되지 않은 경고가 생성되지만 다음과 같은 주석으로 억제 할 수 있습니다.

@SuppressWarnings({"unchecked"})

그러나 배열 대신 목록을 사용하는 것이 좋습니다.

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

Java Generics는 컴파일 시간에 유형을 확인하고 적절한 캐스트를 삽입하여 작동하지만 지우기 컴파일 된 파일의 유형. 이로 인해 제네릭 라이브러리는 제네릭을 이해하지 못하는 코드 (고의적 인 디자인 결정)를 이해하지 못하지만 일반적으로 실행 시간에 유형이 무엇인지 알 수 없습니다.

공개 Stack(Class<T> clazz,int capacity) 생성자는 실행 시간에 클래스 객체를 전달해야합니다. 이는 클래스 정보를 의미합니다. ~이다 런타임에 필요한 코드로 제공됩니다. 그리고 Class<T> 형태는 컴파일러가 전달한 클래스 객체가 T 형의 서브 클래스가 아니라 t의 슈퍼 클래스가 아니라 정확하게 t의 클래스 객체인지 확인한다는 것을 의미합니다.

이것은 생성자에서 적절한 유형의 배열 객체를 만들 수 있음을 의미합니다. 즉, 컬렉션에 저장 한 개체의 유형이 컬렉션에 추가되는 지점에서 유형을 확인할 수 있습니다.

안녕하세요, 실이 죽었지 만 이것에 대한 관심을 끌고 싶습니다.

제네릭은 컴파일 시간 동안 유형 확인에 사용됩니다.

  • 그러므로 목적은 당신이 필요한 것이 무엇인지 확인하는 것입니다.
  • 당신이 반환하는 것은 소비자가 필요로하는 것입니다.
  • 이것을 확인하십시오 :

enter image description here

일반적인 수업을 작성할 때 타임 캐스트 경고에 대해 걱정하지 마십시오. 당신이 그것을 사용할 때 걱정하십시오.

이 솔루션은 어떻습니까?

@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
    return elems;
}

그것은 작동하고 너무 단순 해 보이기 어렵게 보입니다. 단점이 있습니까?

이 코드도보십시오.

public static <T> T[] toArray(final List<T> obj) {
    if (obj == null || obj.isEmpty()) {
        return null;
    }
    final T t = obj.get(0);
    final T[] res = (T[]) Array.newInstance(t.getClass(), obj.size());
    for (int i = 0; i < obj.size(); i++) {
        res[i] = obj.get(i);
    }
    return res;
}

모든 종류의 객체 목록을 동일한 유형의 배열로 변환합니다.

나는 나를 위해 일하는 빠르고 쉬운 방법을 찾았습니다. Java JDK 8에서만 사용했습니다. 이전 버전에서 작동하는지 모르겠습니다.

특정 유형 매개 변수의 일반적인 배열을 인스턴스화 할 수는 없지만 이미 생성 된 배열을 일반 클래스 생성자로 전달할 수 있습니다.

class GenArray <T> {
    private T theArray[]; // reference array

    // ...

    GenArray(T[] arr) {
        theArray = arr;
    }

    // Do whatever with the array...
}

이제 주로 우리는 다음과 같은 배열을 만들 수 있습니다.

class GenArrayDemo {
    public static void main(String[] args) {
        int size = 10; // array size
        // Here we can instantiate the array of the type we want, say Character (no primitive types allowed in generics)
        Character[] ar = new Character[size];

        GenArray<Character> = new Character<>(ar); // create the generic Array

        // ...

    }
}

배열에 대한 유연성을 높이려면 링크 된 목록을 사용할 수 있습니다. java.util.arraylist 클래스에있는 Arraylist 및 기타 방법.

클래스 인수를 생성자에게 전달할 필요는 없습니다. 이 시도.

public class GenSet<T> {
    private final T[] array;
    @SuppressWarnings("unchecked")
    public GenSet(int capacity, T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
              "Do not provide values for dummy argument.");
        Class<?> c = dummy.getClass().getComponentType();
        array = (T[])Array.newInstance(c, capacity);
    }
    @Override
    public String toString() {
        return "GenSet of " + array.getClass().getComponentType().getName()
            + "[" + array.length + "]";
    }
}

그리고

GenSet<Integer> intSet = new GenSet<>(3);
System.out.println(intSet);
System.out.println(new GenSet<String>(2));

결과:

GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]

예제는 Java 반사를 사용하여 배열을 만듭니다. 이 작업을 수행하는 것은 일반적으로 TypeSafe가 아니기 때문에 권장되지 않습니다. 대신, 당신이해야 할 일은 내부 목록을 사용하고 배열을 전혀 피하는 것입니다.

나는이 코드 스 니펫을 만들어 간단한 자동 테스트 유틸리티를 위해 전달되는 클래스를 반사적으로 인스턴스화했습니다.

Object attributeValue = null;
try {
    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }
    else if(!clazz.isInterface()){
        attributeValue = BeanUtils.instantiateClass(clazz);
    }
} catch (Exception e) {
    logger.debug("Cannot instanciate \"{}\"", new Object[]{clazz});
}

이 세그먼트에 유의하십시오.

    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }

배열을 시작하는 곳 Array.newinstance (배열 클래스, 배열 크기). 클래스는 원시 (int.class)와 객체 (integer.class) 일 수 있습니다.

Beanutils는 봄의 일부입니다.

실제로 그렇게하는 더 쉬운 방법은 객체 배열을 만들어 다음 예와 같이 원하는 유형으로 캐스팅하는 것입니다.

T[] array = (T[])new Object[SIZE];

어디 SIZE 일정하고 T 유형 식별자입니다

값 목록 전달 ...

public <T> T[] array(T... values) {
    return values;
}

다른 사람들이 제안한 강제 캐스트는 나를 위해 일하지 않았으며 불법 캐스팅을 제외하고는 효과가 없었습니다.

그러나이 암시 적 캐스트는 잘 작동했습니다.

Item<K>[] array = new Item[SIZE];

여기서 항목은 멤버를 포함하는 클래스를 정의했습니다.

private K value;

이렇게하면 유형 k 배열 (항목에 값만있는 경우) 또는 클래스 항목에 정의하려는 일반 유형을 얻습니다.

당신이 게시 한 예에서 무슨 일이 일어나고 있는지에 대한 다른 사람은 아무도 대답하지 않았습니다.

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

다른 사람들이 말했듯이 편집 중에 제네릭이 "지워진 "다. 따라서 런타임에 일반적인 인스턴스는 구성 요소 유형이 무엇인지 알 수 없습니다. 그 이유는 역사적이기 때문입니다. Sun은 기존 인터페이스 (소스와 이진 모두)를 깨지 않고 제네릭을 추가하고 싶었습니다.

반면에 배열 하다 런타임에서 구성 요소 유형을 알고 있습니다.

이 예제는 생성자 (유형을 알고있는)를 호출하는 코드를 사용하여 클래스에 필요한 유형을 알려주는 매개 변수를 전달하여 문제를 해결합니다.

그래서 응용 프로그램은

Stack<foo> = new Stack<foo>(foo.class,50)

그리고 생성자는 이제 구성 요소 유형이 무엇인지 알고 있으며 해당 정보를 사용하여 반사 API를 통해 배열을 구성 할 수 있습니다.

Array.newInstance(clazz, capacity);

마지막으로 컴파일러가 배열이 Array#newInstance() 올바른 유형입니다 (우리가 알고 있지만).

이 스타일은 약간 추악하지만 때로는 런타임에서 구성 요소 유형을 알아야하는 일반 유형을 만드는 데 가장 나쁜 솔루션이 될 수 있습니다 (배열 생성 또는 구성 요소 유형의 인스턴스 작성 등).

나는이 문제에 대한 일종의 연구를 발견했다.

아래 줄은 일반적인 배열 생성 오류를 던집니다

List<Person>[] personLists=new ArrayList<Person>()[10];

그러나 내가 캡슐화하면 List<Person> 별도의 수업에서는 작동합니다.

import java.util.ArrayList;
import java.util.List;


public class PersonList {

    List<Person> people;

    public PersonList()
    {
        people=new ArrayList<Person>();
    }
}

당신은 수업에 사람을 노출시킬 수 있습니다. 아래 줄은 당신에게 배열을 줄 것입니다. List<Person> 모든 요소에서. 다시 말해서 배열 List<Person>.

PersonList[] personLists=new PersonList[10];

나는 내가 작업하고 있던 일부 코드에서 이와 같은 것이 필요했고 이것이 내가 그것을 작동시키기 위해 한 일입니다. 지금까지 문제가 없습니다.

객체 배열을 생성하고 어디서나 E로 캐스트 할 수 있습니다. 예, 그것은 매우 깨끗한 방법은 아니지만 적어도 작동해야합니다.

이 시도.

private int m = 0;
private int n = 0;
private Element<T>[][] elements = null;

public MatrixData(int m, int n)
{
    this.m = m;
    this.n = n;

    this.elements = new Element[m][n];
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            this.elements[i][j] = new Element<T>();
        }
    }
}

이에 대한 지저분한 해결 방법은 쉽지 않지만 메인 클래스 내부의 두 번째 "홀더"클래스를 중첩하고 데이터를 보유하는 데 사용하는 것입니다.

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}

어쩌면이 질문과 관련이 없지만 내가 얻는 동안 "generic array creation"사용에 대한 오류

Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];

나는 다음과 같은 작품을 발견했다 (그리고 나를 위해 일했다) @SuppressWarnings({"unchecked"}):

 Tuple<Long, String>[] tupleArray = new Tuple[10];

이 코드가 효과적인 일반 배열을 만들 것인지 궁금합니다.

public T [] createArray(int desiredSize){
    ArrayList<T> builder = new ArrayList<T>();
    for(int x=0;x<desiredSize;x++){
        builder.add(null);
    }
    return builder.toArray(zeroArray());
}

//zeroArray should, in theory, create a zero-sized array of T
//when it is not given any parameters.

private T [] zeroArray(T... i){
    return i;
}

편집 : 아마도 그러한 배열을 생성하는 대체 방법, 필요한 크기가 알려져 있고 작 으면 필요한 수의 "null"을 ZeroArray 명령에 공급하는 것입니까?

분명히 이것은 CreateArray 코드를 사용하는 것만 큼 다재다능하지는 않습니다.

캐스트를 사용할 수 있습니다.

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

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}

나는 실제로 일반적인 배열을 시작할 수없는 것을 우회 할 수있는 매우 독특한 솔루션을 발견했습니다. 당신이해야 할 일은 다음과 같은 일반적인 변수 t를 차지하는 클래스를 만드는 것입니다.

class GenericInvoker <T> {
    T variable;
    public GenericInvoker(T variable){
        this.variable = variable;
    }
}

그런 다음 배열 클래스에서 그렇게 시작하십시오.

GenericInvoker<T>[] array;
public MyArray(){
    array = new GenericInvoker[];
}

시작 a new Generic Invoker[] 확인되지 않은 문제를 일으키지 만 실제로 문제가 없어야합니다.

배열에서 얻으려면 배열을 호출해야합니다. [i].

public T get(int index){
    return array[index].variable;
}

배열 크기 조정과 같은 나머지는 Array.copyof ()와 같이 수행 할 수 있습니다.

public void resize(int newSize){
    array = Arrays.copyOf(array, newSize);
}

추가 기능은 다음과 같이 추가 할 수 있습니다.

public boolean add(T element){
    // the variable size below is equal to how many times the add function has been called 
    // and is used to keep track of where to put the next variable in the array
    arrays[size] = new GenericInvoker(element);
    size++;
}
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}

일반 어레이 생성은 Java에서 허용되지 않지만 그렇게 할 수 있습니다.

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top