리플렉션을 사용하여 Java에서 일반 매개 변수 유형 가져 오기


143

일반 매개 변수의 유형을 얻을 수 있습니까?

예를 들면 :

public final class Voodoo {
    public static void chill(List<?> aListWithTypeSpiderMan) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = ???;
    }

    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>());
    }
}

답변:


156

하나의 구조, 나는 한 번 넘어졌다

Class<T> persistentClass = (Class<T>)
   ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];

불행히도 완전히 이해하지 못하는 주변에 반성 마법이있는 것 같습니다 ... 미안합니다.


2
그러나 그것을 추상으로 선언하십시오. 사용하고 싶을 때는 인라인으로 서브 클래 싱하십시오.
Snicolas

42
이것이 매우 오래 되어 어떤 이유로 든 받아 들여지 더라도 나는 단순히 질문에 대답하지 않기 때문에 하향 투표했습니다. 그것은 것입니다 하지 질문에 주어진 예에서 "스파이더"를 제공합니다. 그것은 어떤 상황에서는 의심 할 여지없이 유용하지만, 질문에 대해서는 효과가 없습니다.
Jon Skeet

15
지금까지 온 사람은 아무도 "답을 할 수 없습니다"를 받아들이 려하지 않습니다. 우리는 필사적이기 때문에 여기에 있습니다.
Addison

14
이 예외가 발생합니다. Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType 제약 조건이 무엇인지 잘 모르겠습니다.
Dish

4
@Dish처럼 난 그냥 얻을java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
케니 와이 랜드

133

@DerMike의 답변을 세분화하여 설명하려고합니다.

첫째, 유형의 삭제는 그 JDK의 의미하지 않는다 을 제거해을 가 런타임에 유형 정보를 . 컴파일 타임 유형 검사 및 런타임 유형 호환성이 동일한 언어로 공존 할 수 있도록하는 방법입니다. 이 코드 블록에서 알 수 있듯이 JDK는 지워진 유형 정보를 유지합니다. 이는 확인 된 캐스트 및 항목과 관련이 없습니다.

둘째, 이것은 검사중인 콘크리트 유형에서 계층 구조의 정확히 한 레벨 위의 일반 클래스에 일반 유형 정보를 제공합니다. 그 으로부터 직접 상속받습니다. 이 클래스가 비 추상적이고 인스턴스화되었거나 구체적인 구현이 두 수준 아래로 내려간 경우에는 작동하지 않습니다 (조금의 약간의 지미로 인해 하나 이상의 클래스 또는 가장 낮은 클래스까지 미리 정해진 수의 레벨에 적용 할 수 있음) X 제네릭 형식 매개 변수 등).

어쨌든, 설명에. 다음은 쉽게 참조 할 수 있도록 코드를 다시 한 줄로 분리 한 것입니다.

1 # 클래스 genericParameter0OfThisClass = 
2 # (클래스)
3 # ((매개 변수화 된 유형)
4 # getClass ()
5 # .getGenericSuperclass ())
6 # .getActualTypeArguments () [0];

이 코드를 포함하는 제네릭 형식의 추상 클래스 인 'us'를 보자. 이것을 대략 내부에서 읽는다.

  • 4 행은 현재 구체적 클래스의 Class 인스턴스를 가져옵니다. 이것은 우리의 직계 후손의 구체적인 유형을 식별합니다.
  • 5 행은 해당 클래스의 수퍼 타입을 유형으로 가져옵니다. 이것은 우리입니다. 우리는 파라 메트릭 타입이기 때문에 안전하게 ParameterizedType (라인 3)으로 캐스팅 할 수 있습니다. 핵심은 Java가이 Type 개체를 결정할 때 자식에있는 형식 정보를 사용하여 형식 정보를 새 ParameterizedType 인스턴스의 형식 매개 변수와 연결한다는 것입니다. 이제 제네릭의 구체적인 유형에 액세스 할 수 있습니다.
  • 6 행은 클래스 코드에 선언 된 순서대로 제네릭에 매핑 된 형식의 배열을 가져옵니다. 이 예에서는 첫 번째 매개 변수를 뽑습니다. 이것은 유형으로 돌아옵니다.
  • 2 행은 클래스에 반환 된 최종 Type을 캐스팅합니다. 우리는 제네릭 형식 매개 변수가 취할 수있는 유형을 알고 있으며 모두 유형이 될 것임을 확인할 수 있기 때문에 안전합니다 (Java에서는 Class 인스턴스가없는 일반 매개 변수를 얻는 방법을 잘 모르겠습니다) 실제로 관련).

... 그 정도입니다. 그래서 우리는 우리 자신의 구체적인 구현에서 타입 정보를 다시 우리 자신에게 푸시하고 그것을 사용하여 클래스 핸들에 액세스합니다. 우리는 getGenericSuperclass ()를 두 배로 늘리고 두 레벨로 가거나 getGenericSuperclass ()를 제거하고 구체적인 유형으로 가치를 얻을 수 있습니다 (캐비티 :이 시나리오를 테스트하지 않았지만 아직 나에게 오지 않았습니다).

구체적인 자녀가 홉을 임의의 수로 멀리 떨어 뜨리거나 구체적이지 않고 최종적이지 않은 경우 까다로워지고, (매우 깊은) 자녀 중 하나가 자신의 제네릭을 갖기를 기대하는 경우 특히 까다 롭습니다. 그러나 일반적으로 이러한 고려 사항을 중심으로 디자인 할 수 있으므로 대부분의 방법으로 얻을 수 있습니다.

이것이 누군가를 도왔기를 바랍니다! 이 게시물이 고대인 것 같습니다. 아마도이 설명을 잘라내어 다른 질문을 위해 보관할 것입니다.


3
"Java에서 클래스 인스턴스와 관련이없는 일반 매개 변수를 얻는 방법에 대해 잘 모르겠습니다.") : 일반 클래스의 매개 변수가 " 와일드 카드 표현식 "(예 :? SomeClass 확장) 또는"유형 변수 "(예 : <T> 여기서 T는 일반 서브 클래스에서 나옵니다). 두 경우 모두 리턴 된 "Type"인스턴스를 "Class"로 캐스트 할 수 없습니다. 각 VM은 Type 인터페이스를 구현하지만 java.lang.Class에서 직접 파생되지 않은 두 가지 구현 방식이 다를 수 있기 때문입니다.
Roberto Andrade

19

실제로 나는 이것을 작동시켰다. 다음 스 니펫을 고려하십시오.

Method m;
Type[] genericParameterTypes = m.getGenericParameterTypes();
for (int i = 0; i < genericParameterTypes.length; i++) {
     if( genericParameterTypes[i] instanceof ParameterizedType ) {
                Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments();
//parameters[0] contains java.lang.String for method like "method(List<String> value)"

     }
 }

jdk 1.6을 사용하고 있습니다.


12
-1 : 이것은 문제의 문제를 해결하지 못한다; 실제 런타임 유형이 아니라 메소드 서명에 선언 된 유형 만 제공합니다.
Michael Borgwardt

5
OP의 질문에 대답하지 못할 수도 있지만 유용한 기술입니다.
dnault

3
이것은 완전히 다른 질문에 대한 답변입니다.
Dawood ibn Kareem

m.getParameterTypes ()를 호출하여 모든 실제 유형을 가져올 수 있습니다. 예를 들어 "List"를 반환합니다.
Lee Meador

genericParameterTypes [i]가 ParameterizedType의 인스턴스가 아닌 경우 클래스 일 수 있습니다. 즉, 메소드에 대한 특정 인수는 매개 변수화되지 않습니다.
Lee Meador

18

실제로 "익명 클래스"트릭슈퍼 타입 토큰 의 아이디어를 적용하여 해결책이 있습니다 .

public final class Voodoo {
    public static void chill(final List<?> aListWithSomeType) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        System.out.println(aListWithSomeType.getClass().getGenericSuperclass());
        System.out.println(((ParameterizedType) aListWithSomeType
            .getClass()
            .getGenericSuperclass()).getActualTypeArguments()[0]);
    }
    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>() {});
    }
}
class SpiderMan {
}

의 창조의 트릭 거짓말 익명 클래스 , new ArrayList<SpiderMan>() {}원래 (단순)의 장소에서 new ArrayList<SpiderMan>(). 성가신 클래스 (가능한 경우)를 사용하면 컴파일러가 SpiderMantype 매개 변수에 지정된 형식 인수에 대한 정보를 유지합니다 List<?>. oil!


