[java 1.6] Generic конструктор.

danilov

Странная штука. Хочу создать конструктор по листу с произвольным доступом, пишу:


private final List<T> entry;
...

public <V extends List<T> & RandomAccess> RingList(@NotNull V underlayingList) {
entry = underlayingList;
}

...

Так она отлично хавает во-первых LinkedList (не RandomAccess а во-вторых TreeList (не generic).
И притом нормально работает.
Более того, если RandomAccess заменить на непустой интерфейс, она нормально работает, если у underlayingList не вызывать методов этого интерфейса.
Иначе вылазит ClassCastException.
Но компилятор всё это отлично хавает.
idea на проверку if(!(underlayingList instanceof RandomAccess выдаёт, что значение по if'ом всегда false.
Этому есть объяснение какое-то или просто багло?
Если заменить на фабрику, то всё становится правильно (компилятор ругается, где надо).

v_Alex

А если так?

public <V extends RandomAccess & List<T> > RingList(@NotNull V underlayingList) {
entry = underlayingList;
}

v_Alex

а во-вторых TreeList (не generic).
TreeList который из commons-collections? он экстендит AbstractList

danilov

Да, но экстендит его без параметризации элементов.

v_Alex

Это нормально

danilov

А так работает...
Ваще не понимаю.

danilov

Не, всё правильно TreeList не generic.
И твой вариант конструктора его резонно не пускает.

v_Alex

а,точно, ты прав
по ходу действительно какой-то баг :confused:
ждем гуру явы :p

bleyman

Может, это из-за type erasure?

v_Alex

Вполне резонно :)) (Если не использовать raw types, все нормально)
Вот только не очень понятно, как порядок объявления в списке extends может влиять на результат компиляции

Svyatogor

По определению в raw-type (и в коде) для этого типа будет только erasure первого типа в списке ограничения extends. JLS 3rd, 4.8 и 4.6. Этот тип будет у всех полей, аргументов методов, возвращаемых значений и т.п. В исходном варианте raw-конструктор будет иметь тип

public RingList(List underlayingList)

а после изменения порядка перечисления границ raw-конструктор станет

public RingList(RandomAccess underlayingList)

freezer

а что, в метаданных все эти списки ограничений не хранятся и компилятором не проверяются?

bleyman

О господи. И люди на этом что-то программируют...
Может, я чего-то не понимаю, обесните. Как это вообще так? Вот дотнетовский компайлер — тупо сохраняет все произведённые в компайл тайм инстанциации генериков при помощи символа "`" и перечисления параметров после него, ещё сохраняет немножко метаданных про сам темплейт (которыми всё равно никто никогда не пользуется) и всё прекрасно, участие со стороны CLR не требуется.
Как вообще могло получиться то, что получилось в жаве? Один знакомый сказал, что это от того, что не хотели менять JVM ещё раз, но бля, я не понимаю, можно же было её и не менять! Сейчас-то полная порнография получается, я раньше думал, что они просто стирают тайп информейшен о генерик параметрах, проверяют, что не случается оверлоадинг конфликтов и всё. А тут вдруг выясняется, что есть ещё какие-то правила. Зачем?

bleyman

18.03.2008 2:14 ICQ: а
18.03.2008 2:14 ICQ: хаххаа
18.03.2008 2:14 ICQ: я понял, что он сделал
18.03.2008 2:14 ICQ: короче, new Foo<Long>(new ArrayList<Long> работает
18.03.2008 2:14 ICQ: new Foo<Long>(new LinkedList<Long> — ошибка компиляции
18.03.2008 2:14 ICQ: поскольку LinkedList не RandomAccess
18.03.2008 2:15 ICQ: но этот чувак, походу, написал new Foo(new LinkedList<Long>
18.03.2008 2:15 ICQ: то есть, просто взял сырую версию класса
18.03.2008 2:15 ICQ: без всяких генериков
18.03.2008 2:15 ICQ: и поэтому там нет вообще никаких проверок
18.03.2008 2:15 Fj: а
18.03.2008 2:15 ICQ: я уже на такое натыкался один раз, это действительно заеб ихней реализации
18.03.2008 2:15 ICQ: но это не имеет отношения к erasure
18.03.2008 2:15 Fj: пиздец. прикольно
18.03.2008 2:16 ICQ: грубо говоря, если ты взял generic-вызов и вызвал его без генериков, от него отрезаются вообще все генерики
18.03.2008 2:16 ICQ: а не только явно прописанные в вызове
18.03.2008 2:16 Fj: так это, а что с порядком перечисления интерфейсов?
18.03.2008 2:16 Fj: это правда, что от него что-то зависит?
18.03.2008 2:16 ICQ: да
18.03.2008 2:17 ICQ: если ты через рефлекшн запросишь этот метод, он будет после erasure уже
18.03.2008 2:17 Fj: жопа
18.03.2008 2:17 ICQ: поэтому будет выглядеть как new Foo(new LinkedList)
18.03.2008 2:17 ICQ: но при этом там можно достать все ограничения, на самом деле
18.03.2008 2:17 ICQ: которые в декларации прописаны
18.03.2008 2:17 ICQ: а не при generic instantiation
18.03.2008 2:17 Fj: можно, я кусок лога туда запощу, потому что там, кажется, не было такого написано?
18.03.2008 2:18 ICQ: ща
18.03.2008 2:18 ICQ: я найду параграф в jls
18.03.2008 2:20 ICQ: хотя там в общем дали ссылку на него
18.03.2008 2:20 ICQ: 4.8 raw types

Svyatogor

Ограничения честно хранятся в байт-коде. Но метаданные проверяются только в случае, если тип/метод/поле используются соответствующим образом (т.е. как параметризованный вызов, а не raw-тип). Это позволяет сохранять совместимость на уровне кода со старыми библиотеками и приложениями и позволяет переводить на Generic приложения частями, а не одним титаническим усилием по всему приложению.
Скорее всего, в случае автора все разрешение подходящих конструкторов (методов) свелось к raw-типам. Либо он не указал параметр типа при создании объекта (т.е. использовал new RingList вместо new RingList<String>. Либо использовал raw-тип в аргументах (тот же TreeList или LinkedList без указания аргументов, что привело к выбору конструктора (метода) как raw). Если указывать необходимые параметры-типы, то компилятор выдаст ошибку. Кроме того, если не указывать эти типы, то компилятор (sun javac) выдает предупреждение вроде "Note: T.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details". Ну и по спецификации (тот же раздел 4.8): "It is possible that future versions of the Java programming language will disallow the use of raw type".

freezer

"It is possible that future versions of the Java programming language will disallow the use of raw type".
ага, ну понятно. Просто в .NET этими raw-типами можно пользоваться только для инстанциирования

Svyatogor

Был незаслуженно забыт Heap Pollution :). Это гораздо более страшная штука.
Джава-компилятор тоже сохраняет параметры и ограничения на типы. Но не плодит на каждую инстанциацию Generic'а отдельный класс. Что, между прочим, позволяет вынести Generic в библиотеку и ее исходный код не потребуется для компиляции. И, кроме того, позволит заменить библиотеку на новую версию совершенно не требуя перекомпиляции всего остального кода. На уровень runtime такие проверки пока не выносили, иначе информация о generic/nongeneric параметрах станет частью типа объекта. Такие проверки на самом деле - хорошо, но не понятно, что делать со всей инфраструктурой, наработанной до версии 1.4 включительно.
Итого мы сейчас имеем то, что имеем. На уровне исходного кода Generics дают некоторую безопасность типов (те же дополнительные проверки, что в список строк не может быть положено число). На уровне байт-кода - дополнительную метаинформацию, которая в дальнейшем используется компилятором и, возможно, при верификации байт-кода (не уверен, если интересно, могу поднять спецификацию JVM и посмотреть, изменился ли процесс верификации). Следующим шагом может быть введение полноценной поддержки о Generic-типа во время выполнения (что может таки сломать совместимость с библиотеками, которые не были generified). Ну а писать на этом не слишком сложно. На этапе разработки компилятор умеет выдавать предупреждения, среды разработки также настраиваются на предупреждение использования raw types, которые уже deprecated. Итого получаются достаточно полноценные (без raw types - абсолютно полноценные) проверки на этапе компиляции. Для runtime иногда возникают сложности, например, когда нужно получить значение параметра-типа. Приходится явно передавать соответствующий объект Class<?>. Но это пока (пока на них не забили) проблемы переходного периода.

Dasar

Но не плодит на каждую инстанциацию Generic'а отдельный класс. Что, между прочим, позволяет вынести Generic в библиотеку и ее исходный код не потребуется для компиляции. И, кроме того, позволит заменить библиотеку на новую версию совершенно не требуя перекомпиляции всего остального кода.
а что возможно какое-то другое поведение?
зы
на всякий случай: generic != template

danilov

Круто, спасибо. И всем ответившим тож. Тогда второй вопрос:
Почему жава умеет делать подстановку generic'ов в обычные методы, и не умеет в конструкторах?
Вызов Collections.emptyMap(new HashMap<T, V> выдаст Map<T, V>,
а new HashMap(new HashMap<T, V> - просто Map...

Svyatogor

Почему не умеет? Прекрасно умеет, но только если конструктор - generic (не путать с generic-типом, которому принадлежит конструктор). При этом полную сигнатуру типа нужно указывать всегда (иначе это будет raw-type, которые не рекомендуется использовать).

class NonGenClassGenCons {
<T> NonGenClassGenCons(T arg) {
}
}

class GenClassNonGenCons<T> {
GenClassNonGenCons(T arg) {
}
}

class GenClassAndCons<T> {
<V> GenClassAndCons(T t, V v) {
}
}

public class Test {
public static void main(String [] args) {
final NonGenClassGenCons e1 = new NonGenClassGenCons("Test");
final GenClassNonGenCons<String> e2 = new GenClassNonGenCons<String>("test");
final GenClassAndCons<String> e3 = new GenClassAndCons<String>("test", "Test");
final GenClassAndCons<String> e4 = new <Object>GenClassAndCons<String>("test", "Test");
}
}

e3 и e4 по сути одно и то же, только e4 записана полностью, а для e3 тип V вычисляется компилятором.
Оставить комментарий
Имя или ник:
Комментарий: