Список генерирующих ролей в 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
}