Список генерирующих ролей в Java

Я обнаружил странную ситуацию при создании родословных. Я запустил этот код:

class A { }

class B { }

public class Program {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        List<A> listA = new ArrayList<A>();
        List<?> list = listA;
        ((List<B>)list).add(new B());

        for (Object item : listA) {
            System.out.println(item.toString());
        }
    }
}

Он очень хорошо компилируется (только с предупреждением, но без ошибок) и запускается без каких-либо исключений, а выход:

B @88140ed

Как я это сделал? Я имею в виду, почему Java разрешает мне это делать? Я добавил экземпляр класса B в список A s?

Это очень плохое поведение дженериков. Почему это происходит?

Кстати, я попробовал это с Java 7.

EDIT:
Меня удивило то, что Java только уведомляет проблему с предупреждением о том, что каждый программист может ее игнорировать. Я знаю, что SuppressWarnings - плохая идея, но почему Java не отрицала такого поведения с ошибкой или исключением?

Кроме того, это предупреждение всегда показывалось, если вы считаете, что ваше кастинг правильный, у вас нет выбора, кроме как игнорировать его. Но если вы думаете, что это хорошее кастинг и игнорировать его, но это не так?

Ответы

Ответ 1

Каждый язык программирования позволяет снимать себя в ногу.

В этом случае Java находится в дилемме: он может хранить информацию генериков в байт-коде и разрывать миллионы строк существующего кода или молча отбрасывать информацию о генериках после того, как компилятор сделает все возможное, чтобы проверить и сохранить обратную совместимость.

Команда Java решила для последнего и представила Type Erasure - которая имеет свои недостатки. Но если бы они уничтожили миллионы совершенно прекрасных (если не законченных) строк кода Java, люди бы увидели вилы и горящие факелы...

Ответ 2

Вы отменили проверку времени компиляции Java в результате кастинга и подавления предупреждений.

Обратите внимание, что благодаря типу-стиранию созданный вами список (под обложками) представляет собой простой список типов, не имеющий значения, и содержит нет проверок времени или утверждений относительно того, что вы вкладываете в него.

Ответ 3

Это очень плохое поведение дженериков. Почему это происходит?

Потому что вы заставили это произойти с актом, а затем убедились, что предупреждения будут проигнорированы также с вашим @SuppressWarnings("unchecked").

Это ваша вина, а не механизм generics.

Ответ 4

Как говорили другие, вы обошли безопасность типа java.

Я добавлю, что ваш код не взрывается, потому что ваш код не требует, чтобы элементы были чем-то конкретным (просто Object). Однако, если бы вы закодировали это:

for (A item : listA) { /* whatever */ }

Он должен был скомпилироваться, но во время выполнения был бы исключен ClassCastException.

Ответ 5

Вы можете изменить код для более безопасного:

for (A item : listA) {/* your code here */}

Или

for (Object item : listA) {
                if (item instanceof A) {for (A item : listA) {/* your code here*/}
}

Даже если вы измените код, приведенный ниже, вы получите ClassCastException:

for (Object item : listA) {
   System.out.println(((A)item).toString()); // Here you will get ClassCastException
}