Genel java.util.List türünü edinin


262

Sahibim;

List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();

Listenin genel türünü almanın (kolay) bir yolu var mı?


List nesnesini programatik olarak inceleyebilmek ve genel türünü görebilmek. Bir yöntem, koleksiyonun genel türüne göre nesne eklemek isteyebilir. Bu, derleme zamanı yerine çalışma zamanında jenerikleri uygulayan dillerde mümkündür.
Steve Kuo

4
Sağ - çalışma zamanı algılamaya izin vermenin tek yolu alt sınıflandırmadır: aslında genel türü genişletebilir ve daha sonra kullanılan alt türün yansıma bulma türü bildirimini kullanabilirsiniz. Bu biraz yansıma ama mümkün. Ne yazık ki, bir kişinin genel alt sınıfı kullanması gerektiğini zorlamanın kolay bir yolu yoktur.
StaxMan

1
Elbette stringList dizeleri ve integerList tam sayılarını içerir? Neden daha karmaşık hale getirelim?
Ben Thurley

Yanıtlar:


408

Bunlar aslında belirli bir sınıfın alanlarıysa, o zaman onları biraz yansıma yardımı ile alabilirsiniz:

package test;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

public class Test {

    List<String> stringList = new ArrayList<String>();
    List<Integer> integerList = new ArrayList<Integer>();

    public static void main(String... args) throws Exception {
        Field stringListField = Test.class.getDeclaredField("stringList");
        ParameterizedType stringListType = (ParameterizedType) stringListField.getGenericType();
        Class<?> stringListClass = (Class<?>) stringListType.getActualTypeArguments()[0];
        System.out.println(stringListClass); // class java.lang.String.

        Field integerListField = Test.class.getDeclaredField("integerList");
        ParameterizedType integerListType = (ParameterizedType) integerListField.getGenericType();
        Class<?> integerListClass = (Class<?>) integerListType.getActualTypeArguments()[0];
        System.out.println(integerListClass); // class java.lang.Integer.
    }
}

Bunu parametre türleri ve dönüş yöntemleri için de yapabilirsiniz.

Ancak, onlar hakkında bilmeniz gereken sınıfın / yöntemin aynı kapsamı içindeyse, onları bilmenin bir anlamı yoktur, çünkü onları zaten kendiniz beyan etmişsinizdir.


17
Bunun yukarıda yararlı olduğu durumlar kesinlikle vardır. Örneğin, yapılandırılmamış ORM çerçevelerinde.
BalusC

3
.. alan adını bilmenize gerek kalmadan tüm alanları almak için Class # getDeclaredFields () yöntemini kullanabilir.
BalusC

1
BalusC: Bana bir enjeksiyon çerçevesi gibi geliyor .. Bu zaten demek istediğim bir tür kullanım.
falstro

1
@loolooyyyy TypeLiteral kullanmak yerine Guava'dan TypeToken kullanmanızı öneririm. Açıklandı
Babyburger

1
@Oleg Değişkeniniz (sınıf türü) <?>yerine (joker karakter türü) kullanıyor <Class>. Senin yerine <?>biriminin by <Class>ya da her neyse <E>.
BalusC

19

Aynı şeyi yöntem parametreleri için de yapabilirsiniz:

Type[] types = method.getGenericParameterTypes();
//Now assuming that the first parameter to the method is of type List<Integer>
ParameterizedType pType = (ParameterizedType) types[0];
Class<?> clazz = (Class<?>) pType.getActualTypeArguments()[0];
System.out.println(clazz); //prints out java.lang.Integer

Method.getGenericParameterTypes (); yöntem nedir?
Neo Ravi

18

Kısa cevap: hayır.

Bu muhtemelen bir kopyadır, şu anda uygun bir tane bulamıyor.

Java, tür silme adı verilen bir şey kullanır, bu da çalışma zamanında her iki nesnenin de eşdeğer olduğu anlamına gelir. Derleyici, listelerin tamsayı veya dize içerdiğini bilir ve bu nedenle bir tür güvenli ortamı koruyabilir. Bu bilgiler çalışma zamanında (nesne örneği bazında) kaybolur ve liste yalnızca 'Nesneler' içerir.

