Yansımasıyla Java'da genel bir parametre türü edinin


143

Genel bir parametre türü elde etmek mümkün müdür?

Bir örnek:

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

Yanıtlar:


156

Bir yapı, bir zamanlar tökezledim

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

Bu yüzden, talihsiz bir şekilde tam olarak anlamadığım bir yansıma büyüsü var gibi görünüyor ... Üzgünüm.


2
Ama o zaman soyut olarak ilan edin. kullanmak istediğinizde satır içi alt sınıfını kullanın.
Snicolas

42
Bu çok eski ve bazı nedenlerden ötürü kabul edilmiş olsa da , aşağı düştüm çünkü soruya cevap vermiyor. O olur değil söz verilen örnekte "Spiderman" verir. Kuşkusuz bazı durumlarda yararlıdır, ancak sorulan soru için işe yaramaz.
Jon Skeet

15
Buraya kadar gelen kimse bir cevap için "Bu yapılamaz" ı kabul etmek üzere değildir. Buradayız çünkü umutsuzuz.
Addison

14
Bu istisnayı alıyorum: Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType kısıtlamanın ne olduğundan emin değilim.
Çanak

4
@Dish gibi ben sadece bir olsunjava.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
Kenny

133

@DerMike'ın cevabını açıklamak istiyorum:

İlk olarak, silme işlemi JDK'nın elimine ettiği anlamına gelmez çalışma zamanında tür bilgisini . Derleme zamanı türü denetimi ve çalışma zamanı türü uyumluluğunun aynı dilde bir arada var olmasına izin veren bir yöntemdir. Bu kod bloğunun da ifade ettiği gibi, JDK silinmiş tür bilgilerini saklar - sadece kontrol edilen dökümler ve diğer şeylerle ilişkili değildir.

İkincisi, bu, genel bir sınıfa, kontrol edilen beton tipinden heirarşinin tam bir seviye yukarısında jenerik tip bilgisi sağlar - yani jenerik tip parametrelerine sahip soyut bir ebeveyn sınıfı, somut bir uygulama için tip parametrelerine karşılık gelen beton tiplerini bulabilir o doğrudan ondan devralır. Bu sınıf soyut ve somutlaştırılmış olsaydı ya da somut uygulama iki seviye aşağı olsaydı, bu işe yaramazdı (her ne kadar biraz jimmying, bir veya daha düşük sınıfa kadar önceden belirlenmiş sayıda seviyeye uygulanabilmesine rağmen X jenerik tip parametreleri ile, vb.).

Her neyse, açıklamaya geçelim. Referans kolaylığı için satırlara ayrılmış kod tekrar:

1 # Sınıf genericParameter0OfThisClass = 
2 # (Sınıf)
3 # ((Parametreli Tip)
4 # getClass ()
5 # .getGenericSuperclass ())
6 # .getActualTypeArguments () [0];

