Java: Genel bir türden bir sınıf değişmezini nasıl alabilirim?


194

Tipik olarak, insanların sınıf değişmezini şu şekilde kullandığını gördüm:

Class<Foo> cls = Foo.class;

Peki ya tür genel ise, örneğin Liste? Bu iyi çalışıyor, ancak List'in parametrelendirilmesi gerektiğinden bir uyarı var:

Class<List> cls = List.class

Öyleyse neden bir <?>? Peki, bu tür uyumsuzluk hatasına neden olur:

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

Böyle bir şey işe yarayacağını düşündüm, ama bu sadece düz bir sözdizimi hatası:

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

Class<List<Foo>>Statik olarak nasıl alabilirim , örneğin sınıf değişmezini kullanarak?

Ben olabilir kullanmak @SuppressWarnings("unchecked"), ilk örnekte Listesinin olmayan parametreli kullanımından kaynaklanan uyarıları kurtulmak için Class<List> cls = List.class, ama ben tercih ederim.

Baska öneri?

Yanıtlar:


161

Tür silme nedeniyle yapamazsınız .

Java jenerikleri, Object dökümleri için sözdizimsel şekerden biraz daha fazlasıdır. Göstermek:

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

Genel tür bilgilerinin çalışma zamanında tutulduğu tek örnek, Field.getGenericType()bir sınıfın üyelerini yansıma yoluyla sorgulamaktır.

Tüm bu neden Object.getClass()bu imzayı taşıyor:

public final native Class<?> getClass();

Önemli olan varlık Class<?>.

Başka bir deyişle, Java Generics SSS bölümünden :

Beton parametreli tipler için neden sınıf değişmezi yoktur?

Çünkü parametrelenen türün tam çalışma zamanı türü gösterimi yoktur.

Sınıf değişmezi, Class belirli bir türü temsil eden bir nesneyi belirtir . Örneğin, sınıf değişmezi , türü temsil eden nesneyi String.classbelirtir ve yöntem bir nesneye çağrıldığında döndürülen nesne ile aynıdır . Sınıf değişmezi, çalışma zamanı türü denetimleri ve yansıma için kullanılabilir.ClassStringClassgetClassString

Parametrelendirilmiş türler, tür silme adı verilen bir işlemde derleme sırasında bayt koduna çevrildiklerinde tür bağımsız değişkenlerini kaybederler. Tür silme işleminin bir yan etkisi olarak, genel bir türün tüm örneklemeleri, aynı çalışma zamanı gösterimini, yani karşılık gelen ham türün gösterimini paylaşır. Başka bir deyişle, parametrelenen türlerin kendilerinin tür gösterimi yoktur. Sonuç olarak, orada gibi sınıf değişmezleri oluşturulmasında herhangi bir nokta List<String>.class, List<Long>.classve List<?>.class böyle bir yana, Classnesnelerin var. Yalnızca ham türün List, Class çalışma zamanı türünü temsil eden bir nesnesi vardır. Olarak adlandırılır List.class.


12
List<Integer> list1 = new ArrayList<Integer>(); List<String> list2 = (List<String>)list1; list2.add("foo"); // perfectly legal Bunu Java'da yapamazsanız, tür uyumsuzluğu derleme hatası alırsınız!
DhafirNz

4
yani ... ihtiyacım olursa ne yapmalıyım?
Christopher Francisco

2
Her zaman derleyiciyi kandırabilirsinList<String> list2 = (List<String>) (Object) list1;
kftse

17
Yine başka bir "Bu sadece C # çalışır, ama Java değil" benim için. Bir JSON nesnesinin serisini kaldırıyorum ve typeof (Liste <MyClass>) C # mükemmel çalışıyor, ancak Liste <MyClass> .class Java bir sözdizimi hatasıdır. Evet, bunun her zamanki gibi Cletus'un yazdığı gibi mantıklı bir açıklaması var, ama her zaman neden tüm bunların neden C # 'da çalıştığını merak ediyorum.
Lanet Sebzeler

2
Ne demek mükemmel yasal? Kodun bu kısmı derlenmiyor mu?
Eduardo Dennis

63

Parametreli türler için Sınıf değişmez değerleri yoktur, ancak bu türleri doğru tanımlayan Tür nesneleri vardır.

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

Google'ın Gson kütüphanesi, basitçe parametrelenmiş türler oluşturmaya izin veren bir TypeToken sınıfı tanımlar ve bunu, karmaşık parametreli türlere sahip json nesnelerini genel olarak uygun bir şekilde belirtmek için kullanır. Örneğinizde şunları kullanırsınız:

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

TypeToken ve Gson sınıfları javadoc'a bağlantılar göndermeyi amaçladım, ancak Stack Overflow, yeni bir kullanıcı olduğum için birden fazla bağlantı göndermeme izin vermiyor, Google aramasını kullanarak bunları kolayca bulabilirsiniz