이것은 질문에 간접적으로 대답합니다. 원래 문제에는 해결책이 없습니다. 형식이 일반 매개 변수를 유지하려면 하위 클래스와 같은 다른 형식에 포함되어야합니다. 이 예제는 chill ()에서 일반 유형 매개 변수를 복구 할 수 있도록 호출 코드를 변경하는 방법을 보여줍니다.
Florian F

참고로, 첫 번째 링크는 죽었습니다
Ashvin Sharma

@AshvinSharma 나는 동일한 자료를 여기에 사용할 수 있다고 생각합니다 : rgomes.info/using-typetokens-to-retrieve-generic-parameters
Yann-Gaël Guéhéneuc

7

유형 삭제 때문에 목록의 유형을 아는 유일한 방법은 유형을 메소드에 매개 변수로 전달하는 것입니다.

public class Main {

    public static void main(String[] args) {
        doStuff(new LinkedList<String>(), String.class);

    }

    public static <E> void doStuff(List<E> list, Class<E> clazz) {

    }

}

7

매개 변수화 된 인터페이스의 일반 매개 변수를 가져 오는 @DerMike의 답변 부록 ( 중복을 피하기 위해 Java-8 기본 메소드 내에서 #getGenericInterfaces () 메소드 사용 ) :

import java.lang.reflect.ParameterizedType; 

public class ParametrizedStuff {

@SuppressWarnings("unchecked")
interface Awesomable<T> {
    default Class<T> parameterizedType() {
        return (Class<T>) ((ParameterizedType)
        this.getClass().getGenericInterfaces()[0])
            .getActualTypeArguments()[0];
    }
}

static class Beer {};
static class EstrellaGalicia implements Awesomable<Beer> {};

public static void main(String[] args) {
    System.out.println("Type is: " + new EstrellaGalicia().parameterizedType());
    // --> Type is: ParameterizedStuff$Beer
}

이것은 원래 게시물이 요구 한 것이 아닙니다. 여기에의 서브 클래스를 작성했습니다 Awesomeable<Beer>. 이 경우 유형 정보가 유지됩니다. 당신 new Awesomable<Beer> ()이 방법으로 전달 하면 wt 작동하지 않습니다.
Florian F

당신이 전달 @FlorianF 경우 Awesomable<Beer>처럼 구체적인 서브 클래스의 명시 적으로 정의하지 않고 즉시 EstrellaGalicia이 경우, 여전히이 파라미터 화 된 형태를 받고있다 : 나는 지금 그것을 실행 : System.out.println("Type is: " + new Awesomable<Beer>() {}.parameterizedType());---> 유형은 다음과 같습니다 ParameterizedStuff $ 맥주
CAMPA

6

아니, 그건 불가능 해 호환성 호환성 문제로 인해 Java의 제네릭은 유형 삭제 (즉 , 런타임시)를 기반으로 List합니다. 런타임에 형식 매개 변수에 대한 정보가 있지만 개체 정의가 아니라 클래스 정의에 있습니다 (예 : " 이 필드의 정의에서 어떤 일반 형식을 사용합니까? ").


Java가 런타임 중에 이것을 메타 데이터로 저장할 수는 있지만 그렇지 않습니까? 나는 그것이기를 바랐다. 불행.
cimnine 2009

1
@ user99475 : 아뇨. 나는 옳고 안드레이는 틀렸다. 그는 내 대답의 두 번째 부분에서 언급 한 유형 정보를 언급하지만 질문이 요구하는 것은 아닙니다.
Michael Borgwardt

5

@bertolami가 지적했듯이 변수 유형을 사용할 수 없으며 미래 가치 (typeOfList 변수의 내용)를 얻을 수 없습니다.

그럼에도 불구하고 다음과 같이 클래스를 매개 변수로 전달할 수 있습니다.

public final class voodoo {
    public static void chill(List<T> aListWithTypeSpiderMan, Class<T> clazz) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = clazz;
    }

    public static void main(String... args) {
        chill(new List<SpiderMan>(), Spiderman.class );
    }
}

그것은 클래스 변수를 ActivityInstrumentationTestCase2 의 생성자에 전달해야 할 때 Google 이하는 일 입니다.



1

여기 에서 찾은이 예제와 같이 리플렉션이있는 일반 매개 변수의 유형을 얻을 수 있습니다 .

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Home<E> {
    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass(){
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>{}
    private static class StringBuilderHome extends Home<StringBuilder>{}
    private static class StringBufferHome extends Home<StringBuffer>{}   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }
}

V of Home <K, V>를 얻는 방법은 무엇입니까?
Rafael Sanches

1

Java 제네릭 형식 지우기 때문에 질문에 대한 빠른 답변 은 없습니다.

더 긴 대답은 다음과 같이 목록을 만든 경우입니다.

new ArrayList<SpideMan>(){}

그런 다음이 경우 제네릭 형식은 위의 새 익명 클래스의 제네릭 수퍼 클래스에 유지됩니다.

목록 으로이 작업을 수행하는 것이 좋지는 않지만 리스너 구현입니다.

new Listener<Type>() { public void doSomething(Type t){...}}

그리고 일반적인 유형의 수퍼 클래스와 수퍼 인터페이스를 추정하면 JVM간에 변경되므로 일반적인 해결책은 일부 대답이 제안하는 것처럼 간단하지 않습니다.

여기 내가 지금 해냈다.


0

Java의 제네릭은 컴파일 타임에만 고려되기 때문에 불가능합니다. 따라서 Java 제네릭은 일종의 전 처리기입니다. 그러나 목록 멤버의 실제 클래스를 얻을 수 있습니다.


그래, 내가 지금하고있는 일이야. 그러나 이제 목록이 비어 있어도 유형을 알고 싶습니다. 그러나 네 사람은 틀릴 수 없습니다. 다들 감사 해요)!
cimnine 2009

