簡體   English   中英

如何安全地轉換通用通配符“?” 到 Java 中的已知類型參數?

[英]How to safely cast generic wildcard "?" to known type parameter in Java?

如何安全地將Class<?> (由Class.forName()返回)轉換為Class<Annotation>而不發出“Unchecked cast”警告?

    private static Class<? extends Annotation> getAnnotation() throws ClassNotFoundException {
        final Class<?> loadedClass = Class.forName("java.lang.annotation.Retention");
        if (!Annotation.class.isAssignableFrom(loadedClass)) {
            throw new IllegalStateException("@Retention is expected to be an annotation.");
        }
        @SuppressWarnings("unchecked")
        final Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) loadedClass;
        return annotationClass;
    }

在深入研究答案之前,需要解釋多個誤解。

您使用了錯誤的方差

final Class<Annotation> annotationClass = (Class<Annotation>) loadedClass;

這在任何情況下實際上都是非法的。 試試看:

Class<Number> n = Integer.class;

那不會編譯。

Generics 是不變的。 這意味着在<>中,您不能使用超類型作為子類型的替代品,反之亦然。

正常 java (當<>不涉及時)是協變的。 任何子類型都是其超類型之一的替代品。 這個:

Number n = Integer.valueOf(5);

是完全合法的 java。 但在 generics 世界中它不是。 如果你想要它,那么你必須選擇它: X extends Y是你選擇協方差的方式,而X super Y是你選擇逆變的方式(逆變好像Integer i = new Number();是合法 - 超級類型可以代表子類型)。

這都是因為這就是宇宙最終的運作方式。 如果 generics 是自然協變的,這將編譯:

List<Integer> listOfInts = new ArrayList<>();
List<Number> listOfNums = listOfInts;
listOfNums.add(Double.valueOf(1.0));
int i = listOfInts.get(0);

但是,跟隨你自己的眼睛,你就會意識到代碼是一種步行類型違規。 它將非整數推入整數列表。 這就是為什么選擇協變或逆變關門的原因。 如果您選擇協方差,則 add 方法將被禁用*1:

List<? extends Number> list = new ArrayList<Integer>(); //legal
list.add(Integer.valueOf(5)); // will not compile

同樣,如果您選擇逆變, add 效果很好,但get被禁用 類型系統意義上的“禁用”:您可以調用它。 但表達式list.get(i)的類型為 Object:

List<? super Integer> list = new ArrayList<Number>(); // legal
list.add(Integer.valueOf(5)); // legal
Integer i = list.get(0); // won't compile
Object o = list.get(0); // this will.

對於“寫”不完全清楚的類,很難理解為什么Class<Annotation> c = SomeSpecificAnno.class; 應該無法編譯,但確實如此,這是重要的實現之一。

你為什么在這里使用反射?

您可以在 java 中制作 class 文字。 這很好用:

Class<? extends Number> c = Integer.class;

That's real java: You can stick .class at the end of any type and that will be an expression of type java.lang.Class , in fact, it's of type Class<TheExactThing> . 所以:

private static Class<? extends Annotation> getAnnotationType() {
    return Retention.class;
}

工作和編譯非常好。 我必須更新返回類型,因為如上所述,返回jlClass的實例,該實例表示指定返回Class<Annotation>的方法的Retention注釋與從指定返回的方法返回 integer 一樣損壞一個字符串。

答案

如果您的代碼示例使用java.lang.annotation.Retention作為替代,但這里的實際字符串是您在編譯時不知道的動態值,則return Retention.class; 選項不在討論范圍內,然后:

private static Class<? extends Annotation> getAnnotationType(String fqn) throws ClassNotFoundException {
    return Class.forName(fqn).asSubclass(Annotation.class);
}

同樣,除非沒有其他方法,否則不要使用反射,如果字符串常量中有 class,通常不需要反射。

*1 ) 您可以調用 add,但只能使用 null 文字; list.add(null); 編譯,因為 null 是任何類型的普通有效值。 但是,這當然不是特別有用。

由於類型擦除,泛型類型信息在運行時不再可訪問。 這意味着: Class<?>Class<? extends Annotation> Class<? extends Annotation>在運行時無法區分-因此您不能進行運行時檢查以使未檢查的強制轉換為已檢查的。

這意味着:你必須忍受警告(注意:警告不是錯誤,它只是意味着“這是有問題的,確保你知道你在做什么。)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM