Почему метод getInterfaces() java.lang.Class возвращает Class‹?›[] а не Class‹? супер Т›[]?

(Чтобы прояснить вопрос, «T» относится к параметру типа, объявленному в классе)

В качестве примера рассмотрим следующее приложение:

public class TestClass {

    interface InterfaceA{}

    interface InterfaceB{}

    interface InterfaceC{}

    class ClassA implements InterfaceA, InterfaceB,  InterfaceC{}


    public static void main(String[] args){    

        Class<? super ClassA> superClass1 = ClassA.class;
        Class<? super ClassA> superclass2 = InterfaceA.class;
        Class<? super ClassA> superclass3 = InterfaceB.class;
        Class<? super ClassA> superclass4 = InterfaceC.class;

        for(Class<?> clazz : ClassA.class.getInterfaces()){
            System.out.println(clazz.getName());
        }   
    }
}

Вывод такой, какой и следовало ожидать:

TestClass$InterfaceA
TestClass$InterfaceB
TestClass$InterfaceC

Итак, основываясь на приведенном выше примере, мы видим, что компилятор распознает, что InterfaceA действительно соответствует границам wildcard , как и все остальные интерфейсы.

Интуитивно я ожидал бы, что следующее будет безопасным, но это не так:

Class<? super ClassA>[] interfaces = ClassA.class.getInterfaces();

Компилятор выдает предупреждение, потому что в сигнатуре сказано, что он возвращает Class; однако в javadoc для класса указано следующее:

Если этот объект представляет класс, возвращаемое значение представляет собой массив, содержащий объекты, представляющие все интерфейсы, реализованные классом.

«Этот объект» в нашем случае относится к ClassA. На основании этого утверждения, если мы позвоним:

ClassA.class.getInterfaces()

тогда мы логически знаем, что каждый Class<?> в возвращаемом массиве будет содержать ссылку на супертип ClassA.

В качестве дополнительной точки отсчета:

Из справочника по языку Java, третье издание:

Класс обязательно реализует все интерфейсы, которые реализуют его непосредственные суперклассы и непосредственные суперинтерфейсы. Это (множественное) наследование интерфейса позволяет объектам поддерживать (множественное) общее поведение без совместного использования какой-либо реализации.

Читая эту и другие части спецификации, я думаю, что любой класс или интерфейс, реализованный или расширенный классом T, попадет в границы <? super T>.

ОТРЕДАКТИРОВАНО:

Было высказано предположение, что нет конкретной причины для моего вопроса. Я приведу пример:

1    import java.util.Map;
2    import java.util.HashMap;
3    
4    public class MapWrapper<T> {
5        private final Map<Class<?>, OtherClass<T,?>> map = new HashMap <Class<?>, OtherClass<T,?>>();
6    
7        public <W> void addToMap(Class<W> type, OtherClass<T,? super W> otherClass){
8            map.put(type, otherClass);
9        }
10    
11        public <W> OtherClass<T,? super W> getOtherClassForAnyInterface(Class<W> type){
12            if(type.getInterfaces()!=null){
13                for(Class<?> interfaceType : type.getInterfaces()){
14                    // Here, a cast is necessary.  It throws a compiler warning for unchecked operations (rightfully so)
15                    OtherClass<T,? super W> otherClass = (OtherClass<T,? super W>)getOtherClassForInterface(interfaceType);
16                    if(null!=otherClass){
17                        return otherClass;
18                    }
19                }
20            }
21            return null;
22        }
23    
24        
25        public class OtherClass<T,V> {}
26    
27    }

Проблема в строке 15. Вы должны привести к <? super W>, чтобы получить правильный тип. Предупреждение компилятора можно подавить, но оправдано ли это? Есть ли какой-то сценарий, в котором этот состав не соответствует действительности?


person sager    schedule 18.11.2011    source источник


Ответы (2)


Вы правы, тип возвращаемого значения может быть более конкретным.

Однако Class<? super X> в любом случае не очень полезен. Class<?> достаточно для большинства применений.

Если у нас есть объявление G<T>, чтобы G<? super X> было полезно, обычно G должны иметь методы, которые принимают T. Например, у List<T> есть add(T). Итак, List<? super X> полезен, мы можем вызвать для него add(x) (x имеет тип X)

Class не имеет таких методов.

У вас есть убедительный вариант использования, который вам действительно нужен Class<? super ClassA>?

person irreputable    schedule 18.11.2011
comment
Класс‹T› может не иметь таких методов, которые на самом деле используют какой-либо суперкласс T, но, представляя сам тип, он претендует на предоставление информации о супертипах. Если вы посмотрите на метод класса getSuperclass(), он возвращает тип Class<? super T>. В этом случае мне кажется даже более подходящим, чтобы getInterfaces() возвращал аналогичный мне результат. Если бы они думали, как вы предполагаете, что Class никогда не будет использовать какие-либо из этих методов, доступных, разрешив ограниченный подстановочный знак, зачем Class предоставлять суперкласс, указанный как <? super T>? - person sager; 21.11.2011
comment
Вероятно, это ошибка дизайнера API. Я утверждаю, что это все равно не имеет значения. - person irreputable; 22.11.2011
comment
Я добавил конкретный случай использования Class таким образом. Он сейчас в самом квесте. - person sager; 22.11.2011

Опять же, предположим, что у вас есть это объявление универсального метода:

Пример без массива

<T super Integer> void add(T number) // hypothetical! currently illegal in Java

И у вас есть эти объявления переменных:

Integer anInteger Number aNumber Object anObject String aString

Ваше намерение с <T super Integer> (если это законно) состоит в том, чтобы разрешить add(anInteger), add(aNumber) и, конечно, add(anObject), но НЕ add(aString). Что ж, String — это объект, поэтому add(aString) все равно будет компилироваться.

скопировать из: Переполнение стека - Ограничение дженериков ключевым словом "супер"

Мне это помогло. Может поможет и вам :)

person Nickolas Evans    schedule 29.01.2018