[java] Java : 제네릭 형식에서 클래스 리터럴을 어떻게 얻습니까?

일반적으로 사람들이 다음과 같이 클래스 리터럴을 사용하는 것을 보았습니다.

Class<Foo> cls = Foo.class;

그러나 유형이 일반적인 경우 목록은 무엇입니까? 이것은 잘 작동하지만 List가 매개 변수화되어야하기 때문에 경고가 있습니다.

Class<List> cls = List.class

왜 추가하지 <?>않습니까? 글쎄, 이것은 유형 불일치 오류를 발생시킵니다.

Class<List<?>> cls = List.class

나는 이런 식으로 작동 할 것이라고 생각했지만 이것은 단지 일반적인 ol ‘구문 오류입니다.

Class<List<Foo>> cls = List<Foo>.class

어떻게받을 수 있습니까? Class<List<Foo>>예를 들어 클래스 리터럴을 사용하여 정적으로 있습니까?

내가 할 수 사용하는 @SuppressWarnings("unchecked")첫 번째 예제에서 목록의 비 매개 변수 사용으로 인한 경고 없애,Class<List> cls = List.class 하지만, 차라리하지 않는 게 좋을.

어떤 제안?



답변

삭제 유형 으로 인해 수 없습니다 .

Java 제네릭은 Object 캐스트의 구문 설탕에 지나지 않습니다. 시연하려면 :

List<Integer> list1 = new ArrayList<Integer>();
List<String> list2 = (List<String>)list1;
list2.add("foo"); // perfectly legal

런타임에 일반 형식 정보가 유지되는 유일한 경우는 Field.getGenericType()리플렉션을 통해 클래스 멤버를 심문하는 경우입니다.

이 모든 Object.getClass()것이이 서명 이있는 이유입니다 .

public final native Class<?> getClass();

중요한 부분은 Class<?>입니다.

Java Generics FAQ 에서 다른 방법으로 넣으려면 :

구체적인 매개 변수화 된 유형에 대한 클래스 리터럴이없는 이유는 무엇입니까?

매개 변수화 된 유형에는 정확한 런타임 유형 표현이 없기 때문입니다.

클래스 리터럴은 Class
주어진 유형을 나타내는 객체를 나타냅니다. 예를 들어 클래스 리터럴
String.classClass
유형을 나타내는 객체를 나타내며
객체에서 메서드 를 호출
할 때 반환 String되는 Class객체 와 동일
합니다. 클래스 리터럴은 런타임 유형 확인 및 반영에 사용될 수 있습니다.getClassString

매개 변수화 된 유형은 유형 삭제라는 프로세스에서 컴파일 중에 바이트 코드로 변환 될 때 유형 인수를 잃습니다. 유형 삭제의 부작용으로 일반 유형의 모든 인스턴스화는 동일한 런타임 표현, 즉 해당 원시 유형의 인스턴스화를 공유합니다. 다시 말해, 매개 변수화 된 유형에는 자체 유형이 없습니다. 따라서,이 같은 클래스 리터럴을 형성 소용이없고 List<String>.class,
List<Long>.class그리고 List<?>.class
그러한 때문에, Class물체가 존재하지 않는다. 만 원시 타입은 ListClass
자사의 실행시의 형태를 나타내는 개체를. 이라고합니다
List.class.


답변

매개 변수화 된 형식에 대한 클래스 리터럴은 없지만 이러한 형식을 올바르게 정의하는 Type 개체가 있습니다.

java.lang.reflect.ParameterizedType 참조-http: //java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/ParameterizedType.html

Google의 Gson 라이브러리는 단순히 매개 변수화 된 유형을 생성하고이를 사용하여 복잡한 매개 변수화 된 유형을 가진 json 객체를 일반적인 친숙한 방식으로 지정하는 데 사용되는 TypeToken 클래스를 정의합니다. 귀하의 예에서는 다음을 사용합니다.

Type typeOfListOfFoo = new TypeToken<List<Foo>>(){}.getType()

javadoc TypeToken 및 Gson 클래스에 대한 링크를 게시하려고했지만 새 사용자이므로 Stack Overflow에서 둘 이상의 링크를 게시 할 수 없으므로 Google 검색을 사용하여 쉽게 찾을 수 있습니다


답변

이중 캐스트로 관리 할 수 ​​있습니다.

@SuppressWarnings("unchecked")
Class<List<Foo>> cls = (Class<List<Foo>>)(Object)List.class


답변

cletus의 답변을 설명하기 위해 런타임에 일반 유형의 모든 레코드가 제거됩니다. 제네릭은 컴파일러에서만 처리되며 추가 형식 안전성을 제공하는 데 사용됩니다. 그것들은 컴파일러가 적절한 장소에 타입 캐스트를 삽입 할 수있게 해주는 약칭입니다. 예를 들어 이전에는 다음을 수행해야했습니다.

List x = new ArrayList();
x.add(new SomeClass());
Iterator i = x.iterator();
SomeClass z = (SomeClass) i.next();

된다

List<SomeClass> x = new ArrayList<SomeClass>();
x.add(new SomeClass());
Iterator<SomeClass> i = x.iterator();
SomeClass z = i.next();

이를 통해 컴파일러는 컴파일 타임에 코드를 확인할 수 있지만 런타임에는 여전히 첫 번째 예제처럼 보입니다.


답변

자바 제네릭 FAQ 따라서도 클리 터스 ‘ 응답 가지고있는 중심점이 없기처럼 소리 Class<List<T>>, 그러나 진짜 문제는 이것이 매우 위험하다는 것이다 :

@SuppressWarnings("unchecked")
Class<List<String>> stringListClass = (Class<List<String>>) (Class<?>) List.class;

List<Integer> intList = new ArrayList<>();
intList.add(1);
List<String> stringList = stringListClass.cast(intList);
// Surprise!
String firstElement = stringList.get(0);

cast()안전 할 것처럼 만든다 그것은 보이지만, 실제로는 전혀 안전하지 않습니다.


List<?>.class= 할 수없는 곳을 얻지 못하지만 Class<List<?>>일반적인 유형에 따라 유형을 결정하는 방법이있을 때 도움이 될 것이므로 =Class 인수 .

들어 getClass()있다 JDK-6184881은 와일드 카드를 사용하여 스위치에 요청이 변경 (곧) 수행되는 것처럼 앞의 코드와 호환되지 않기 때문에, 그러나 그것은 (참조 보이지 않는 이 댓글을 ).


답변

우리 모두는 그것이 지워지는 것을 알고 있습니다. 그러나 클래스 계층 구조에서 유형이 명시 적으로 언급되는 일부 상황에서는 알 수 있습니다.

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

public abstract class CaptureType<T> {
    /**
     * {@link java.lang.reflect.Type} object of the corresponding generic type. This method is useful to obtain every kind of information (including annotations) of the generic type.
     *
     * @return Type object. null if type could not be obtained (This happens in case of generic type whose information cant be obtained using Reflection). Please refer documentation of {@link com.types.CaptureType}
     */
    public Type getTypeParam() {
        Class<?> bottom = getClass();
        Map<TypeVariable<?>, Type> reifyMap = new LinkedHashMap<>();

        for (; ; ) {
            Type genericSuper = bottom.getGenericSuperclass();
            if (!(genericSuper instanceof Class)) {
                ParameterizedType generic = (ParameterizedType) genericSuper;
                Class<?> actualClaz = (Class<?>) generic.getRawType();
                TypeVariable<? extends Class<?>>[] typeParameters = actualClaz.getTypeParameters();
                Type[] reified = generic.getActualTypeArguments();
                assert (typeParameters.length != 0);
                for (int i = 0; i < typeParameters.length; i++) {
                    reifyMap.put(typeParameters[i], reified[i]);
                }
            }

            if (bottom.getSuperclass().equals(CaptureType.class)) {
                bottom = bottom.getSuperclass();
                break;
            }
            bottom = bottom.getSuperclass();
        }

        TypeVariable<?> var = bottom.getTypeParameters()[0];
        while (true) {
            Type type = reifyMap.get(var);
            if (type instanceof TypeVariable) {
                var = (TypeVariable<?>) type;
            } else {
                return type;
            }
        }
    }

    /**
     * Returns the raw type of the generic type.
     * <p>For example in case of {@code CaptureType<String>}, it would return {@code Class<String>}</p>
     * For more comprehensive examples, go through javadocs of {@link com.types.CaptureType}
     *
     * @return Class object
     * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType}
     * @see com.types.CaptureType
     */
    public Class<T> getRawType() {
        Type typeParam = getTypeParam();
        if (typeParam != null)
            return getClass(typeParam);
        else throw new RuntimeException("Could not obtain type information");
    }


    /**
     * Gets the {@link java.lang.Class} object of the argument type.
     * <p>If the type is an {@link java.lang.reflect.ParameterizedType}, then it returns its {@link java.lang.reflect.ParameterizedType#getRawType()}</p>
     *
     * @param type The type
     * @param <A>  type of class object expected
     * @return The Class<A> object of the type
     * @throws java.lang.RuntimeException If the type is a {@link java.lang.reflect.TypeVariable}. In such cases, it is impossible to obtain the Class object
     */
    public static <A> Class<A> getClass(Type type) {
        if (type instanceof GenericArrayType) {
            Type componentType = ((GenericArrayType) type).getGenericComponentType();
            Class<?> componentClass = getClass(componentType);
            if (componentClass != null) {
                return (Class<A>) Array.newInstance(componentClass, 0).getClass();
            } else throw new UnsupportedOperationException("Unknown class: " + type.getClass());
        } else if (type instanceof Class) {
            Class claz = (Class) type;
            return claz;
        } else if (type instanceof ParameterizedType) {
            return getClass(((ParameterizedType) type).getRawType());
        } else if (type instanceof TypeVariable) {
            throw new RuntimeException("The type signature is erased. The type class cant be known by using reflection");
        } else throw new UnsupportedOperationException("Unknown class: " + type.getClass());
    }

