Приведение ссылочных типов в Java - Вопросы

Всего: 6 вопросов

1. 

Что такое upcasting и downcasting в Java и чем они отличаются?

Upcasting (расширение, восходящее преобразование) - приведение ссылки от наследника к суперклассу или интерфейсу. Выполняется неявно и всегда безопасно:

Box6 box = new HeavyBox1(); // upcasting

Downcasting (сужение, нисходящее преобразование) - приведение от суперкласса или интерфейса к наследнику. Требует явного оператора (Тип) и может завершиться ClassCastException:

HeavyBox1 heavy = (HeavyBox1) box; // downcasting

Сам объект при приведении не меняется - меняется только тип ссылки, а значит, набор доступных полей и методов. После upcasting через ссылку Box6 видны только члены Box6, но переопределённые методы всё равно вызываются по реальному классу объекта (полиморфизм). Downcasting нужен, чтобы снова получить доступ к членам наследника, например к полю weight.

2. 

Когда возникает ClassCastException и почему компилятор не может его предотвратить?

ClassCastException выбрасывается во время выполнения, когда при явном приведении реальный объект не является экземпляром целевого типа:

Box6 box = new ColorBox();
HeavyBox1 heavy = (HeavyBox1) box; // компилируется, но ClassCastException

Компилятор знает только объявленный тип переменной (Box6) и проверяет лишь, что приведение в принципе возможно: HeavyBox1 - наследник Box6. Какой объект окажется в переменной, известно только JVM во время выполнения. Если же типы заведомо несовместимы (например, (String) box), это ошибка компиляции.

Как избежать: проверять тип через instanceof перед сужением, в Java 16+ - использовать pattern matching (if (box instanceof HeavyBox1 h)), а ещё лучше - проектировать код так, чтобы нужное поведение было доступно через методы суперкласса или интерфейса.

3. 

Что проверяет оператор instanceof и какие у него особенности?

obj instanceof Type возвращает true, если ссылка не null и объект является экземпляром Type, его наследника или класса, реализующего интерфейс Type. Иначе говоря, true означает, что приведение (Type) obj пройдёт без ClassCastException.

  • Для null результат всегда false. При этом само приведение (HeavyBox1) null допустимо и возвращает null.
  • Левый операнд - только ссылочный тип; с примитивами (int x; x instanceof Integer) код не скомпилируется.
  • Если класс переменной и проверяемый класс заведомо не связаны наследованием, это ошибка компиляции, а не false.
  • Массив - тоже объект: new int[5] instanceof Object даёт true.
4. 

Что такое pattern matching для instanceof в Java 16+ и как он заменяет явное приведение?

Начиная с Java 16 (JEP 394), проверку типа и приведение можно объединить: после instanceof объявляется переменная, которая уже имеет нужный тип.

// до Java 16
if (box instanceof HeavyBox1) {
    HeavyBox1 heavy = (HeavyBox1) box;
    System.out.println(heavy.weight);
}

// Java 16+
if (box instanceof HeavyBox1 heavy && heavy.weight > 10) {
    System.out.println(heavy.weight);
}

Переменная шаблона видна только там, где проверка гарантированно прошла: в ветке if, справа от &&, а после if (!(obj instanceof T t)) return; - и в оставшейся части метода. Справа от || она недоступна - это ошибка компиляции. В Java 21 тот же подход работает в switch (pattern matching for switch): case HeavyBox1 h -> ....

5. 

Почему приведение к интерфейсу компилируется, даже если класс его не реализует, а для final-класса - нет?

Приведение между классами разрешено только внутри одной иерархии: (String) box для Box6 box - ошибка компиляции. С интерфейсами правило мягче. Если класс не final, у него может существовать наследник, реализующий интерфейс, поэтому компилятор разрешает приведение и откладывает проверку до выполнения:

Box6 box = new Box6();
Runnable r = (Runnable) box;       // компилируется, ClassCastException при выполнении
// Runnable r2 = (Runnable) "abc"; // ошибка компиляции

String - final-класс и не реализует Runnable. Наследников у него быть не может, значит, приведение заведомо невозможно, и компилятор сообщает об ошибке сразу.

6. 

Какие преобразования типов массивов разрешены в Java и что такое ArrayStoreException?

  • Запрещено: массив в примитив и обратно; int[] в Integer[] и обратно (автоупаковка на массивы не распространяется); int[] в long[], хотя int расширяется до long.
  • Разрешено: HeavyBox1[] в Box6[] неявно (upcasting); обратно - явным приведением, с проверкой во время выполнения (ClassCastException, если реальный массив - Box6[]).
  • Любой массив приводится к Object, Cloneable и Serializable.

Массивы в Java ковариантны, но помнят реальный тип элементов. Поэтому запись объекта неподходящего типа приводит к ArrayStoreException:

Box6[] box = new HeavyBox1[4];
box[0] = new Box6(); // ArrayStoreException

Дженерики устроены иначе: List<HeavyBox1> нельзя присвоить List<Box6> - ошибка ловится уже при компиляции.

Страница 1 из 1