19
사실이 아닙니다. 까다 롭지 만 다음 게시물에서 ParameterizedType이 허용하는 것을 볼 수 있습니다.
Edmondo1984

1
동의 할 수 있습니다. DerMike의 예제는 그것을 설명합니다 (나를 위해 일했습니다).
mac

"4 명의 사람은 틀릴 수 없다"에서 LOL. 이 대답은 맞습니다.
Dawood ibn Kareem

0

accept 또는 return을 기대하는 메소드에 대해 이것을 코딩했습니다 Iterable<?...>. 코드는 다음과 같습니다.

/**
 * Assuming the given method returns or takes an Iterable<T>, this determines the type T.
 * T may or may not extend WindupVertexFrame.
 */
private static Class typeOfIterable(Method method, boolean setter)
{
    Type type;
    if (setter) {
        Type[] types = method.getGenericParameterTypes();
        // The first parameter to the method expected to be Iterable<...> .
        if (types.length == 0)
            throw new IllegalArgumentException("Given method has 0 params: " + method);
        type = types[0];
    }
    else {
        type = method.getGenericReturnType();
    }

    // Now get the parametrized type of the generic.
    if (!(type instanceof ParameterizedType))
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);
    ParameterizedType pType = (ParameterizedType) type;
    final Type[] actualArgs = pType.getActualTypeArguments();
    if (actualArgs.length == 0)
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);

    Type t = actualArgs[0];
    if (t instanceof Class)
        return (Class<?>) t;

    if (t instanceof TypeVariable){
        TypeVariable tv =  (TypeVariable) actualArgs[0];
        AnnotatedType[] annotatedBounds = tv.getAnnotatedBounds();///
        GenericDeclaration genericDeclaration = tv.getGenericDeclaration();///
        return (Class) tv.getAnnotatedBounds()[0].getType();
    }

    throw new IllegalArgumentException("Unknown kind of type: " + t.getTypeName());
}

0

변수에서 일반 매개 변수를 얻을 수 없습니다. 그러나 메소드 또는 필드 선언에서 다음을 수행 할 수 있습니다.

Method method = getClass().getDeclaredMethod("chill", List.class);
Type[] params = method.getGenericParameterTypes();
ParameterizedType firstParam = (ParameterizedType) params[0];
Type[] paramsOfFirstGeneric = firstParam.getActualTypeArguments();

이것은 스레드 "메인"java.lang.ClassCastException에서 예외를 제공합니다. sun.reflect.generics.reflectiveObjects.TypeVariableImpl
Lluis Martinez