    /**
     * This method is the preferred method of usage in case of complex generic types.
     * <p>It returns {@link com.types.TypeADT} object which contains nested information of the type parameters</p>
     *
     * @return TypeADT object
     * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType}
     */
    public TypeADT getParamADT() {
        return recursiveADT(getTypeParam());
    }

    private TypeADT recursiveADT(Type type) {
        if (type instanceof Class) {
            return new TypeADT((Class<?>) type, null);
        } else if (type instanceof ParameterizedType) {
            ArrayList<TypeADT> generic = new ArrayList<>();
            ParameterizedType type1 = (ParameterizedType) type;
            return new TypeADT((Class<?>) type1.getRawType(),
                    Arrays.stream(type1.getActualTypeArguments()).map(x -> recursiveADT(x)).collect(Collectors.toList()));
        } else throw new UnsupportedOperationException();
    }

}

public class TypeADT {
    private final Class<?> reify;
    private final List<TypeADT> parametrized;

    TypeADT(Class<?> reify, List<TypeADT> parametrized) {
        this.reify = reify;
        this.parametrized = parametrized;
    }

    public Class<?> getRawType() {
        return reify;
    }

    public List<TypeADT> getParameters() {
        return parametrized;
    }
}

이제 다음과 같은 작업을 수행 할 수 있습니다.

static void test1() {
        CaptureType<String> t1 = new CaptureType<String>() {
        };
        equals(t1.getRawType(), String.class);
    }

    static void test2() {
        CaptureType<List<String>> t1 = new CaptureType<List<String>>() {
        };
        equals(t1.getRawType(), List.class);
        equals(t1.getParamADT().getParameters().get(0).getRawType(), String.class);
    }


    private static void test3() {
            CaptureType<List<List<String>>> t1 = new CaptureType<List<List<String>>>() {
            };
            equals(t1.getParamADT().getRawType(), List.class);
        equals(t1.getParamADT().getParameters().get(0).getRawType(), List.class);
    }

    static class Test4 extends CaptureType<List<String>> {
    }

    static void test4() {
        Test4 test4 = new Test4();
        equals(test4.getParamADT().getRawType(), List.class);
    }

    static class PreTest5<S> extends CaptureType<Integer> {
    }

    static class Test5 extends PreTest5<Integer> {
    }

    static void test5() {
        Test5 test5 = new Test5();
        equals(test5.getTypeParam(), Integer.class);
    }

    static class PreTest6<S> extends CaptureType<S> {
    }

    static class Test6 extends PreTest6<Integer> {
    }

    static void test6() {
        Test6 test6 = new Test6();
        equals(test6.getTypeParam(), Integer.class);
    }



    class X<T> extends CaptureType<T> {
    }

    class Y<A, B> extends X<B> {
    }

    class Z<Q> extends Y<Q, Map<Integer, List<List<List<Integer>>>>> {
    }

    void test7(){
        Z<String> z = new Z<>();
        TypeADT param = z.getParamADT();
        equals(param.getRawType(), Map.class);
        List<TypeADT> parameters = param.getParameters();
        equals(parameters.get(0).getRawType(), Integer.class);
        equals(parameters.get(1).getRawType(), List.class);
        equals(parameters.get(1).getParameters().get(0).getRawType(), List.class);
        equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getRawType(), List.class);
        equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getParameters().get(0).getRawType(), Integer.class);
    }




    static void test8() throws IllegalAccessException, InstantiationException {
        CaptureType<int[]> type = new CaptureType<int[]>() {
        };
        equals(type.getRawType(), int[].class);
    }

    static void test9(){
        CaptureType<String[]> type = new CaptureType<String[]>() {
        };
        equals(type.getRawType(), String[].class);
    }

    static class SomeClass<T> extends CaptureType<T>{}
    static void test10(){
        SomeClass<String> claz = new SomeClass<>();
        try{
            claz.getRawType();
            throw new RuntimeException("Shouldnt come here");
        }catch (RuntimeException ex){

        }
    }

    static void equals(Object a, Object b) {
        if (!a.equals(b)) {
            throw new RuntimeException("Test failed. " + a + " != " + b);
        }
    }

자세한 내용은 여기를 참조하십시오 . 그러나 다시 검색하는 것은 거의 불가능합니다.

class SomeClass<T> extends CaptureType<T>{}
SomeClass<String> claz = new SomeClass<>();

그것이 지워지는 곳.


답변

클래스 리터럴에 일반 유형 정보가 없다는 사실 때문에 모든 경고를 제거하는 것이 불가능하다고 가정해야합니다. 어떤 식 으로든Class<Something> 하는 것은 제네릭 형식을 지정하지 않고 컬렉션을 사용하는 것과 같습니다. 내가 얻을 수있는 최선은 다음과 같습니다.

private <C extends A<C>> List<C> getList(Class<C> cls) {
    List<C> res = new ArrayList<C>();
    // "snip"... some stuff happening in here, using cls
    return res;
}

public <C extends A<C>> List<A<C>> getList() {
    return getList(A.class);
}