#java #исключения #generics
class Super {} class Sub extends Super {} class GenericArrayHolder{ T[] array; @SuppressWarnings("unchecked") GenericArrayHolder(int n) { array = (T[]) new Super[n]; } void set(int i, T t) { array[i] = t; } } public class Test { public static void main(String... args) { GenericArrayHolder h = new GenericArrayHolder<>(10); h.set(3, new Sub()); h.array[3] = new Sub(); // ClassCastException } } Собственно вопрос в том, почему 3 строчка в main генерирует исключение? Особенно меня удивляет, что это происходит не смотря на то, что 2 строчка работает нормально.
Ответы
Ответ 1
Кажется, я понял. Дело таки в type erasure. Смотрите, вот эксперимент. Уберём new Sub(), запишем просто null. http://ideone.com/zXc7kG Получим ошибку: Exception in thread "main" java.lang.ClassCastException: [LSuper; cannot be cast to [LSub;. Документация говорит, что [LSuper; означает массив элементов типа Super, a [LSub; — массив элементов типа Sub. Как работает type erasure? На время компиляции T заменяется на Super, и всё, что выдаёт наружу T, обкладывается рантайм-проверками. То есть код h.array на самом деле превращается в (Sub[])h.array. В вашем случае array на самом деле типа Super[], каст из-за type erasure не обнаруживает, что тип-то не тот! Ошибка возникает лишь при доступе. Что делать? Создайте несущий массив правильного типа: array = (Т[])Array.newInstance(cl, 10); Для этого вам понадобится класс: GenericArrayHolder(int n, Classcl) { array = (Т[])Array.newInstance(cl, 10); } Более прямого пути с type erasure, кажется, нет. Ответ 2
Это привет от type erasure. В коде дженериков на самом деле хранится код для базового типа, и везде расставляются преобразования типов. В вашем случае код: array = (T[]) new Super[n]; на самом деле выполняется так: array = (Super[]) new Super[n]; потому что T в GenericArrayHolder ограничен "снизу" этим типом. Когда вы выполняете метод у дженерикового типа, то все касты корректно расставляются, в результате код: array[i] = t; выполняется так: array[i] = (Super)t; Когда же вы обращаетесь к массиву напрямую, то компилятор считает, что массив надо преобразовать к типу Sub[]. Сделать он этого не может, отсюда исключение. Похожий вопрос был рассмотрен на английском Stack Overflow: Generic array throws ClassCastException when referenced directly (it doesn't when calling through helper method). Там в похожем коде исключение выбрасывается даже при обращении к полю массива length.Ответ 3
А зачем вам массив типа T? Можно хранить массив Super, и тогда все отлично работает. class Super {} class Sub extends Super {} class GenericArrayHolder{ Super[] array; GenericArrayHolder(int n) { array = new Super[n]; } void set(int i, T t) { array[i] = t; } } class Test { public static void main(String[] args) { GenericArrayHolder h = new GenericArrayHolder<>(10); h.set(3, new Sub()); h.array[3] = null; } }
Комментариев нет:
Отправить комментарий