Bu kodu içeren genel türlere sahip soyut sınıf olalım. Bunu kabaca içeriden okumak:

  • Satır 4, mevcut somut sınıfın Sınıf örneğini alır. Bu, derhal torunumuzun beton tipini tanımlar.
  • 5. satır, bu sınıfın üst türünü Tür olarak alır; bu biziz. Parametrik bir tip olduğumuzdan, kendimizi güvenle ParameterizedType (satır 3) 'e atabiliriz. Anahtar, Java bu Type nesnesini belirlediğinde, yeni ParameterizedType örneğindeki tür parametrelerimizle tür bilgisini ilişkilendirmek için alt öğede bulunan tür bilgisini kullanmasıdır. Artık jeneriklerimiz için somut tiplere erişebiliyoruz.
  • 6. satır, sınıf kodunda bildirildiği gibi, jeneriklerimize eşlenen türler dizisini alır. Bu örnek için ilk parametreyi çıkarıyoruz. Bu bir Tür olarak geri gelir.
  • Satır 2, bir Sınıfa döndürülen son Türü verir. Bu güvenlidir çünkü genel tip parametrelerimizin hangi türleri alabileceğini biliyoruz ve bunların hepsinin sınıf olacağını doğrulayabiliriz (Java'da bir Class örneği olmayan genel bir parametre almaya nasıl devam edeceğinden emin değilim) aslında onunla ilişkili).

... ve işte bu kadar. Bu nedenle, kendi somut uygulamamızdan tür bilgisini kendimize geri itiyoruz ve bir sınıf tanıtıcısına erişmek için kullanıyoruz. getGenericSuperclass () 'ı ikiye katlayabilir ve iki seviyeye gidebiliriz veya getGenericSuperclass ()' ı ortadan kaldırabilir ve somut bir tür olarak kendimiz için değerler alabiliriz (uyarı: Bu senaryoları test etmedim, henüz benim için gelmediler).

Somut çocuklarınızın keyfi sayıda atlama olması veya somut ve nihai değilseniz, özellikle (değişken derinlikteki) çocuklarınızdan birinin kendi jeneriklerine sahip olmasını beklemek zor olur. Ancak genellikle bu hususlar etrafında tasarım yapabilirsiniz, bu da sizi en çok şekilde yönlendirir.

Umarım bu birine yardımcı olmuştur! Bu yazının eski olduğunu biliyorum. Muhtemelen bu açıklamayı kesip başka sorular için saklayacağım.


3
Sadece "Java'da kendisiyle ilişkilendirilmiş bir Class örneği olmayan genel bir parametre almaya nasıl devam edeceğinden emin değilim." joker ifade ifadesi "(say:? SomeClass'ı genişletir) veya bir" tür değişkeni "(say: <T>; burada T genel bir alt sınıftan gelir). her iki durumda da, döndürülen "Tür" örnekleri "Sınıf" a dönüştürülemez, çünkü her VM'nin Tür arabirimini uygulayan ancak doğrudan java.lang.Class öğesinden türetmeyen farklı bir uygulaması olabilir.
Roberto Andrade

19

Aslında bunu işe aldım. Aşağıdaki snippet'i düşünün:

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 kullanıyorum


12
-1: bu sorudaki sorunu çözmez; size gerçek çalışma zamanı türünü değil, yalnızca yöntem imzasında bildirilen türü verir.
Michael Borgwardt

5
OP'nin sorusuna cevap vermeyebilir, ancak bu yararlı bir tekniktir.
dnault

3
Bu tamamen farklı bir sorunun cevabıdır.
Dawood ibn Kareem

Tüm gerçek türleri almak için m.getParameterTypes () yöntemini çağırabilirsiniz. Örnek için "Liste" döndürür.
Lee Meador

GenericParameterTypes [i], ParameterizedType örneğinin bir örneği değilse, bir Sınıf olabilir. Bu, yönteme ilişkin belirli bir argümanın hiç parametreleştirilmediğini gösterir.
Lee Meador

18

Aslında "anonim sınıf" numaralarını ve Süper Tip Jetonlarından gelen fikirleri uygulayarak bir çözüm var :

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

Bir yaratılmasına hile yalan anonim sınıfın , new ArrayList<SpiderMan>() {}, orijinal (basit) yerine new ArrayList<SpiderMan>(). Anonim bir sınıfın kullanılması (mümkünse) derleyicinin type parametresine SpiderManverilen tür argümanı hakkında bilgi tutmasını sağlar List<?>. Voilà!


Bu dolaylı olarak şu soruyu cevaplar: Orijinal sorunun çözümü yoktur. Türün genel parametrelerini koruyabilmesi için, alt sınıf gibi başka bir türe gömülmesi gerekir. Bu örnek, chill () içindeki generic type parametresini kurtarmayı mümkün kılmak için arama kodunu nasıl değiştirmeniz gerektiğini gösterir.
Florian F

İlk bağlantı öldü
Ashvin Sharma

@AshvinSharma Aynı malzemenin burada mevcut olduğuna inanıyorum: rgomes.info/using-typetokens-to-retrieve-generic-parameters
Yann-Gaël Guéhéneuc

7

Tür silme nedeniyle, listenin türünü bilmenin tek yolu, türü yönteme parametre olarak iletmek olacaktır:

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'ın parametreli bir arayüzün genel parametresini almak için verdiği cevaba ek ( çoğaltmayı önlemek için bir Java-8 varsayılan yöntemi içinde #getGenericInterfaces () yöntemini kullanarak ):

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
}

Orijinal gönderinin istediği bu değildi. Burada, bir alt sınıf oluşturdunuz Awesomeable<Beer>. Bu durumda tip bilgisi korunur. new Awesomable<Beer> ()Yönteme geçerseniz wt çalışmaz.
Florian F

@FlorianF Bu durumda olduğu Awesomable<Beer>gibi somut bir alt sınıfın açık tanımı olmadan anında bir şekilde iletirseniz EstrellaGalicia, yine de parametrelenmiş türü çıkarıyor: Şu anda çalıştırdım: System.out.println("Type is: " + new Awesomable<Beer>() {}.parameterizedType());---> Tür: ParameterizedStuff $ Beer
Campa

6

Hayır, bu mümkün değil. Aşağı doğru uyumluluk sorunları nedeniyle, Java'nın jenerikleri tür silinmesine dayanır , yani çalışma zamanında, sahip olduğunuz tek şey jenerik olmayan bir Listnesnedir. Çalışma zamanında tür parametreleri hakkında bazı bilgiler vardır, ancak nesne tanımlarında değil, sınıf tanımlarında bulunur (yani " bu alanın tanımı hangi genel türü kullanıyor? " Diye sorabilirsiniz ).


Java bunu çalışma zamanı sırasında meta veri olarak depolayabilse de, değil mi? Umarım olur. Kötü Şans.
cimnine

1
@ user99475: Hayır. Haklıyım ve Andrey yanlış. Cevabımın ikinci bölümünde bahsettiğim tip bilgisine atıfta bulunuyor, ancak sorunun sorduğu şey bu değil.
Michael Borgwardt

