Страницы

Поиск по вопросам

вторник, 7 января 2020 г.

Простой код с массивом в обобщённом классе и непонятное ClassCastException

#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, Class cl) { 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; } }

Комментариев нет:

Отправить комментарий