Приведение ссылочных типов в Java - Вопросы
Всего: 6 вопросов
1. Что такое upcasting и downcasting в Java и чем они отличаются?
Что такое upcasting и downcasting в Java и чем они отличаются?
Upcasting (расширение, восходящее преобразование) - приведение ссылки от наследника к суперклассу или интерфейсу. Выполняется неявно и всегда безопасно:
Box6 box = new HeavyBox1(); // upcasting
Downcasting (сужение, нисходящее преобразование) - приведение от суперкласса или интерфейса к наследнику. Требует явного оператора (Тип) и может завершиться ClassCastException:
HeavyBox1 heavy = (HeavyBox1) box; // downcasting
Сам объект при приведении не меняется - меняется только тип ссылки, а значит, набор доступных полей и методов. После upcasting через ссылку Box6 видны только члены Box6, но переопределённые методы всё равно вызываются по реальному классу объекта (полиморфизм). Downcasting нужен, чтобы снова получить доступ к членам наследника, например к полю weight.
2. Когда возникает ClassCastException и почему компилятор не может его предотвратить?
Когда возникает 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 и какие у него особенности?
Что проверяет оператор 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+ и как он заменяет явное приведение?
Что такое 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-класса - нет?
Почему приведение к интерфейсу компилируется, даже если класс его не реализует, а для 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?
Какие преобразования типов массивов разрешены в 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> - ошибка ловится уже при компиляции.