5

@Bertolami tarafından işaret edildiği gibi, değişken bir tip bizim için ve gelecekteki değerini (typeOfList değişkeninin içeriği) almak mümkün değildir.

Yine de, sınıfı aşağıdaki gibi parametre olarak iletebilirsiniz:

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 yapıcısına bir sınıf değişkeni iletmeniz gerektiğinde Google'ın yaptığı şey budur .



1

Burada buldum bu örnekte olduğu gibi yansıması ile genel bir parametre türü alabilirsiniz :

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

Home of <K, V> 'dan V almak için aynı şeyi nasıl yapardınız?
Rafael Sanches

1

Hızlı cevap Soru çünkü Java genel tür silme, hayır yapamazsın olduğunu.

Daha uzun cevap, listenizi şu şekilde oluşturduysanız:

new ArrayList<SpideMan>(){}

Daha sonra bu durumda genel tür, yukarıdaki yeni anonim sınıfın genel üst sınıfında korunur.

Bunu listelerle yapmanızı önermiyorum, ancak bir dinleyici uygulamasıdır:

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

Ve, süper sınıfların ve süper arayüzlerin jenerik tiplerini tahmin etmek JVM'ler arasında değiştiğinden, jenerik çözüm bazı cevapların önerebileceği kadar basit değildir.

İşte şimdi yaptım.


0

Bu imkansızdır, çünkü Java'daki jenerikler sadece derleme zamanında dikkate alınır. Böylece, Java jenerikleri sadece bir tür ön işlemcidir. Ancak, liste üyelerinin gerçek sınıfını alabilirsiniz.


Evet şu an bunu yapıyorum. Ama şimdi liste boş olsa bile türü bilmek istiyorum. Ama dört adam yanlış olamaz. Hepinize teşekkür ederim)!
cimnine

19
Bu doğru değil. Zor olsa da, bir sonraki gönderide ParameterizedType'ın bunu yapmasına izin verdiğini görebilirsiniz.
Edmondo1984

1
Kabul ediyorum, yapılabilir. DerMike örneği bunu özetliyor (benim için çalıştı).
mac

LOL "dört adam yanlış olamaz". Bu cevap doğrudur.
Dawood ibn Kareem

0

Kabul etmeyi veya geri dönmeyi bekleyen yöntemler için bunu kodladım Iterable<?...>. İşte kod:

/**
 * 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

Değişkenden genel bir parametre alamazsınız. Ancak bir yöntem veya alan bildiriminden yapabilirsiniz:

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

Bu bana "ana" iş parçacığında bir istisna verir java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl
Lluis Martinez

'params', birkaç alt ara yüze sahip olan Type'lardır. TypeVariableImpl yöntem bağımsız değişkenleri için kullanılan bir ifadedir ve temsil ettiği Sınıf değil, <> s içindeki jenerik adını söyler.
Lee Meador

0

Sadece bu kod snippet'ini okumak benim için zordu, sadece 2 okunabilir satıra böldüm:

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

Benim yöntem için herhangi bir parametre olmadan Type parametresinin bir örneğini oluşturmak istedim:

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

İşte başka bir numara. Genel bir vararg dizisi kullanın

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

Birçok insanın getGenericSuperclass()çözüme yaslandığını fark ettim :

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

Ancak, bu çözüm hataya açıktır. Bu olacak değil torunları eşdeğer ilaç varsa düzgün çalışması. Bunu düşün:

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

class Bar extends Foo<Double> {}

Hangi tip olacak Bar.persistentClass? Class<Integer>? Hayır, olacak Class<Double>. Bu, getClass()her zaman Barbu durumda olan en üst sınıfı döndürmesi ve genel süper sınıfı olması nedeniyle olur Foo<Double>. Bu nedenle, argüman türüDouble .

Eğer başarısız olmayan güvenilir bir çözüme ihtiyacınız varsa iki tane önerebilirim.

  1. Kullanın Guava. Bu amaçla tam yapıldığı bir sınıfı vardır: com.google.common.reflect.TypeToken. Tüm köşe kılıflarını iyi işler ve daha hoş bir işlevsellik sunar. Dezavantajı ekstra bir bağımlılıktır. Bu sınıfı kullandığınız düşünüldüğünde, kodunuz aşağıdaki gibi basit ve anlaşılır görünecektir:
class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  public final Class<T> persistentClass = (Class<T>) (new TypeToken<T>(getClass()) {}.getType());
}
  1. Aşağıdaki özel yöntemi kullanın. Yukarıda bahsedilen Guava sınıfına benzer önemli ölçüde basitleştirilmiş bir mantık uygular. Ancak, hata eğilimli olduğunu garanti etmem. Yine de genel torunları ile sorunu çözer.
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

kullanın:

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

Bu yanlış. toArray()döner Object[]. Belirli bir alt tip olmasına güvenemezsiniz ve buna da güvenmemelisiniz.
Radiodef
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.