Raw Types and Unchecked Warnings
A generic type without its formal type parameters is called a raw type. The raw type is the supertype of all parameterized types of the generic type. For example, the raw type Node is the supertype of the parameterized types Node<String>, Node<Integer>, and Node<Node<String>>. The last parameterized type is an example of a nested parameterization. It means that a node of this type has a node of type Node<String> as data.
A parameterized type (e.g., Node<String>) is not a class. Parameterized types are used by the compiler to check that objects created are used correctly in the program. The parameterized types Node<String>, Node<Integer>, and Node<Node<String>> are all represented at runtime by their raw type Node. In other words, the compiler does not create a new class for each parameterized type. Only one class (Node) exists that has the name of the generic class (Node<E>), and the compiler generates only one class file (Node.class) with the Java bytecode for the generic class.
Only reference types (excluding array creation and enumerations) can be used in invocations of generic types. A primitive type is not permitted as an actual type parameter, the reason being that values of primitive types have different sizes. This would require different code being generated for each primitive type used as an actual type parameter, but there is only one implementation of a generic class in Java.
Generics are implemented in the compiler only. The JVM is oblivious about the use of generic types. It does not differentiate between Node<String> and Node<Integer>, and just knows about the class Node. The compiler translates the generic class by a process known as type erasure; meaning that information about type parameters is erased and casts are inserted to make the program type-safe at runtime. The compiler guarantees that casts added at compile time never fail at runtime, when the program compiles without any unchecked warnings.
It is possible to use a generic class by its raw type only, like a non-generic class, without specifying actual type parameters for its usage. Example 11.6 illustrates mixing generic and non-generic code. The compiler will issue an unchecked warning if such a use can be a potential problem at runtime. Such usage is permitted for backward compatibility with legacy code, but is strongly advised against when writing new code.
The assignment at (5) in Example 11.6 shows that it is always possible to assign the reference value of a parameterized type to a reference of the raw type, as the latter is the supertype of the former. However, the raw type reference can be used to violate the type-safety of the node at runtime, as shown at (6). Calling a method on a node using the raw type reference results in an unchecked call warning by the compiler. In this particular case, a String is set as the data of an Integer node.
…
Node<Integer> intNode = new Node<>(2020, null);
Integer iRef = intNode.getData(); // Integer object with int value 2020
…
Node rawNode = intNode; // (5) Assigning to raw type always possible.
rawNode.setData(“BOOM”); // (6) Unchecked call warning!
intNode = rawNode; // (7) Unchecked conversion warning!
iRef = intNode.getData(); // (8) ClassCastException!
iRef = rawNode.getData(); // (9) Compile-time error!
Assigning the reference value of a raw type to a reference of the parameterized type results in an unchecked conversion warning from the compiler, as shown at (7). If the node referred to by the raw type reference is not of type Integer, using it as a node of type Integer can lead to problems at runtime, as shown at (8). The assignment at (8) is only type compatible, not type-safe, as its type-safety is compromised at (6) as explained above. A ClassCastException is thrown at runtime, since an Integer was expected, but a String was returned by the getData() method.
The assignment at (9) does not compile because of type mismatch: Without the generic type information, the compiler infers that the call on the getData() method using the raw type reference rawNode can only return an Object, whereas the type of the variable on the left-hand side is Integer.
The class Preliminaries in Example 11.6 is shown compiled with the non-standard option -Xlint:unchecked. The compiler recommends using this option when non-generic and generic code are mixed in this way. The program compiles in spite of the unchecked warnings, and can be executed. But all guarantees of type-safety are off in the face of unchecked warnings. See also §11.11, p. 613, which provides details on translation of generic code by type erasure.
Example 11.6 Unchecked Warnings
// A client for the generic class Node<E> in
Example 11.2
,
p. 568
.
public class Preliminaries {
public static void main(String[] args) {
Node<Integer> intNode = new Node<>(2018, null);
Integer iRef = intNode.getData(); // Integer object with int value 2018
intNode.setData(2020); // Ok.
// intNode.setData(“TwentyTwenty”); // (1) Compile-time error!
intNode.setNext(new Node<>(2019, null)); // (2020, (2019, null))
// intNode.setNext(new Node<>(“Hi”, null)); // (2) Compile-time error!
Node<String> strNode = new Node<>(“Hi”, null);
// intNode = strNode; // (3) Compile-time error!
String str = strNode.getData(); // (4) No explicit cast necessary.
Node rawNode = intNode; // (5) Assigning to raw type always possible.
rawNode.setData(“BOOM”); // (6) Unchecked call warning!
intNode = rawNode; // (7) Unchecked conversion warning!
iRef = intNode.getData(); // (8) ClassCastException!
// iRef = rawNode.getData(); // (9) Compile-time error!
}
}
Compiling the program:
>javac -Xlint:unchecked Preliminaries.java
Preliminaries.java:16: warning: [unchecked] unchecked call to setData(E) as a
member of the raw type Node
rawNode.setData(“BOOM”); // (6) Unchecked call warning!
^
where E is a type-variable:
E extends Object declared in class Node
Preliminaries.java:17: warning: [unchecked] unchecked conversion
intNode = rawNode; // (7) Unchecked conversion warning!
^
required: Node<Integer>
found: Node
2 warnings
Running the program:
>java Preliminaries
Exception in thread “main” java.lang.ClassCastException: java.lang.String cannot
be cast to java.lang.Integer
at Preliminaries.main(Preliminaries.java:18)
Leave a Reply