รับประเภททั่วไปของคลาสที่รันไทม์


508

ฉันจะบรรลุสิ่งนี้ได้อย่างไร

public class GenericClass<T>
{
    public Type getMyType()
    {
        //How do I return the type of T?
    }
}

ทุกสิ่งที่ฉันได้ลองมาแล้วจะส่งคืนประเภทเสมอObjectมากกว่าประเภทเฉพาะที่ใช้


3
@SamuelVidal นี่ไม่ใช่. NET นี่คือ Java
Frontear

คำตอบ:


317

ดังที่คนอื่น ๆ กล่าวถึงเป็นไปได้ผ่านการไตร่ตรองในบางสถานการณ์เท่านั้น

หากคุณต้องการประเภทจริง ๆ นี่เป็นรูปแบบการแก้ปัญหาตามปกติ (ประเภทปลอดภัย):

public class GenericClass<T> {

     private final Class<T> type;

     public GenericClass(Class<T> type) {
          this.type = type;
     }

     public Class<T> getMyType() {
         return this.type;
     }
}

58
ฉันชอบคำตอบนี้ แต่มันค่อนข้างยุ่งยากในการสร้างอินสแตนซ์: GenericClass <AnotherClass> g = new GenericClass <AnotherClass> (AnotherClass.class);
Eliseo Ocampos

1
มันละเอียดยิ่งขึ้นถ้าคุณใช้แนวทาง dao / factory / manager Foo foo1 = GetDao<Foo>(Foo.class).get(Foo.class, 1)
djmj

2
เป็นจริง แต่ไม่สามารถใช้งานได้ในทุกกรณีเช่นถั่วไร้สายระยะไกลซึ่งมีการสร้างอินสแตนซ์โดยคอนเทนเนอร์ / การสะท้อน
djmj

6
เช่นเดียวกับการติดตามความคิดเห็นก่อนหน้าของฉัน - หลังจากความเจ็บปวดมากมายที่เล่นกับการสะท้อนฉันจบลงด้วยการใช้คำตอบนี้
Tomáš Zato - Reinstate Monica

18
คุณสามารถหลีกเลี่ยงการอ้างอิงฟุ่มเฟือยได้โดยจัดทำวิธีการคงที่จากโรงงานทั่วไป สิ่งที่ต้องการแล้วเรียกมันว่าเป็นเช่น:public static <T> GenericClass<T> of(Class<T> type) {...} GenericClass<String> var = GenericClass.of(String.class)ดีกว่านิดหน่อย
Joeri Hendrickx

262

ฉันเห็นบางสิ่งเช่นนี้

private Class<T> persistentClass;

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

ในไฮเบอร์เนต GenericDataAccessObjectsตัวอย่าง


84
เทคนิคนี้ทำงานโดยที่พารามิเตอร์ type ถูกกำหนดไว้บนซูเปอร์คลาสทันที แต่จะล้มเหลวหากพารามิเตอร์ type ถูกกำหนดไว้ที่อื่นในลำดับชั้นชนิด สำหรับการจัดการกับกรณีที่ซับซ้อนมากขึ้นคุณสามารถใช้บางอย่างเช่นTypeToolsได้ เอกสารประกอบด้วยตัวอย่างของ DAO ทั่วไปที่ซับซ้อนยิ่งขึ้น
Jonathan

25
สิ่งนี้จะคืนค่าพารามิเตอร์ประเภทจริงที่ใช้เมื่อCLASSใช้ / ขยายบางสิ่งที่มีการประกาศทั่วไป แต่จะไม่ส่งคืนพารามิเตอร์ชนิดจริงที่ใช้เมื่อINSTANCEถูกสร้างอินสแตนซ์ ในคำอื่น ๆ ก็สามารถบอกได้ว่าในclass A implements Comparable<String>พารามิเตอร์ชนิดที่เกิดขึ้นจริงStringแต่มันก็ไม่สามารถบอกได้ว่าในพารามิเตอร์ชนิดที่เกิดขึ้นจริงSet<String> a = new TreeSet<String>() Stringในความเป็นจริงข้อมูลพารามิเตอร์ประเภท "ลบ" หลังจากการรวบรวมตามที่อธิบายในคำตอบอื่น ๆ
งุดชิงโปง - อะซึกะเคนจิ -

32
ฉันได้java.lang.Class cannot be cast to java.lang.reflect.ParameterizedTypeคำตอบนี้แล้ว
Tomáš Zato - Reinstate Monica

4
วิธีการนี้สามารถทำได้โดยใช้Class-Mateจากคนแจ็คสัน ฉันเขียนส่วนสำคัญที่นี่gist.github.com/yunspace/930d4d40a787a1f6a7d1
yunspace

