Непредвиденное нарушение безопасности типа
В следующем коде dowcast для явно несовместимого типа проходит компиляцию:
public class Item {
List<Item> items() { return asList(new Item()); }
Item m = (Item) items();
}
Item
и List<Item>
являются разрозненными типами, поэтому приведение не может быть успешным. Почему компилятор разрешил это?
Ответы
Ответ 1
A List<Item>
вполне может быть Item. См. Например:
public class Foo extends Item implements List<Item> {
// implement required methods
}
Приведение говорит компилятору: "Я знаю, что вы не можете быть уверены, что это объект типа Item, но я знаю лучше вас, поэтому, пожалуйста, скомпилируйте". Компилятор откажется от компиляции, если невозможно, чтобы возвращаемый объект был экземпляром Item (например, Integer
не может быть String
)
Во время выполнения проверяется тип фактического объекта, возвращаемого методом, и если он не является объектом объекта типа Item, вы получите исключение ClassCastException.
Ответ 2
Соответствующая запись спецификации можно найти здесь. Пусть S - источник, а T - цель; в этом случае источником является интерфейс, а цель - не конечный тип.
Если S - тип интерфейса:
-
Если T - тип массива, то S должен быть типом java.io.Serializable
или Cloneable
(единственными интерфейсами, реализованными массивами), или ошибка времени компиляции.
-
Если T - тип, который не является окончательным (§8.1.1), то, если существует супертип X из T и супертип Y из S, такой, что оба X и Y являются доказуемо отличающиеся параметризованные типы, и что стирания X и Y одинаковы, возникает ошибка времени компиляции.
В противном случае листинг всегда является законным во время компиляции (потому что даже если T не реализует S, подкласс T может).
Чтобы получить это прямо, потребовалось несколько чтений, но пусть начнется сверху.
- Цель не является массивом, поэтому это правило не применяется.
- Наша цель не связана с параметризованным типом, поэтому это правило не будет применяться.
- Это означает, что бросок всегда будет законным во время компиляции по причине, проиллюстрированной JB Nizet: наш целевой класс может не реализовать источник, но может быть подклассом.
Это также означает, что это не сработает, если мы перейдем к конечному классу, который не реализует интерфейс в этом фрагменте:
Если S не является параметризованным типом или необработанным типом, тогда T должен реализовать S, или возникает ошибка времени компиляции.