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


194

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

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 하지만, 차라리하지 않는 게 좋을.

어떤 제안?

답변:


161

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

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.


12
List<Integer> list1 = new ArrayList<Integer>(); List<String> list2 = (List<String>)list1; list2.add("foo"); // perfectly legal Java에서는 그렇게 할 수 없으며 형식 불일치 컴파일 오류가 발생합니다!
DhafirNz

4
그래서 ... 필요하면 어떻게해야합니까?
Christopher Francisco

2
컴파일러는 다음과 같이 항상 바보로 만들 수 있습니다.List<String> list2 = (List<String>) (Object) list1;
kftse

17
그러나 또 다른 "C #에서는 작동하지만 Java에서는 작동하지 않습니다." JSON 객체를 직렬화 해제하고 C #에서는 typeof (List <MyClass>)가 완벽하게 작동하지만 List <MyClass> .class는 Java의 구문 오류입니다. 그렇습니다. Cletus가 쓴 것처럼 평소에 대한 논리적 설명이 있지만 항상 모든 것이 왜 C #에서 작동하는지 궁금합니다.
젠장 야채

2
완벽하게 합법적이라는 것은 무엇을 의미합니까? 코드의 해당 부분이 컴파일되지 않습니까?
Eduardo Dennis

63

매개 변수화 된 형식에 대한 클래스 리터럴은 없지만 이러한 형식을 올바르게 정의하는 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 검색을 사용하여 쉽게 찾을 수 있습니다


1
이것으로 나는 일반적인 E로 클래스를 만든 다음 clzz = new TypeToken<E>(){}.getRawType();나중에 비슷한 구조의 열거 형을 반복하고 clzz.getEnumConstants()마침내 refection을 사용하여 멤버 메소드를 Method method = clzz.getDeclaredMethod("getSomeFoo");너무 많이 이겼습니다! 감사합니다!
나루토 Sempai

57

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

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


2
두 번째 캐스트를에서 Object으로 변경하면 Class(무의미한) 체크 된 런타임 캐스트의 오버 헤드를 절약 할 수 있습니다.
Clashsoft

2
@Clashsoft 당신이 제안하는 것처럼 Class대신 에을 사용 하는 Object것이 더 의미있는 것처럼 보이지만 @SuppressWarnings("unchecked")주석 의 필요성을 제거하지는 않으며 새로운 경고를 추가합니다.Class is a raw type. References to generic type Class<T> should be parameterized
Ortomala Lokni

10
당신은 사용할 수 있습니다 Class<?>:(Class<List<Foo>>)(Class<?>)List.class
Devstr

@Devstr 나는 그것을 시도 할 때 당신이 맞다고 본다 ... (Object) 또는 (Class <?>)를 사용하기위한 인수는 무엇입니까?
cellepo 2016 년

2
이 대답은 전혀 의미가 없습니다. OP가 클래스 경로를 매개 변수화하려는 이유는 unchecked경고 가 있기 때문 입니다. 이 답변은 그중 어떤 것도 변경 / 개선하지 않습니다. 영업 이익은 심지어 자신이 사용하지 않는 자신의 질문에 언급 SuppressWarnings...
Spenhouet

6

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();

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


추가 설명에 감사드립니다. 제네릭에 대한 이해가 훨씬 명확 해져서 런타임 메커니즘이 아니라는 것을 알게되었습니다. :)
Tom

2
제 생각에 이것은 Sun이 일반을 평범한 방식으로 구현했음을 의미합니다 .Oracle이 언젠가이 문제를 해결하기를 바랍니다. 일반의 C # '의 구현은 훨씬 더 나은 (앤더스는 신 같은이)입니다
마르셀 발데스 오 로즈 코

1
@MarcelValdezOrozco AFAIK, Java에서는 이전 코드 (1.5 이전)가 아무런 문제없이 새로운 JVM에서 작동하기를 원했기 때문에 그것을 구현했습니다. 호환성을 고려한 매우 현명한 디자인 결정 인 것 같습니다. 나는 그것에 평범한 것이 없다고 생각합니다.
peter.petrov

3

자바 제네릭 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은 와일드 카드를 사용하여 스위치에 요청이 변경 (곧) 수행되는 것처럼 앞의 코드와 호환되지 않기 때문에, 그러나 그것은 (참조 보이지 않는 이 댓글을 ).


2

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

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<>();

그것이 지워지는 곳.


이것은 JAX-RS가 사용하는 해결 방법이기도합니다. GenericEntity그리고 GenericType.
Hein Blöd 2016 년

1

클래스 리터럴에 일반 유형 정보가 없다는 사실 때문에 모든 경고를 제거하는 것이 불가능하다고 가정해야합니다. 어떤 식 으로든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);
}

1

도우미 메소드를 사용 @SuppressWarnings("unchecked")하여 클래스 전체를 제거 할 수 있습니다 .

@SuppressWarnings("unchecked")
private static <T> Class<T> generify(Class<?> cls) {
    return (Class<T>)cls;
}

그럼 당신은 쓸 수 있습니다

Class<List<Foo>> cls = generify(List.class);

다른 사용 예는

  Class<Map<String, Integer>> cls;

  cls = generify(Map.class);

  cls = TheClass.<Map<String, Integer>>generify(Map.class);

  funWithTypeParam(generify(Map.class));

public void funWithTypeParam(Class<Map<String, Integer>> cls) {
}

그러나 실제로는 거의 유용하지 않으며 메서드 사용으로 인해 컴파일러의 형식 검사가 무효화되므로 공개적으로 액세스 할 수있는 곳에 구현하지 않는 것이 좋습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.