5
@ TomášZatoการเรียกรหัสข้างบนนั้นกลับข้อยกเว้นแบบเดียวกันสำหรับฉัน ฉันรู้ว่ามันช้าไปหน่อย แต่อย่างไรก็ตามในกรณีของฉันฉันต้องโทรหา(Class<T>) ((ParameterizedType)getClass().getSuperclass().getGenericSuperclass()).getActualTypeArguments()อาร์กิวเมนต์ชนิดจริง
AndrewMcCoist

98

ยาชื่อสามัญจะไม่reifiedที่ใช้เวลา ซึ่งหมายความว่าข้อมูลจะไม่แสดง ณ เวลาทำการ

การเพิ่มยาชื่อสามัญให้กับ Java ในขณะที่ความเข้ากันได้ย้อนหลังเป็นทัวร์พลัง (คุณสามารถดูเอกสารน้ำเชื้อเกี่ยวกับมัน: ทำให้อนาคตที่ปลอดภัยสำหรับอดีต: เพิ่มความมีชีวิตชีวาให้กับภาษาการเขียนโปรแกรมภาษาจาวา )

มีวรรณกรรมมากมายเกี่ยวกับเรื่องนี้และบางคนไม่พอใจกับสถานะปัจจุบันบางคนบอกว่าจริง ๆ แล้วมันเป็นสิ่งล่อใจและไม่มีความต้องการที่แท้จริงสำหรับมัน คุณสามารถอ่านลิงก์ทั้งสองได้ฉันพบว่ามันน่าสนใจทีเดียว


179
แน่นอนว่าเราไม่พอใจ. NET มีกลไกการจัดการทั่วไปที่ดีกว่ามาก
Pacerier

6
@Pacerier: แต่การแก้ไขข้อมูลทั่วไปเพียงอย่างเดียวจะไม่ทำให้ Java อยู่ในระดับ. NET ประเภทค่ารหัสพิเศษสำหรับรหัสเหล่านั้นมีความสำคัญเท่าเทียมกันอย่างน้อยสำหรับสาเหตุที่. NET ดีกว่าในพื้นที่ข้อมูลทั่วไป
Joachim Sauer

@JoachimSauer ใช่ประเภทค่า ฉันต้องการที่อยู่ในจาวาเสมอ Btw คุณหมายถึงอะไรโดยรหัสพิเศษ?
Pacerier

3
@ spaaarky21 ไม่พารามิเตอร์ประเภททั่วไปจะถูกลบออกในระหว่างการรวบรวม (ที่เรียกว่า "การลบ" คุณสามารถ google ได้) เคล็ดลับในคำตอบของ FrVaBe จะใช้ได้ก็ต่อเมื่อรู้จักประเภทพารามิเตอร์ของ superclass แบบคงที่ (ดูคำยกย่องแรกของ Johnathn)
ewernli

1
การลบประเภท Java เป็นข้อบกพร่องในการออกแบบทางประวัติศาสตร์ มีการเขียนโค้ดเพิ่มเติมเพื่อหลีกเลี่ยงการเขียนมากกว่าที่จะเขียน
Abhijit Sarkar

68

ใช้ฝรั่ง

import com.google.common.reflect.TypeToken;
import java.lang.reflect.Type;

public abstract class GenericClass<T> {
  private final TypeToken<T> typeToken = new TypeToken<T>(getClass()) { };
  private final Type type = typeToken.getType(); // or getRawType() to return Class<? super T>

  public Type getType() {
    return type;
  }

  public static void main(String[] args) {
    GenericClass<String> example = new GenericClass<String>() { };
    System.out.println(example.getType()); // => class java.lang.String
  }
}

ในขณะที่ก่อนหน้านี้ฉันโพสต์ตัวอย่างเต็มรูปแบบบางคลาสรวมถึงคลาสนามธรรมและคลาสย่อยที่นี่ที่นี่

หมายเหตุ: สิ่งนี้ต้องการให้คุณสร้างอินสแตนซ์ย่อยของGenericClassมันเพื่อให้สามารถผูกพารามิเตอร์ประเภทได้อย่างถูกต้อง Tมิฉะนั้นมันก็จะกลับเป็นประเภท


3
โปรดสังเกตว่าฉันสร้าง subclass ที่ไม่ระบุชื่อว่างเปล่า (ดูที่เครื่องหมายปีกกาสองอันในตอนท้าย) สิ่งนี้ใช้การสะท้อนเพื่อต่อสู้กับการลบประเภทรันไทม์ของ Java คุณสามารถเรียนรู้เพิ่มเติมได้ที่นี่: code.google.com/p/guava-l ไลบรารี/wiki/ReflectionExplained
Cody A. Ray