Sınıf hakkında ne tür parametreler olabileceğini biraz öğrenebilirsiniz, ancak normalde bu sadece "Nesne" yi genişleten herhangi bir şeydir, yani herhangi bir şey. Bir türü şu şekilde tanımlarsanız:

class <A extends MyClass> AClass {....}

AClass.class sadece A parametresinin MyClass tarafından sınırlandırıldığı gerçeğini içerecektir, ancak bundan daha fazlasını söylemenin bir yolu yoktur.


1
Jenerik somut sınıfı belirtilmezse bu doğru olacaktır; örneğinde, listeleri açıkça List<Integer>ve olarak bildiriyor List<String>; bu durumlarda tip silme geçerli değildir.
Haroldo_OK

13

Bir koleksiyonun genel türü yalnızca içinde nesneler varsa önemli olmalıdır, değil mi? Yani bunu yapmak daha kolay değil:

Collection<?> myCollection = getUnknownCollectionFromSomewhere();
Class genericClass = null;
Iterator it = myCollection.iterator();
if (it.hasNext()){
    genericClass = it.next().getClass();
}
if (genericClass != null) { //do whatever we needed to know the type for

Çalışma zamanında genel bir tür diye bir şey yoktur, ancak çalışma zamanında içerideki nesnelerin bildirilen genel ile aynı tür olması garanti edilir, bu nedenle işlemeden önce öğenin sınıfını test etmek yeterince kolaydır.

Yapabileceğiniz başka bir şey, listeyi doğru türdeki üyeleri almak, başkalarını görmezden gelmek (veya farklı şekilde işlemek) için işlemektir.

Map<Class<?>, List<Object>> classObjectMap = myCollection.stream()
    .filter(Objects::nonNull)
    .collect(Collectors.groupingBy(Object::getClass));

// Process the list of the correct class, and/or handle objects of incorrect
// class (throw exceptions, etc). You may need to group subclasses by
// filtering the keys. For instance:

List<Number> numbers = classObjectMap.entrySet().stream()
        .filter(e->Number.class.isAssignableFrom(e.getKey()))
        .flatMap(e->e.getValue().stream())
        .map(Number.class::cast)
        .collect(Collectors.toList());

Bu, sınıfları alt sınıfları olan Numberve daha sonra istediğiniz gibi işleyebileceğiniz tüm öğelerin bir listesini verecektir . Öğelerin geri kalanı diğer listelere filtrelendi. Haritada oldukları için bunları istediğiniz gibi işleyebilir veya yoksayabilirsiniz.

Diğer sınıfların öğelerini tamamen göz ardı etmek istiyorsanız, çok daha basit hale gelir:

List<Number> numbers = myCollection.stream()
    .filter(Number.class::isInstance)
    .map(Number.class::cast)
    .collect(Collectors.toList());

Bir listenin SADECE belirli bir sınıfla eşleşen öğeleri içermesini sağlamak için bir yardımcı program yöntemi bile oluşturabilirsiniz:

public <V> List<V> getTypeSafeItemList(Collection<Object> input, Class<V> cls) {
    return input.stream()
            .filter(cls::isInstance)
            .map(cls::cast)
            .collect(Collectors.toList());
}

5
Ve eğer boş bir koleksiyonunuz varsa? Veya bir tamsayı ve bir dize ile bir koleksiyon <Object> varsa?
Falci

Sınıf sözleşmesi, yalnızca son nesnelerin listelerinin iletilmesini gerektirebilir ve yöntem boş, liste boşsa veya liste türü geçersizse yöntem çağrısı boş döndürür.
ggb667

1
Boş bir koleksiyon söz konusu olduğunda, çalışma zamanında herhangi bir liste türüne atamak güvenlidir, çünkü içinde hiçbir şey yoktur ve tür silme gerçekleştikten sonra Liste Listedir Listedir. Bu nedenle, boş bir koleksiyonun türünün önemli olacağı hiçbir örneği düşünemiyorum.
Steve K

Bir iş parçacığı içinde referans tutunmuş ve bir üretici tüketici senaryosunda nesneler görünmeye başladıktan sonra işlediyseniz "önemli" olabilir. Liste yanlış nesne türlerine sahipse kötü şeyler olabilir. Devam etmeden önce listede en az bir öğe olana kadar uyuyarak işlemi durdurmak zorunda kalacağınızı tahmin ediyorum.
ggb667

Bu tür üretici / tüketici senaryosuna sahipseniz, listeye ne konabileceğinden emin olmadan listenin belirli bir genel türe sahip olmasını planlamak kötü tasarımdır. Bunun yerine onu bir koleksiyon koleksiyonu olarak ilan eder Objectve listeden çıkarırsanız, türlerine göre uygun bir işleyiciye verirsiniz.
Steve K

11

Bir alanın genel türünü bulmak için:

((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()

10

Döndürülen türün genel türünü almanız gerekiyorsa, döndürülen bir sınıftaki yöntemleri bulup Collectionsonra genel türlerine erişmem gerektiğinde bu yaklaşımı kullandım :

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;

public class Test {

    public List<String> test() {
        return null;
    }

    public static void main(String[] args) throws Exception {

        for (Method method : Test.class.getMethods()) {
            Class returnClass = method.getReturnType();
            if (Collection.class.isAssignableFrom(returnClass)) {
                Type returnType = method.getGenericReturnType();
                if (returnType instanceof ParameterizedType) {
                    ParameterizedType paramType = (ParameterizedType) returnType;
                    Type[] argTypes = paramType.getActualTypeArguments();
                    if (argTypes.length > 0) {
                        System.out.println("Generic type is " + argTypes[0]);
                    }
                }
            }
        }

    }

}

Bu çıktılar:

Genel tür java.lang.String sınıfıdır


6

Steve K'nin cevabına genişleyen:

/** 
* Performs a forced cast.  
* Returns null if the collection type does not match the items in the list.
* @param data The list to cast.
* @param listType The type of list to cast to.
*/
static <T> List<? super T> castListSafe(List<?> data, Class<T> listType){
    List<T> retval = null;
    //This test could be skipped if you trust the callers, but it wouldn't be safe then.
    if(data!=null && !data.isEmpty() && listType.isInstance(data.iterator().next().getClass())) {
        @SuppressWarnings("unchecked")//It's OK, we know List<T> contains the expected type.
        List<T> foo = (List<T>)data;
        return retval;
    }
    return retval;
}
Usage:

protected WhateverClass add(List<?> data) {//For fluant useage
    if(data==null) || data.isEmpty(){
       throw new IllegalArgumentException("add() " + data==null?"null":"empty" 
       + " collection");
    }
    Class<?> colType = data.iterator().next().getClass();//Something
    aMethod(castListSafe(data, colType));
}

aMethod(List<Foo> foo){
   for(Foo foo: List){
      System.out.println(Foo);
   }
}

aMethod(List<Bar> bar){
   for(Bar bar: List){
      System.out.println(Bar);
   }
}

Steve K'ın cevabı ile aynı problemler: Ya liste boşsa? Liste bir String ve Integer içeren <Object> türündeyse ne olur? Kodunuz, yalnızca listedeki ilk öğe bir dize ve hiç tamsayı yoksa daha fazla Dize ekleyebileceğiniz anlamına gelir ...
subrunner

Liste boşsa şansınız kalmaz. Liste Nesne ise ve aslında bir Nesne içeriyorsa, sorun yoktur, ancak bir şeyler karışımı varsa şansınız kalmaz. Silme, bunun yapabildiğiniz kadar iyi olduğu anlamına gelir. Bence silme yok. Bunun sonuçları, hemen anlaşılamamıştır ve tipik endişe verici ilginç ve istenen kullanım durumlarını ele almak için yetersizdir. Hiçbir şey ne tür bir soru sorulması gereken bir sınıf oluşturmayı engellemez, ancak yerleşik sınıflar bu şekilde çalışmaz. Koleksiyonlar ne içerdiklerini bilmeli, ama ne yazık ki ...
ggb667

4

Çalışma zamanında, hayır, yapamazsınız.

Ancak yansıma yoluyla türü parametreleri vardır erişilebilir. Deneyin

for(Field field : this.getDeclaredFields()) {
    System.out.println(field.getGenericType())
}

Yöntem getGenericType()bir Type nesnesi döndürür. Bu durumda, bu ParametrizedTypeyöntemlerin getRawType()( List.classbu durumda içerecek olan ) sahip olduğu ve getActualTypeArguments()bir dizi döndürecek (bu örnekte, uzunluk olarak String.classveya birini içeren Integer.class) bir örneği olacaktır .


2
ve bir sınıfın alanları yerine bir yöntemle alınan parametreler için çalışıyor ???
opensas

@opensas method.getGenericParameterTypes()Bir yöntemin parametrelerinin bildirilen türlerini almak için kullanabilirsiniz .
Radiodef

4

Aynı sorun vardı, ama bunun yerine instanceof kullandım. Bu şekilde mi:

List<Object> listCheck = (List<Object>)(Object) stringList;
    if (!listCheck.isEmpty()) {
       if (listCheck.get(0) instanceof String) {
           System.out.println("List type is String");
       }
       if (listCheck.get(0) instanceof Integer) {
           System.out.println("List type is Integer");
       }
    }
}

Bu, denetlenmeyen dökümleri kullanmayı içerir, bu nedenle bunu yalnızca bir liste olduğunu ve ne tür olabileceğini bildiğinizde yapın.


3

Genellikle imkansız, çünkü List<String>ve List<Integer>aynı çalışma zamanı sınıfı paylaşırlar.

Yine de listeyi tutan alanın beyan edilen türünü yansıtabilirsiniz (beyan edilen türün kendisi değeri bilmediğiniz bir tür parametresine başvurmazsa).


2

Diğerlerinin söylediği gibi, tek doğru cevap hayırdır, tür silinmiştir.

Listede sıfır olmayan sayıda öğe varsa, ilk öğenin türünü araştırabilirsiniz (örneğin, getClass yöntemini kullanarak). Bu size listenin genel türünü söylemez, ancak genel türün listedeki türlerin bazı üst sınıfları olduğunu varsaymak makul olacaktır.

Bu yaklaşımı savunmam, ancak bir bağda yararlı olabilir.


Jenerik somut sınıfı belirtilmezse bu doğru olacaktır; örneğinde, listeleri açıkça List<Integer>ve olarak bildiriyor List<String>; bu durumlarda tip silme geçerli değildir.
Haroldo_OK

2
import org.junit.Assert;
import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class GenericTypeOfCollectionTest {
    public class FormBean {
    }

    public class MyClazz {
        private List<FormBean> list = new ArrayList<FormBean>();
    }

    @Test
    public void testName() throws Exception {
        Field[] fields = MyClazz.class.getFields();
        for (Field field : fields) {
            //1. Check if field is of Collection Type
            if (Collection.class.isAssignableFrom(field.getType())) {
                //2. Get Generic type of your field
                Class fieldGenericType = getFieldGenericType(field);
                //3. Compare with <FromBean>
                Assert.assertTrue("List<FormBean>",
                  FormBean.class.isAssignableFrom(fieldGenericType));
            }
        }
    }

    //Returns generic type of any field
    public Class getFieldGenericType(Field field) {
        if (ParameterizedType.class.isAssignableFrom(field.getGenericType().getClass())) {
            ParameterizedType genericType =
             (ParameterizedType) field.getGenericType();
            return ((Class)
              (genericType.getActualTypeArguments()[0])).getSuperclass();
        }
        //Returns dummy Boolean Class to compare with ValueObject & FormBean
        return new Boolean(false).getClass();
    }
}

1
ne getFieldGenericTypefieldGenericTypebulunamadı
shareef


0

FieldBunları almak için Yansıma kullanın, sonra şunları yapabilirsiniz: field.genericTypegenel hakkında da bilgi içeren türü almak için.


Bazı örnek kodlar eklemeyi düşünün, aksi takdirde bu bir yorum için değil, cevap için daha uygundur
Alexey Kamenskiy
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.