Общий тип getConstructors() в классе

В родовом классе Class<T> метод getConstructors() имеет тип возврата с неизвестным общим вместо T. Причиной этого является объяснение в javadoc.

Обратите внимание: хотя этот метод возвращает массив объектов Constructor<T> (это массив конструкторов из этого класса), возвращаемый тип этого метода Constructor<?>[], а не Constructor<T>[], как и следовало ожидать. Этот менее информативный тип возвращаемого значения необходим, поскольку после его возврата из этого метода массив может быть изменен для хранения объектов Constructor для разных классов, что нарушит гарантии типа Constructor<T>[].

Мой коллега и я попытались понять это объяснение. В нашем понимании они в основном говорят о том, что он имеет неизвестный общий тип, потому что некоторые вызывающие могут помещать в этот массив другие объекты Constructor. Мы получили это право? И если да, то зачем кому-то проектировать API таким образом. Не лучше ли использовать конкретный тип и доверять программисту правильно использовать массив? Для нас это звучит немного как "Мы ​​делаем худший API, потому что программист, использующий его, может попробовать что-то глупое". Где лежит наша ошибка?

Ответ 1

Точка, о которой упоминал Ашу Пачаури в комментарии (а именно, что массив возвращается для обратной совместимости), безусловно, действителен. И вообще, массивы и дженерики не очень хорошо сочетаются. (Для получения доказательств найдите все вопросы, связанные с stackoverflow, связанные с "Generic Arrays" ...)

Кроме того, существует правило, что API должен быть прост в использовании и трудно неправильно использовать. В этом случае это связано с Принцип наименьшего удивления: кто-то, получивший конструкторы с помощью этого метода, мог бы выполнить совершенно законную последовательность операций на возвращенный массив и, в конце концов, получить неожиданный ClassCastException. Таким образом, можно сказать, что тот факт, что массив Constructor<?>[] возвращен, направлен на "неудачное" поведение.

Иллюстративный пример:

import java.lang.reflect.Constructor;

public class GetConstructorsReturnType
{
    public static void main(String[] args) throws Exception
    {
        // This causes a warning, due to the cast, but imagine
        // this was possible
        Constructor<DerivedA> constructorsA[] =
            (Constructor<DerivedA>[])DerivedA.class.getConstructors();

        // The following lines are valid due to the subtype
        // relationship, but would not be valid if constructorsA
        // was declared as "Constructor<?>"
        Constructor<? extends Base> constructors[] = constructorsA;
        constructors[0] = DerivedB.class.getConstructor();

        // This causes a ClassCastException (and would also not
        // be possible constructorsA was declared as "Constructor<?>"
        DerivedA instance = constructorsA[0].newInstance();
    }
}
class Base
{
}
class DerivedA extends Base
{
    public DerivedA()
    {
    }
}
class DerivedB extends Base
{
    public DerivedB()
    {
    }
}

Ответ 2

Это та же самая причина, по которой вам не разрешено делать new Constructor<T>[], но вам разрешено делать new Constructor<?>[]. Вы можете применить тот же аргумент и сказать: "Разве не лучше использовать разрешить определенный тип и доверять программисту правильно использовать массив?" Ну, Java решила нет. (Вы можете представить, что внутри метода getConstrucotrs им нужно создать массив Constructor, и они не могут сделать new Constructor<T>[], но они могут сделать new Constructor<?>[].)

Конечно, вы можете сделать неконтролируемый отбор Constructor<?>[] на Constructor<T>[], но это даст вам предупреждение в вашем коде, и в этом случае вы возьмете на себя ответственность за обеспечение безопасности. Но если метод getConstructors это этот неконтролируемый отбор в их коде, вы, как вызывающий, никогда не будете предупреждены о неясности.