3
@ CodyA.Ray java.lang.IllegalArgumentException: class com.google.common.reflect.TypeToken isn't parameterizedรหัสของคุณพ่น ดังนั้นผมจึงเปลี่ยนสายไปnew TypeToken(getClass()) { } new TypeToken<T>(getClass()) { }ตอนนี้โค้ดทำงานได้ดี แต่ประเภทยังคงเป็น 'T' ดูสิ่งนี้: gist.github.com/m-manu/9cda9d8f9d53bead2035
มนู Manjunath

3
@Dominik โปรดดูตัวอย่างการอัปเดตที่คุณสามารถคัดลอกและวางเพื่อทดสอบด้วยตัวเอง ฉันได้เพิ่มบันทึกย่อที่อธิบายว่าคุณต้องยกตัวอย่างคลาสย่อย (ดังที่แสดง) ตามมารยาททั่วไปโปรดอ่านบทความที่เชื่อมโยงใด ๆ และ javadocs ที่เกี่ยวข้องก่อนที่คุณจะกล่าวหาโปสเตอร์ "ความคิดนึกอยากรู้" ฉันใช้รหัสการผลิตที่คล้ายกันหลายครั้ง ผู้ช่วยของ Guava ที่ฉันแสดงให้เห็นนั้นมีไว้สำหรับกรณีการใช้งานจริงและ javadocs ของพวกเขาจะแสดงคำตอบที่ถูกต้องสำหรับคำถามนี้ docs.guava
Cody A. Ray

1
คำตอบนี้ใช้ได้ดีกับ Java8 - ฉันมีอินเทอร์เฟซและคลาสโรงงานเพื่อปล่อยประเภทต่าง ๆ และต้องการให้ผู้ใช้สามารถกำหนดประเภทได้อย่างง่ายดาย ในอินเทอร์เฟซของฉันฉันเพิ่มวิธีการเริ่มต้น: default Type getParameterType() { final TypeToken<T> typeToken = new TypeToken<T>(getClass()) {}; final Type type = typeToken.getType(); return type; }
Richard Sand

2
@ CodyA.Ray เนื่องจากใช้งานได้กับคลาสย่อยของGenericClassเท่านั้นคุณควรทำให้คลาสabstractนั้นใช้งานผิดดังนั้นจึงไม่ได้รวบรวม
มาร์ติน

37

แน่นอนว่าคุณสามารถ

Java ไม่ใช้ข้อมูล ณ เวลาทำงานเนื่องจากเหตุผลด้านความเข้ากันได้ย้อนหลัง แต่ข้อมูลนั้นแสดงเป็นเมทาดาทาจริงและสามารถเข้าถึงได้ผ่านการสะท้อนกลับ (แต่ยังไม่ได้ใช้สำหรับการตรวจสอบประเภท)

จาก API อย่างเป็นทางการ:

http://download.oracle.com/javase/6/docs/api/java/lang/reflect/ParameterizedType.html#getActualTypeArguments%28%29

อย่างไรก็ตามสำหรับสถานการณ์ของคุณฉันจะไม่ใช้การสะท้อน โดยส่วนตัวฉันมีแนวโน้มที่จะใช้สิ่งนั้นสำหรับรหัสกรอบ ในกรณีของคุณฉันจะเพิ่มประเภทเป็นพารามิเตอร์ตัวสร้าง


10
getActualTypeArguments ส่งคืนอาร์กิวเมนต์ชนิดสำหรับคลาสทันที หากคุณมีลำดับชั้นของประเภทที่ซับซ้อนโดยที่ T สามารถกำหนดค่าพารามิเตอร์ได้ทุกที่ในลำดับชั้นคุณจะต้องทำงานเล็กน้อยเพื่อหาว่ามันคืออะไร นี่เป็นสิ่งที่TypeToolsทำมากขึ้นหรือน้อยลง
Jonathan

36

Java generics เป็นส่วนใหญ่เวลารวบรวมซึ่งหมายความว่าข้อมูลประเภทจะหายไปที่รันไทม์

class GenericCls<T>
{
    T t;
}

จะถูกรวบรวมไปยังสิ่งที่ชอบ

class GenericCls
{
   Object o;
}

ในการรับข้อมูลชนิดที่รันไทม์คุณต้องเพิ่มมันเป็นอาร์กิวเมนต์ของ ctor

class GenericCls<T>
{
     private Class<T> type;
     public GenericCls(Class<T> cls)
     {
        type= cls;
     }
     Class<T> getType(){return type;}
}

ตัวอย่าง:

GenericCls<?> instance = new GenericCls<String>(String.class);
assert instance.getType() == String.class;

9
private final Class<T> type;
naXa

ฉันจะสร้างประเภทอาเรย์ได้อย่างไร:Type t = //String[]
Pawel Cioch

@PawelCioch java.lang.reflect.Array.newInstance (ประเภทองค์ประกอบความยาว); หวังว่าสิ่งนี้จะช่วยให้ (javadoc สามารถพบได้ที่นี่docs.oracle.com/javase/8/docs/api/java/lang/reflect/… )
josefx

