Недавно я наткнулся на очень интересную оговорку API JDK, метод Class.getConstructors()
. Его сигнатура метода такова:
1
|
Constructor<?>[] getConstructors() |
Интересно, что Class.getConstructor(Class...)
возвращает Constructor<T>
с сохранением <T>
:
1
|
Constructor<T> getConstructor(Class<?>... parameterTypes) |
Почему есть разница, т.е. почему первый метод не возвращает Constructor<T>[]
?
Давайте рассмотрим Javadoc:
Обратите внимание, что хотя этот метод возвращает массив объектов Constructor <T> (это массив конструкторов из этого класса), тип возвращаемого значения этого метода — Constructor <?> [], А не Constructor <T> [], как могло бы быть ожидается. Этот менее информативный тип возвращаемого значения необходим, поскольку после возврата из этого метода массив можно изменить, чтобы он содержал объекты Constructor для разных классов, что нарушало бы гарантии типа Constructor <T> [].
Это сложный вопрос. Исторически, вот как это произошло:
Java 1.0 / Дуб: Массивы
В Java 1.0 (непосредственный преемник языка программирования Oak ) уже были представлены массивы. Фактически, они были представлены до API коллекций, который был представлен в Java 1.2. Массивы страдают от всех проблем, которые мы знаем сегодня, включая их ковариантность, которая приводит к множеству проблем во время выполнения, которые не могут быть проверены во время компиляции:
1
2
|
Object[] objects = new String[ 1 ]; objects[ 0 ] = Integer.valueOf( 1 ); // Ouch |
Java 1.1: API отражения
Если не Class.getConstructors()
«достойного» API коллекций, единственным возможным типом возврата метода Class.getConstructors()
был Constructor[]
. Разумное решение в то время. Конечно, вы можете сделать ту же ошибку, что и выше:
1
2
|
Object[] objects = String. class .getConstructors(); objects[ 0 ] = Integer.valueOf( 1 ); // Ouch |
но в дополнение к вышесказанному, вы также можете по праву написать это:
1
2
3
4
|
Constructor[] constructors = String. class .getConstructors(); constructors[ 0 ] = Object. class .getConstructor(); // Muahahahahahahaha |
Java 1.2: API коллекций
Java была обратно-совместимой с самых ранних дней, даже начиная с Oak. В этом вопросе о переполнении стека есть очень интересное историческое исследование о том, что некоторая обратная совместимость Oak просочилась в Java к этой дате .
Хотя было бы естественно спроектировать API отражения с использованием коллекций, сейчас уже слишком поздно. Лучшее решение могло бы быть:
1
|
List getConstructors() |
Однако обратите внимание, что у нас еще не было обобщений, поэтому массив на самом деле передает больше информации о типах, чем коллекция.
Java 1.5: Обобщения
В Java 5 изменение от
1
|
Constructor[] getConstructors() |
в
1
|
Constructor<?>[] getConstructors() |
был сделан по причинам, указанным выше. Теперь альтернативный API, использующий коллекцию, определенно был бы лучше:
1
|
List<Constructor<T>> getConstructors() |
Но корабль отплыл.
Ява, уродливая бородавка
Ява полна этих маленьких предостережений. Все они задокументированы в Javadocs и часто в переполнении стека. Буквально вчера мы задокументировали новое предупреждение, касающееся совершенно нового API в Map
и ConcurrentHashMap
.
«Ответственность: отрезвляющие части», очень хороший разговор обо всех этих предостережениях и о том, как тяжело их поддерживать, Брайан Гетц можно увидеть здесь:
Краткое содержание доклада:
Когда дизайнеры языка говорят о языке, который они проектируют
Ссылка: | Java Legacy постоянно растет благодаря нашему партнеру по JCG Лукасу Эдеру в блоге JAVA, SQL и JOOQ . |