What is an unchecked warning in Java?
[Java
programming
]
Consider the following code:
import java.util.List;
import java.util.ArrayList;
public class UncheckedChecker {
List list = new ArrayList();
}
When compiled with javac -Xlint
(nonstardard option for turning on all warnings), the following output is spit out to console:
UncheckedChecker.java:5: warning: [rawtypes] found raw type: List
List list = new ArrayList();
^
missing type arguments for generic class List
where E is a type-variable:
E extends Object declared in interface List
UncheckedChecker.java:5: warning: [rawtypes] found raw type: ArrayList
List list = new ArrayList();
^
missing type arguments for generic class ArrayList
where E is a type-variable:
E extends Object declared in class ArrayList
2 warnings
Java generics was introduced in Java 1.5 and is heavily used in modern Java. The question is: why should we always use types in generic classes?
Let’s illustrate this using an code snippet from Effective Java:
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class UncheckedChecker2 {
public static void main(String[] args) {
// This collection will ONLY hold a collection
// of objects of type Stamp!
// Please, don't be cruel and do not insert Coins
// into this collection!
List stamps = new ArrayList();
stamps.add(new Stamp(1998, "JFK"));
stamps.add(new Coin(1954, 20));
//...
for(Iterator i = stamps.iterator(); i.hasNext();) {
Stamp st = (Stamp) i.next();
System.out.println(
"Stamp from the year "
+ st.year
+ " and of value "
+ st.name);
}
}
public static final class Stamp {
public final int year;
public final String name;
public Stamp(int year, String name) {
this.year = year;
this.name = name;
}
}
public static final class Coin {
public final int year;
public final int value;
public Coin(int year, int value) {
this.year = year;
this.value = value;
}
}
}
As you can imagine, compiling and running this results in the following:
$ javac UncheckedChecker2.java
Note: UncheckedChecker2.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
$ java UncheckedChecker2
Stamp from the year 1998 and of value JFK
Exception in thread "main" java.lang.ClassCastException: UncheckedChecker2$Coin cannot be cast to UncheckedChecker2$Stamp
at UncheckedChecker2.main(UncheckedChecker2.java:15)
In Angelika Langer’s FAQ, another possible type of warning is pointed out to be possible to happen:
class Wrapper<T> {
private T wrapped ;
public Wrapper (T arg) {wrapped = arg;}
...
public Wrapper <T> clone() {
Wrapper<T> clon = null;
try {
clon = (Wrapper<T>) super.clone(); // unchecked warning
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
try {
Class<?> clzz = this.wrapped.getClass();
Method meth = clzz.getMethod("clone", new Class[0]);
Object dupl = meth.invoke(this.wrapped, new Object[0]);
clon.wrapped = (T) dupl; // unchecked warning
} catch (Exception e) {}
return clon;
}
}
She explains that “A cast whose target type is either a (concrete or bounded wildcard) parameterized type or a type parameter is unsafe, if a dynamic type check at runtime is involved. At runtime, only the type erasure is available, not the exact static type that is visible in the source code. As a result, the runtime part of the cast is performed based on the type erasure, not on the exact static type.”
In the example, the cast to Wrapper would check whether the object returned from super.clone
is a Wrapper
, not whether it is a wrapper with a particular type. Similarly, the casts to the type parameter T are cast to type Object at runtime, and probably optimized away altogether. Due to type erasure, the runtime system is unable to perform more useful type checks at runtime.
Another point to this is to mention how generics in Java are implemented. Java language implements generics via principle called type erasure. This is basically a dirty hack which is done at compile time - all the generics are erased by the compiler (and compiler adds casts to the generic type where necessary). To demonstrate this further, consider this Java source code and its appropriate bytecode:
import java.util.List;
import java.util.ArrayList;
public class CheckedChecker {
public static void main(String[] args) {
List<String> strs = new ArrayList<>();
strs.add("ahoj");
strs.add("svete");
for(String str : strs) {
System.out.println(str);
}
}
}
Output of javap -c UncheckedChecker
:
public class CheckedChecker {
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList.<init>:()V
7: astore_1
8: aload_1
9: ldc #4 // String ahoj
11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: aload_1
18: ldc #6 // String svete
20: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
25: pop
26: aload_1
27: invokeinterface #7, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
32: astore_2
33: aload_2
34: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
39: ifeq 62
42: aload_2
43: invokeinterface #9, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
48: checkcast #10 // class java/lang/String
51: astore_3
52: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
55: aload_3
56: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
59: goto 33
62: return
}
What we naturally expect from compiler when type erasure is present in the language architecture is that compiler refuses to compile code with generics which breaks type safety. Consider this simple example:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MyStamps {
public static final class Stamp {
public final int year;
public final String name;
public Stamp(int year, String name) {
this.year = year;
this.name = name;
}
}
public static final List<Stamp> STAMPS =
Arrays.asList(
new Stamp(1938, "TGM"),
new Stamp(2008, "Special edition")
);
public static void main(String... args) {
STAMPS.add("My new stamp");
}
}
It should be obvious that adding object of type java.lang.String
should be an illegal operation as java.lang.String
is not a subtype of Stamp
.
For completeness sake, here is the compiler output:
MyStamps.java:21: error: no suitable method found for add(String)
STAMPS.add("My new stamp");
^
method Collection.add(Stamp) is not applicable
(argument mismatch; String cannot be converted to Stamp)
method List.add(Stamp) is not applicable
(argument mismatch; String cannot be converted to Stamp)
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
Further reading:
- Exhaustive generics resource by Angelika Langer
- Joshua Bloch’s Effective Java has got one whole section designated to generics