Автор оригинала: Cristian Stancalau.
1. Обзор
В этом коротком уроке мы сосредоточимся на ClassCastException , общем исключении Java .
ClassCastException – это непроверенное исключение , которое сигнализирует о том, что код попытался привести ссылку на тип, к которому он не является подтипом .
Давайте рассмотрим некоторые сценарии, которые приводят к возникновению этого исключения, и как мы можем их избежать.
2. Явный Кастинг
Для наших следующих экспериментов рассмотрим следующие классы:
public interface Animal { String getName(); }
public class Mammal implements Animal { @Override public String getName() { return "Mammal"; } }
public class Amphibian implements Animal { @Override public String getName() { return "Amphibian"; } }
public class Frog extends Amphibian { @Override public String getName() { return super.getName() + ": Frog"; } }
2.1. Классы литья
Безусловно, наиболее распространенный сценарий встречи с ClassCastException явно приводит к несовместимому типу.
Например, давайте попробуем бросить Лягушку в Млекопитающее :
Frog frog = new Frog(); Mammal mammal = (Mammal) frog;
Мы могли бы ожидать здесь ClassCastException , но на самом деле мы получаем ошибку компиляции: “несовместимые типы: Лягушка не может быть преобразована в млекопитающее”. Однако ситуация меняется, когда мы используем общий супертип:
Animal animal = new Frog(); Mammal mammal = (Mammal) animal;
Теперь мы получаем ClassCastException из второй строки:
Exception in thread "main" java.lang.ClassCastException: class Frog cannot be cast to class Mammal (Frog and Mammal are in unnamed module of loader 'app') at Main.main(Main.java:9)
Проверенное значение Mammal несовместимо со ссылкой Frog , поскольку Frog не является подтипом Mammal . В этом случае компилятор не может нам помочь, так как переменная Animal может содержать ссылку совместимого типа.
Интересно отметить, что ошибка компиляции возникает только тогда, когда мы пытаемся привести к однозначно несовместимому классу. То же самое не относится к интерфейсам, поскольку Java поддерживает наследование нескольких интерфейсов, но только одно наследование для классов. Таким образом, компилятор не может определить, реализует ли ссылочный тип определенный интерфейс или нет. Давайте приведем пример:
Animal animal = new Frog(); Serializable serial = (Serializable) animal;
Мы получаем ClassCastException во второй строке вместо ошибки компиляции:
Exception in thread "main" java.lang.ClassCastException: class Frog cannot be cast to class java.io.Serializable (Frog is in unnamed module of loader 'app'; java.io.Serializable is in module java.base of loader 'bootstrap') at Main.main(Main.java:11)
2.2. Литейные массивы
Мы видели, как классы обрабатывают приведение, теперь давайте посмотрим на массивы. Приведение массива работает так же, как и приведение класса. Однако мы можем запутаться в автобоксах и продвижении типов или их отсутствии.
Таким образом, давайте посмотрим, что происходит с примитивными массивами, когда мы пытаемся выполнить следующее приведение:
Object primitives = new int[1]; Integer[] integers = (Integer[]) primitives;
Вторая строка вызывает исключение ClassCastException as autoboxing не работает для массивов.
Как насчет продвижения по службе? Давайте попробуем следующее:
Object primitives = new int[1]; long[] longs = (long[]) primitives;
Мы также получаем исключение ClassCastException , потому что продвижение типа не работает для целых массивов.
2.3. Безопасное литье
В случае явного приведения настоятельно рекомендуется проверить совместимость типов перед попыткой приведения с помощью instanceof .
Давайте рассмотрим пример безопасного приведения:
Mammal mammal; if (animal instanceof Mammal) { mammal = (Mammal) animal; } else { // handle exceptional case }
3. Загрязнение Кучи
В соответствии со спецификацией Java : ” Загрязнение кучи может произойти только в том случае, если программа выполнила какую-либо операцию с использованием необработанного типа, которая приведет к появлению непроверенного предупреждения во время компиляции”.
Для нашего эксперимента рассмотрим следующий универсальный класс:
public static class Box{ private T content; public T getContent() { return content; } public void setContent(T content) { this.content = content; } }
Теперь мы попытаемся загрязнить кучу следующим образом:
BoxoriginalBox = new Box<>(); Box raw = originalBox; raw.setContent(2.5); Box bound = (Box ) raw; Long content = bound.getContent();
Последняя строка вызовет исключение ClassCastException , поскольку она не может преобразовать ссылку D ouble в Long .
4. Общие типы
При использовании дженериков в Java мы должны быть осторожны с стиранием типов , что также может привести к ClassCastException в некоторых условиях.
Давайте рассмотрим следующий общий метод:
public staticT convertInstanceOfObject(Object o) { try { return (T) o; } catch (ClassCastException e) { return null; } }
А теперь давайте назовем это:
String shouldBeNull = convertInstanceOfObject(123);
На первый взгляд, мы можем разумно ожидать нулевую ссылку, возвращаемую из блока catch. Однако во время выполнения из-за стирания типа параметр приводится к Object вместо String . Таким образом , компилятор сталкивается с задачей присвоения целого числа | строке , которая вызывает исключение ClassCastException.
5. Заключение
В этой статье мы рассмотрели ряд распространенных сценариев неподходящего кастинга.
Независимо от того, явные или неявные, приведение ссылок Java к другому типу может привести к ClassCastException , если целевой тип не совпадает или не является потомком фактического типа .
Код, используемый в этой статье, можно найти на GitHub .