1
Bu ile genel bir E ile bir sınıf oluşturmak ve daha clzz = new TypeToken<E>(){}.getRawType();sonra ile benzer yapılandırılmış numaralandırmalar üzerinde yineleme clzz.getEnumConstants()ve sonra nihayet üye yöntemleri a la Method method = clzz.getDeclaredMethod("getSomeFoo");çok kazanmak çağırmak için refection kullanmak mümkün oldu ! Teşekkür ederim!
Naruto Sempai

57

Bir çift döküm ile yönetebilirsiniz:

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


2
İkinci döküm değiştirerek Objectiçin Classmuhtemelen (anlamsız) yükü tasarrufu yapabilirsiniz çalışma zamanı döküm kontrol etti.
Clashsoft

2
Kullanılması @Clashsoft Classyerine Objectönerdiğiniz gibi, daha anlamlı görünüyor ama ihtiyacını kaldırmaz @SuppressWarnings("unchecked"): açıklama, hatta yeni bir uyarı eklemekClass is a raw type. References to generic type Class<T> should be parameterized
Ortomala Lokni

10
Şunları kullanabilirsiniz Class<?>:(Class<List<Foo>>)(Class<?>)List.class
Devstr

@Devstr Bunu denediğimde doğru olduğunu görüyorum ... (Nesne) veya (Sınıf <?>) Kullanmanın argümanları nelerdir?
cellepo

2
Bu cevap tamamen anlamsız. OP'nin sınıf yolunu parametreleştirmek istemesinin nedeni bir uncheckeduyarı almasıydı. Bu cevap hiçbirini değiştirmez / geliştirmez. OP bile SuppressWarnings
sorusunda

6

Cletus'un cevabına cevap vermek için çalışma zamanında genel türlerin tüm kayıtları kaldırılır. Jenerikler sadece derleyicide işlenir ve ek tip güvenlik sağlamak için kullanılır. Bunlar derleyicinin uygun yerlere daktilolar eklemesine izin veren sadece steno. Örneğin, daha önce aşağıdakileri yapmanız gerekir:

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

olur

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

Bu, derleyicinin kodunuzu derleme zamanında kontrol etmesine izin verir, ancak çalışma zamanında hala ilk örnek gibi görünür.


Ek açıklama için teşekkürler - jenerikler konusundaki anlayışım o kadar açık ki, bir çalışma zamanı mekanizması olmadığını anladım. :)
Tom

2
Bence bu, jenerik Sun tarafından vasat bir şekilde uygulandığı anlamına geliyor, umarım Oracle bunu bir gün düzeltir. C # 'in jenerik uygulaması çok daha iyidir (Anders tanrısaldır)
Marcel Valdez Orozco

1
@MarcelValdezOrozco AFAIK, Java'da bunu böyle uyguladılar çünkü eski kodun (1.5 öncesi) herhangi bir sorun olmadan yeni JVM'ler üzerinde çalışmasını istediler. Uyumluluğu önemseyen çok akıllı bir tasarım kararı gibi görünüyor. Bunda vasat bir şey olduğunu sanmıyorum.
peter.petrov

3

Java Jenerik SSS ve bu nedenle de cletus' cevabı sahip olmanın hiçbir anlamı yoktur gibi ses Class<List<T>>, ancak asıl sorun bu son derece tehlikeli olmasıdır:

@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()Güvenli olduğunu sanki markaları görünmesini, ama gerçekte hepsi güvenli değildir.


Nerede alamadım rağmen List<?>.class= Class<List<?>>bir Classargüman genel türüne dayalı türü belirleyen bir yöntem olduğunda bu oldukça yararlı olacaktır .

İçin getClass()var JDK-6184881 joker kullanarak anahtara talep eden bu değişiklik (çok yakında) yapılacaktır gibi önceki kod ile uyumlu olmadığından, ancak (bkz görünmüyor Bu yorumu ).


2

Hepimizin bildiği gibi silinir. Ancak, türün sınıf hiyerarşisinde açıkça belirtildiği bazı durumlarda bilinebilir:

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

Ve şimdi şöyle şeyler yapabilirsiniz:

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

Daha fazla bilgi burada . Ama yine de, geri almak neredeyse imkansız:

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

nerede silinir.


Bu aynı zamanda JAX-RS, krş. GenericEntityve GenericType.
Hein Blöd

1

Sınıf değişmezlerinin genel tür bilgilerine sahip olmadığı gerçeği nedeniyle, tüm uyarılardan kurtulmanın imkansız olacağını varsaymanız gerektiğini düşünüyorum. Bir bakıma, kullanmak Class<Something>, genel türü belirtmeden bir koleksiyonu kullanmakla aynıdır. Ben ortaya çıkabilir en iyi oldu:

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")Bir sınıfın her yerinden kurtulmak için bir yardımcı yöntem kullanabilirsiniz .

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

Sonra yazabilirsin

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

Diğer kullanım örnekleri

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

Ancak, nadiren gerçekten yararlı olduğundan ve yöntemin kullanımı derleyicinin tür denetimini bozduğundan, bunu herkesin erişebileceği bir yerde uygulamayı önermem.

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.