@PawelCioch พลาด. getClass () เพื่อรับชนิดจากอาร์เรย์ที่สร้างขึ้น ดูเหมือนจะไม่มีวิธีโดยตรงในการรับคลาสอาเรย์ คอลเลกชัน Java ส่วนใหญ่จะใช้ Object [] แทน
josefx

23
public abstract class AbstractDao<T>
{
    private final Class<T> persistentClass;

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

3
ฉัน upvoting คำตอบนี้เพราะมันเป็นวิธีแก้ปัญหาสำหรับคำถามที่ถูกถาม อย่างไรก็ตามสำหรับผู้ที่ต้องการนำทางขึ้นในลำดับชั้นของชั้นอย่างฉันกับคลาสทั่วไปมากกว่าหนึ่งคลาสสิ่งนี้จะไม่ทำงาน เพราะคุณจะได้รับ java.lang.object แทนที่จะเป็นคลาสจริง
KMC

3
โปรดทราบว่าวิธีนี้ใช้งานได้เฉพาะในกรณีที่คลาสที่มีประเภททั่วไปเป็นแบบ
ย่อ

ไม่ได้ทำงานกับคลาสนามธรรมของฉันใน Java 11
JRA_TLL

@JRA_TLL เห็นได้ชัดว่าคุณทำอะไรผิดพลาด ฉันเพิ่งใช้กับ Java 12 และใช้งานได้อย่างมีเสน่ห์
Googie

หากคุณต้องการเลื่อนดูในลำดับชั้นการดูคุณสามารถแปลง genericSuperclass เป็น Class <*> และรับ genericSuperclass โดยเฉพาะอย่างยิ่งในวง
fupduck

21

ฉันใช้วิธีการติดตาม:

public class A<T> {

    protected Class<T> clazz;

    public A() {
        this.clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    public Class<T> getClazz() {
        return clazz;
    }
}

public class B extends A<C> {
   /* ... */
    public void anything() {
       // here I may use getClazz();
    }
}

9
ฉันได้รับ "ข้อยกเว้นในเธรด" หลัก "java.lang.ClassCastException: java.lang.Class ไม่สามารถส่งไปยัง java.lang.reflect.ParameterizedType" ด้วยรหัสตัวอย่างนี้
yuyang

16

ฉันไม่คิดว่าคุณจะทำได้ Java ใช้การลบประเภทเมื่อรวบรวมเพื่อให้โค้ดของคุณเข้ากันได้กับแอปพลิเคชันและไลบรารีที่สร้างไว้ล่วงหน้า

จากเอกสาร Oracle:

ลบประเภท

Generics ถูกนำมาใช้กับภาษา Java เพื่อให้การตรวจสอบประเภทที่เข้มงวดมากขึ้นในเวลารวบรวมและเพื่อสนับสนุนการเขียนโปรแกรมทั่วไป หากต้องการใช้งาน generics คอมไพเลอร์ Java จะใช้การลบประเภทกับ:

แทนที่พารามิเตอร์ประเภททั้งหมดในประเภททั่วไปด้วยขอบเขตหรือวัตถุหากพารามิเตอร์ประเภทนั้นไม่ได้ จำกัด ขอบเขต รหัสไบต์ที่ผลิตจึงมีเฉพาะคลาสอินเทอร์เฟซและวิธีการทั่วไปเท่านั้น ใส่ชนิดปลดเปลื้องหากจำเป็นเพื่อรักษาความปลอดภัยประเภท สร้างวิธีการบริดจ์เพื่อรักษาความหลากหลายในรูปแบบทั่วไปเพิ่มเติม การลบประเภททำให้มั่นใจได้ว่าจะไม่มีการสร้างคลาสใหม่สำหรับประเภทที่กำหนดพารามิเตอร์ ดังนั้นข้อมูลทั่วไปไม่มีค่าใช้จ่ายรันไทม์

http://docs.oracle.com/javase/tutorial/java/generics/erasure.html


Jup มันเป็นไปไม่ได้ Java จะต้องมีการตรวจสอบข้อมูลทั่วไปใหม่เพื่อให้สามารถใช้งานได้
Henning

15

เทคนิคที่อธิบายไว้ในบทความนี้โดย Ian Robertson ได้ผลสำหรับฉัน

ในตัวอย่างรวดเร็วและสกปรกสั้น ๆ :

 public abstract class AbstractDAO<T extends EntityInterface, U extends QueryCriteria, V>
 {
    /**
     * Method returns class implementing EntityInterface which was used in class
     * extending AbstractDAO
     *
     * @return Class<T extends EntityInterface>
     */
    public Class<T> returnedClass()
    {
        return (Class<T>) getTypeArguments(AbstractDAO.class, getClass()).get(0);
    }

    /**
     * Get the underlying class for a type, or null if the type is a variable
     * type.
     *
     * @param type the type
     * @return the underlying class
     */
    public static Class<?> getClass(Type type)
    {
        if (type instanceof Class) {
            return (Class) type;
        } else if (type instanceof ParameterizedType) {
            return getClass(((ParameterizedType) type).getRawType());
        } else if (type instanceof GenericArrayType) {
            Type componentType = ((GenericArrayType) type).getGenericComponentType();
            Class<?> componentClass = getClass(componentType);
            if (componentClass != null) {
                return Array.newInstance(componentClass, 0).getClass();
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * Get the actual type arguments a child class has used to extend a generic
     * base class.
     *
     * @param baseClass the base class
     * @param childClass the child class
     * @return a list of the raw classes for the actual type arguments.
     */
    public static <T> List<Class<?>> getTypeArguments(
            Class<T> baseClass, Class<? extends T> childClass)
    {
        Map<Type, Type> resolvedTypes = new HashMap<Type, Type>();
        Type type = childClass;
        // start walking up the inheritance hierarchy until we hit baseClass
        while (!getClass(type).equals(baseClass)) {
            if (type instanceof Class) {
                // there is no useful information for us in raw types, so just keep going.
                type = ((Class) type).getGenericSuperclass();
            } else {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                Class<?> rawType = (Class) parameterizedType.getRawType();

                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
                for (int i = 0; i < actualTypeArguments.length; i++) {
                    resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
                }

                if (!rawType.equals(baseClass)) {
                    type = rawType.getGenericSuperclass();
                }
            }
        }

        // finally, for each actual type argument provided to baseClass, determine (if possible)
        // the raw class for that type argument.
        Type[] actualTypeArguments;
        if (type instanceof Class) {
            actualTypeArguments = ((Class) type).getTypeParameters();
        } else {
            actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
        }
        List<Class<?>> typeArgumentsAsClasses = new ArrayList<Class<?>>();
        // resolve types by chasing down type variables.
        for (Type baseType : actualTypeArguments) {
            while (resolvedTypes.containsKey(baseType)) {
                baseType = resolvedTypes.get(baseType);
            }
            typeArgumentsAsClasses.add(getClass(baseType));
        }
        return typeArgumentsAsClasses;
    }
  }

3
บรรทัดที่ระบุในรหัสนี้เป็นจริงพารามิเตอร์ประเภทรันไทม์กำลังอ่าน?
Ivan Matavulj

ที่นี่? Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
Ondrej Bozek

9

ฉันคิดว่ามีวิธีแก้ไขปัญหาที่สวยงามอีกอย่างหนึ่ง

สิ่งที่คุณต้องการทำคือ "ผ่าน" ประเภทของพารามิเตอร์ประเภททั่วไปที่เพิ่มขึ้นจากคลาส concerete ไปยังซูเปอร์คลาส

หากคุณอนุญาตให้คุณนึกถึงประเภทของชั้นเรียนว่าเป็น "ข้อมูลเมตา" ในชั้นเรียนซึ่งจะแนะนำวิธีการ Java สำหรับการเข้ารหัสข้อมูลเมตาในตอนรันไทม์: คำอธิบายประกอบ

ก่อนกำหนดคำอธิบายประกอบที่กำหนดเองตามบรรทัดเหล่านี้:

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EntityAnnotation {
    Class entityClass();
}

จากนั้นคุณสามารถเพิ่มคำอธิบายประกอบในคลาสย่อยของคุณ

@EntityAnnotation(entityClass =  PassedGenericType.class)
public class Subclass<PassedGenericType> {...}

จากนั้นคุณสามารถใช้รหัสนี้เพื่อรับชนิดคลาสในคลาสพื้นฐานของคุณ:

import org.springframework.core.annotation.AnnotationUtils;
.
.
.

private Class getGenericParameterType() {
    final Class aClass = this.getClass();
    EntityAnnotation ne = 
         AnnotationUtils.findAnnotation(aClass, EntityAnnotation.class);

    return ne.entityClass();
}

ข้อ จำกัด บางประการของวิธีนี้คือ:

  1. คุณระบุประเภททั่วไป ( PassedGenericType) ในสองสถานที่มากกว่าหนึ่งซึ่งไม่ใช่ DRY
  2. สิ่งนี้เป็นไปได้ก็ต่อเมื่อคุณสามารถแก้ไขคลาสย่อยคอนกรีตได้

ใช่ไม่ใช่ของแห้ง แต่สะอาดกว่าวิธีส่วนขยายที่แนะนำข้างต้น ฉันชอบมัน. ขอบคุณ
agodinhost

8

นี่คือทางออกของฉัน:

import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

public class GenericClass<T extends String> {

  public static void main(String[] args) {
     for (TypeVariable typeParam : GenericClass.class.getTypeParameters()) {
      System.out.println(typeParam.getName());
      for (Type bound : typeParam.getBounds()) {
         System.out.println(bound);
      }
    }
  }
}

10
นี่ไม่ใช่คำตอบสำหรับคำถามนี้
Adam Arold

1
รหัสของฉันไม่ใช่คำตอบที่ถูกต้องสำหรับคำถาม มันคืนค่าพารามิเตอร์ประเภททั่วไปของคลาส แต่ไม่ใช่ประเภท T จริง แต่อาจเป็นประโยชน์สำหรับผู้อื่นที่หยุดคำถามและกำลังมองหาวิธีแก้ปัญหาของฉัน
Matthias M

1
getClass (). getGenericSuperclass () จะได้ผลเช่นเดียวกัน
Gorky

5

นี่คือทางออกการทำงาน !!!

@SuppressWarnings("unchecked")
    private Class<T> getGenericTypeClass() {
        try {
            String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();
            Class<?> clazz = Class.forName(className);
            return (Class<T>) clazz;
        } catch (Exception e) {
            throw new IllegalStateException("Class is not parametrized with generic type!!! Please use extends <> ");
        }
    } 

หมายเหตุ: สามารถใช้เป็นซูเปอร์
คลาสเท่านั้น 1. ต้องขยายด้วยคลาสที่พิมพ์ ( Child extends Generic<Integer>)
หรือ

2. จะต้องสร้างเป็นการใช้งานแบบไม่ระบุชื่อ ( new Generic<Integer>() {};)


4

คุณทำไม่ได้ หากคุณเพิ่มตัวแปรสมาชิกประเภท T ลงในคลาส (คุณไม่จำเป็นต้องกำหนดค่าเริ่มต้น) คุณสามารถใช้ตัวแปรนั้นเพื่อกู้คืนชนิด


2
อ๊ะโอเค คุณไม่ต้อง initialise ได้จากผู้สร้าง
andrewmu

4

นี่เป็นวิธีหนึ่งซึ่งฉันต้องใช้หนึ่งหรือสองครั้ง:

public abstract class GenericClass<T>{
    public abstract Class<T> getMyType();
}

พร้อมด้วย

public class SpecificClass extends GenericClass<String>{

    @Override
    public Class<String> getMyType(){
        return String.class;
    }
}

4
เทคนิคนี้ใช้งานได้ แต่ก็ไม่ได้แก้ปัญหากรณีทั่วไปและฉันคิดว่านั่นคือสิ่งที่โปสเตอร์ต้นฉบับเป็นตามมา
ggb667

สิ่งนี้ไม่สมควรที่จะถูกโหวตอย่างที่เคยมี - โปสเตอร์ดั้งเดิมยังไม่ชัดเจน คำตอบนี้นำเสนอรูปแบบการออกแบบที่ใช้งานได้และง่ายต่อการนำไปใช้เพราะมันเหมาะสมที่จะทำให้นามธรรมของคลาสทั่วไป
VirtualMichael

4

วิธีแก้ปัญหาง่ายๆสำหรับรถแท็กซี่นี้จะเป็นดังนี้

public class GenericDemo<T>{
    private T type;

    GenericDemo(T t)
    {
        this.type = t;
    }

    public String getType()
    {
        return this.type.getClass().getName();
    }

    public static void main(String[] args)
    {
        GenericDemo<Integer> obj = new  GenericDemo<Integer>(5);
        System.out.println("Type: "+ obj.getType());
    }
}

3

เพื่อให้คำตอบที่นี่ฉันต้องรับ ParametrizedType ของ MyGenericClass ไม่ว่าลำดับชั้นจะสูงแค่ไหนด้วยความช่วยเหลือของการเรียกซ้ำ:

private Class<T> getGenericTypeClass() {
        return (Class<T>) (getParametrizedType(getClass())).getActualTypeArguments()[0];
}

private static ParameterizedType getParametrizedType(Class clazz){
    if(clazz.getSuperclass().equals(MyGenericClass.class)){ // check that we are at the top of the hierarchy
        return (ParameterizedType) clazz.getGenericSuperclass();
    } else {
        return getParametrizedType(clazz.getSuperclass());
    }
}

1

นี่คือเคล็ดลับของฉัน:

public class Main {

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

        System.out.println(Main.<String> getClazz());

    }

    static <T> Class getClazz(T... param) {

        return param.getClass().getComponentType();
    }

}

1
หมายเหตุ: สิ่งนี้จะไม่ทำงานเมื่อTเป็นตัวแปรประเภท ในกรณีที่Tเป็นตัวแปรชนิด varargs Tสร้างอาร์เรย์ของการลบออกของ ดูเช่นhttp://ideone.com/DIPNwd
Radiodef

1
ส่งคืน "Object"
yahya

อาจเป็นเพราะคุณกำลังพยายามตอบคำถามอื่น ๆ
Khan

1

ในกรณีที่คุณใช้เก็บตัวแปรโดยใช้ประเภททั่วไปคุณสามารถแก้ไขปัญหานี้ได้อย่างง่ายดายโดยเพิ่มเมธอด getClassType ดังนี้:

public class Constant<T> {
  private T value;

  @SuppressWarnings("unchecked")
  public Class<T> getClassType () {
    return ((Class<T>) value.getClass());
  }
}

ฉันใช้วัตถุคลาสที่ให้ไว้ในภายหลังเพื่อตรวจสอบว่าเป็นตัวอย่างของคลาสที่กำหนดดังนี้:

Constant<?> constant = ...;
if (constant.getClassType().equals(Integer.class)) {
    Constant<Integer> integerConstant = (Constant<Integer>)constant;
    Integer value = integerConstant.getValue();
    // ...
}

2
นี่เป็นปัญหาโชคไม่ดี ก่อนอื่นจะเกิดอะไรขึ้นถ้าvalueเป็นnull? ประการที่สองจะเกิดอะไรขึ้นถ้าvaluesubclass ของT? Constant<Number> c = new Constant<Number>(new Integer(0)); Class<Number> n = c.getClassType();ผลตอบแทนที่ได้เมื่อมันควรจะกลับInteger.class มันจะถูกต้องมากขึ้นที่จะกลับมาNumber.class เป็นแต่ไม่ Class<? extends T>Integer.class Class<? extends Number>Class<Number>
Radiodef

1

นี่คือทางออกของฉัน

public class GenericClass<T>
{
    private Class<T> realType;

    public GenericClass() {
        findTypeArguments(getClass());
    }

    private void findTypeArguments(Type t) {
        if (t instanceof ParameterizedType) {
            Type[] typeArgs = ((ParameterizedType) t).getActualTypeArguments();
            realType = (Class<T>) typeArgs[0];
        } else {
            Class c = (Class) t;
            findTypeArguments(c.getGenericSuperclass());
        }
    }

    public Type getMyType()
    {
        // How do I return the type of T? (your question)
        return realType;
    }
}

ไม่ว่าลำดับชั้นของคลาสของคุณจะมีอยู่กี่ระดับโซลูชันนี้ยังคงใช้งานได้เช่น:

public class FirstLevelChild<T> extends GenericClass<T> {

}

public class SecondLevelChild extends FirstLevelChild<String> {

}

ในกรณีนี้ getMyType () = java.lang.String


นี่ไม่ได้ส่งคืนชนิดของ T แต่จะส่งคืน T ไม่ใช่ java.lang.String นอกเหนือจากรหัสล้มเหลวในการแอบแฝง Type เป็น Class <T>
Mohy Eldeen

นี่คือตัวอย่างออนไลน์ที่ฉันทำ คลิกการคอมไพล์และดำเนินการจากนั้นคุณจะได้รับผลลัพธ์ tutorialspoint.com/…
หลินยูเฉิง

ใช้งานได้สำหรับฉัน - เมื่อ WildFly Weld CDI แตกวิธีการอื่น
อังเดร

ฉันได้รับException in thread "main" java.lang.NullPointerException at Main$ClassA.findTypeArguments(Main.java:54) at Main$ClassA.findTypeArguments(Main.java:54) at Main$ClassA.findTypeArguments(Main.java:54) at Main$ClassA.<init>(Main.java:43) at Main.main(Main.java:61)
yuyang

0
public static final Class<?> getGenericArgument(final Class<?> clazz)
{
    return (Class<?>) ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[0];
}

0

นี่คือทางออกของฉัน ตัวอย่างควรอธิบาย ข้อกำหนดเพียงอย่างเดียวคือคลาสย่อยต้องตั้งค่าประเภททั่วไปไม่ใช่วัตถุ

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.Map;

public class TypeUtils {

    /*** EXAMPLES ***/

    public static class Class1<A, B, C> {

        public A someA;
        public B someB;
        public C someC;

        public Class<?> getAType() {
            return getTypeParameterType(this.getClass(), Class1.class, 0);
        }

        public Class<?> getCType() {
            return getTypeParameterType(this.getClass(), Class1.class, 2);
        }
    }

    public static class Class2<D, A, B, E, C> extends Class1<A, B, C> {

        public B someB;
        public D someD;
        public E someE;
    }

    public static class Class3<E, C> extends Class2<String, Integer, Double, E, C> {

        public E someE;
    }

    public static class Class4 extends Class3<Boolean, Long> {

    }

    public static void test() throws NoSuchFieldException {

        Class4 class4 = new Class4();
        Class<?> typeA = class4.getAType(); // typeA = Integer
        Class<?> typeC = class4.getCType(); // typeC = Long

        Field fieldSomeA = class4.getClass().getField("someA");
        Class<?> typeSomeA = TypeUtils.getFieldType(class4.getClass(), fieldSomeA); // typeSomeA = Integer

        Field fieldSomeE = class4.getClass().getField("someE");
        Class<?> typeSomeE = TypeUtils.getFieldType(class4.getClass(), fieldSomeE); // typeSomeE = Boolean


    }

    /*** UTILS ***/

    public static Class<?> getTypeVariableType(Class<?> subClass, TypeVariable<?> typeVariable) {
        Map<TypeVariable<?>, Type> subMap = new HashMap<>();
        Class<?> superClass;
        while ((superClass = subClass.getSuperclass()) != null) {

            Map<TypeVariable<?>, Type> superMap = new HashMap<>();
            Type superGeneric = subClass.getGenericSuperclass();
            if (superGeneric instanceof ParameterizedType) {

                TypeVariable<?>[] typeParams = superClass.getTypeParameters();
                Type[] actualTypeArgs = ((ParameterizedType) superGeneric).getActualTypeArguments();

                for (int i = 0; i < typeParams.length; i++) {
                    Type actualType = actualTypeArgs[i];
                    if (actualType instanceof TypeVariable) {
                        actualType = subMap.get(actualType);
                    }
                    if (typeVariable == typeParams[i]) return (Class<?>) actualType;
                    superMap.put(typeParams[i], actualType);
                }
            }
            subClass = superClass;
            subMap = superMap;
        }
        return null;
    }

    public static Class<?> getTypeParameterType(Class<?> subClass, Class<?> superClass, int typeParameterIndex) {
        return TypeUtils.getTypeVariableType(subClass, superClass.getTypeParameters()[typeParameterIndex]);
    }

    public static Class<?> getFieldType(Class<?> clazz, AccessibleObject element) {
        Class<?> type = null;
        Type genericType = null;

        if (element instanceof Field) {
            type = ((Field) element).getType();
            genericType = ((Field) element).getGenericType();
        } else if (element instanceof Method) {
            type = ((Method) element).getReturnType();
            genericType = ((Method) element).getGenericReturnType();
        }

        if (genericType instanceof TypeVariable) {
            Class<?> typeVariableType = TypeUtils.getTypeVariableType(clazz, (TypeVariable) genericType);
            if (typeVariableType != null) {
                type = typeVariableType;
            }
        }

        return type;
    }

}

-1

ฉันทำเช่นเดียวกับ @Moesio Above แต่ใน Kotlin สามารถทำได้ด้วยวิธีนี้:

class A<T : SomeClass>() {

    var someClassType : T

    init(){
    this.someClassType = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<T>
    }

}

-4

มันอาจจะเป็นประโยชน์กับใครบางคน คุณสามารถใช้ java.lang.ref.WeakReference; ทางนี้:

class SomeClass<N>{
  WeakReference<N> variableToGetTypeFrom;

  N getType(){
    return variableToGetTypeFrom.get();
  }
}

คลาสนี้ควรใช้อย่างไร ทำไมWeakReference? โปรดให้คำอธิบายกับคำตอบของคุณไม่ใช่แค่บางรหัส
Abhijit Sarkar

ดังนั้นถ้าคุณมีSomeClass<MyClass> คุณสามารถยกตัวอย่างSomeClassและเรียกอินสแตนซ์ที่และมีรันไทม์จะเป็นgetType MyClass
TheLetch

แน่นอนว่า แต่ทำไมWeakReference? สิ่งที่คุณพูดไม่แตกต่างจากคำตอบส่วนใหญ่
Abhijit Sarkar

ก่อนอื่นวิธีการของฉันจะสั้นกว่า (รหัสน้อยกว่า) อันดับที่สองการอ้างอิงที่อ่อนแอไม่ได้ป้องกันผู้อ้างอิงของพวกเขาจากการทำขั้นตอนสุดท้ายและเท่าที่ฉันรู้ว่ามันไม่ได้ใช้การสะท้อนดังนั้นมันจึงรวดเร็ว
TheLetch

นี้ไม่ได้รับประเภทของสิ่งที่นี้จะส่งกลับวัตถุชนิดนั้นซึ่ง FYI, คุณสามารถทำได้ด้วยตัวอักษรใด ๆ ของเสื้อคลุม (มีAtomicReference, List, Set)
Frontear

-5

ฉันพบว่านี่เป็นวิธีแก้ปัญหาที่เข้าใจง่ายและอธิบายได้ง่าย

public class GenericClass<T> {

    private Class classForT(T...t) {
        return t.getClass().getComponentType();
    }

    public static void main(String[] args) {
        GenericClass<String> g = new GenericClass<String>();

        System.out.println(g.classForT());
        System.out.println(String.class);
    }
}

(T...t)อธิบาย (นั่นเป็นสาเหตุที่รหัสนี้ใช้งานไม่ได้)
Christopher Schneider
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.