'params'는 여러 하위 인터페이스가있는 'Type'입니다. TypeVariableImpl은 메소드 인수에 사용되는 것으로서, 그것이 나타내는 클래스가 아니라 <> 안에있는 제네릭의 이름을 알려줍니다.
Lee Meador

0

이 코드 스 니펫을 읽는 것이 어려웠 기 때문에 2 개의 읽을 수있는 줄로 나눕니다.

// assuming that the Generic Type parameter is of type "T"
ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

내 메소드에 매개 변수가없는 Type 매개 변수의 인스턴스를 만들고 싶었습니다.

publc T getNewTypeInstance(){
    ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
    Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

    // for me i wanted to get the type to create an instance
    // from the no-args default constructor
    T t = null;
    try{
        t = c.newInstance();
    }catch(Exception e){
        // no default constructor available
    }
    return t;
}

0

또 다른 트릭이 있습니다. 일반적인 vararg 배열 사용

import java.util.ArrayList;

class TypedArrayList<E> extends ArrayList<E>
{
    @SafeVarargs
    public TypedArrayList (E... typeInfo)
    {
        // Get generic type at runtime ...
        System.out.println (typeInfo.getClass().getComponentType().getTypeName());
    }
}

public class GenericTest
{
    public static void main (String[] args)
    {
        // No need to supply the dummy argument
        ArrayList<Integer> ar1 = new TypedArrayList<> ();
        ArrayList<String> ar2 = new TypedArrayList<> ();
        ArrayList<?> ar3 = new TypedArrayList<> ();
    }
}

0

많은 사람들이 getGenericSuperclass()해결책에 기대어 있다는 것을 알았습니다 .

class RootGeneric<T> {
  public Class<T> persistentClass = (Class<T>)
    ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];
}

그러나이 솔루션은 오류가 발생하기 쉽습니다. 그것은 것입니다 하지 자손의 제네릭이있는 경우 제대로 작동합니다. 이걸 고려하세요:

class Foo<S> extends RootGeneric<Integer> {}

class Bar extends Foo<Double> {}

어떤 유형 Bar.persistentClass이 있습니까? Class<Integer>? 아뇨 Class<Double>. 이것은 getClass()항상 최상위 클래스를 반환하기 때문에 발생 합니다. Bar이 경우 일반적인 수퍼 클래스는 Foo<Double>입니다. 따라서 인수 유형은Double .

실패하지 않는 안정적인 솔루션이 필요한 경우 두 가지를 제안 할 수 있습니다.

  1. 사용하십시오 Guava. 이 목적을 위해 만들어진 클래스가 com.google.common.reflect.TypeToken있습니다. 모든 코너 케이스를 잘 처리하고 더 멋진 기능을 제공합니다. 단점은 추가적인 의존성입니다. 이 클래스를 사용하면 코드는 다음과 같이 간단하고 명확 해집니다.
class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  public final Class<T> persistentClass = (Class<T>) (new TypeToken<T>(getClass()) {}.getType());
}
  1. 아래의 사용자 정의 방법을 사용하십시오. 위에서 언급 한 Guava 클래스와 유사한 상당히 단순화 된 로직을 구현합니다. 그러나 오류가 발생하기 쉽다고 보장하지는 않습니다. 그래도 일반적인 자손 문제를 해결합니다.
abstract class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  private Class<T> getTypeOfT() {
    Class<T> type = null;
    Class<?> iter = getClass();
    while (iter.getSuperclass() != null) {
      Class<?> next = iter.getSuperclass();
      if (next != null && next.isAssignableFrom(RootGeneric.class)) {
        type =
            (Class<T>)
                ((ParameterizedType) iter.getGenericSuperclass()).getActualTypeArguments()[0];
        break;
      }
      iter = next;
    }
    if (type == null) {
      throw new ClassCastException("Cannot determine type of T");
    }
    return type;
  }
}

-1

사용하다:

Class<?> typeOfTheList = aListWithTypeSpiderMan.toArray().getClass().getComponentType();

이것은 잘못이다. toArray()을 반환합니다 Object[]. 특정 하위 유형 인 것에 의존해서는 안됩니다.
Radiodef
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.