- Объяснение ClassCastException в Java
- 1. Обзор
- 2. Явный Кастинг
- 2.1. Классы литья
- 2.2. Литейные массивы
- 2.3. Безопасное литье
- 3. Загрязнение Кучи
- 4. Общие типы
- 5. Заключение
- Читайте ещё по теме:
- How to Handle the Incompatible Types Error in Java
- Incompatible Types Error: What, Why & How?
- Incompatible Types Error Examples
- Explicit type casting
Объяснение ClassCastException в Java
Давайте подробнее рассмотрим исключение ClassCastException.
1. Обзор
В этом коротком уроке мы сосредоточимся на ClassCastException , общем исключении Java .
ClassCastException – это непроверенное исключение , которое сигнализирует о том, что код попытался привести ссылку на тип, к которому он не является подтипом .
Давайте рассмотрим некоторые сценарии, которые приводят к возникновению этого исключения, и как мы можем их избежать.
2. Явный Кастинг
Для наших следующих экспериментов рассмотрим следующие классы:
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 .
Читайте ещё по теме:
How to Handle the Incompatible Types Error in Java
Variables are memory containers used to store information. In Java, every variable has a data type and stores a value of that type. Data types, or types for short, are divided into two categories: primitive and non-primitive. There are eight primitive types in Java: byte , short , int , long , float , double , boolean and char . These built-in types describe variables that store single values of a predefined format and size. Non-primitive types, also known as reference types, hold references to objects stored somewhere in memory. The number of reference types is unlimited, as they are user-defined. A few reference types are already baked into the language and include String , as well as wrapper classes for all primitive types, like Integer for int and Boolean for boolean . All reference types are subclasses of java.lang.Object [1].
In programming, it is commonplace to convert certain data types to others in order to allow for the storing, processing, and exchanging of data between different modules, components, libraries, APIs, etc. Java is a statically typed language, and as such has certain rules and constraints in regard to working with types. While it is possible to convert to and from certain types with relative ease, like converting a char to an int and vice versa with type casting [2], it is not very straightforward to convert between other types, such as between certain primitive and reference types, like converting a String to an int , or one user-defined type to another. In fact, many of these cases would be indicative of a logical error and require careful consideration as to what is being converted and how, or whether the conversion is warranted in the first place. Aside from type casting, another common mechanism for performing type conversion is parsing [3], and Java has some predefined methods for performing this operation on built-in types.
double myDouble = 9; // Automatic casting (int to double) int myInt = (int) 9.87d; // Manual casting (double to int) boolean myBoolean = Boolean.parseBoolean("True"); // Parsing with a native method (String to boolean) System.out.println(myDouble); // 9.0 System.out.println(myInt); // 9 System.out.println(myBoolean); // true
Incompatible Types Error: What, Why & How?
The incompatible types error indicates a situation where there is some expression that yields a value of a certain data type different from the one expected. This error implies that the Java compiler is unable to resolve a value assigned to a variable or returned by a method, because its type is incompatible with the one declared on the variable or method in question. Incompatible, in this context, means that the source type is both different from and unconvertible (by means of automatic type casting) to the declared type.
This might seem like a syntax error, but it is a logical error discovered in the semantic phase of compilation. The error message generated by the compiler indicates the line and the position where the type mismatch has occurred and specifies the incompatible types it has detected. This error is a generalization of the method X in class Y cannot be applied to given types and the constructor X in class Y cannot be applied to given types errors discussed in [4].
The incompatible types error most often occurs when manual or explicit conversion between types is required, but it can also happen by accident when using an incorrect API, usually involving the use of an incorrect reference type or the invocation of an incorrect method with an identical or similar name.
Incompatible Types Error Examples
Explicit type casting
Assigning a value of one primitive type to another can happen in one of two directions. Either from a type of a smaller size to one of a larger size (upcasting), or from a larger-sized type to a smaller-sized type (downcasting). In the case of the former, the data will take up more space but will be intact as the larger type can accommodate any value of the smaller type. So the conversion here is done automatically. However, converting from a larger-sized type to a smaller one necessitates explicit casting because some data may be lost in the process.
Fig. 1(a) shows how attempting to assign the values of the two double variables a and b to the int variables x and y results in the incompatible types error at compile-time. Prefixing the variables on the right-hand side of the assignment with the int data type in parenthesis (lines 10 & 11 in Fig. 1(b)) fixes the issue. Note how both variables lost their decimal part as a result of the conversion, but only one kept its original value—this is exactly why the error message reads possible lossy conversion from double to int and why the incompatible types error is raised in this scenario. By capturing this error, the compiler prevents accidental loss of data and forces the programmer to be deliberate about the conversion. The same principle applies to downcasting reference types, although the process is slightly different as polymorphism gets involved [5].
1 2 3 4 5 6 7 8 9 10 11 12 13 14
package rollbar; public class IncompatibleTypesCasting < public static void main(String. args) < double a = 10.5; double b = 5.0; System.out.println("a: " + a + "\tb: " + b); int x = a; int y = b; System.out.println("x: " + x + "\ty: " + y); >>
IncompatibleTypesCasting.java:10: error: incompatible types: possible lossy conversion from double to int int x = a; ^ IncompatibleTypesCasting.java:11: error: incompatible types: possible lossy conversion from double to int int y = b; ^ 2 errors
1 2 3 4 5 6 7 8 9 10 11 12 13 14
package rollbar; public class IncompatibleTypesCasting < public static void main(String. args) < double a = 10.5; double b = 5.0; System.out.println("a: " + a + "\tb: " + b); int x = (int) a; int y = (int) b; System.out.println("x: " + x + "\ty: " + y); >>
Can Constructors Throw Exceptions in Java
How to Fix and Avoid NullPointerException in Java
How to Fix «Illegal Start of Expression» in Java
«Rollbar allows us to go from alerting to impact analysis and resolution in a matter of minutes. Without it we would be flying blind.»