Подтвердить что ты не робот

Непредвиденное нарушение безопасности типа

В следующем коде dowcast для явно несовместимого типа проходит компиляцию:

public class Item {
  List<Item> items() { return asList(new Item()); }
  Item m = (Item) items();
}

Item и List<Item> являются разрозненными типами, поэтому приведение не может быть успешным. Почему компилятор разрешил это?

4b9b3361

Ответ 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, или возникает ошибка времени компиляции.