1. Introduction
In this quick tutorial, we’re going to take a look at raw types, what they are, and why we should avoid them.
2. Raw Types
A raw type is a name for a generic interface or class without its type argument:
List list = new ArrayList(); // raw type
Instead of:
List<Integer> listIntgrs = new ArrayList<>(); // parameterized type
List
Raw types can be useful when interfacing with non-generic legacy code.
Otherwise, though, it’s discouraged. This is because:
- They are not expressive
- They lack type safety, and
- Problems are observed at run time and not at compile time
3. Inexpressive
A raw type does not document and explains itself the way a parameterized type does.
We can easily infer that a parameterized type List
Let’s see the signature of the method get(int index) in the List interface to understand this better:
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException if the index is out of range
* (<tt>index < 0 || index >= size()</tt>)
*/
E get(int index);
The method get(int index) returns a String at position index in parameterized type List
However, for a raw type List, it returns an Object. Thus, we are required to take extra effort to inspect and identify the type of element in the raw type List and add an appropriate type-casting. This can introduce bugs at run time as raw type is not type safe.
4. Not Type-Safe
We get pre-generics behavior with raw types. Therefore, a raw type List accepts Object and can hold an element of any data type. This can lead to type safety issues when we mix parameterized and raw types.
Let’s see this by creating some code that instantiates a List
public void methodA() {
List<String> parameterizedList = new ArrayList<>();
parameterizedList.add("Hello Folks");
methodB(parameterizedList);
}
public void methodB(List rawList) { // raw type!
rawList.add(1);
}
The code gets compiled (with a warning), and the Integer gets added to the raw type List when executed. The List
The compiler prints out a warning due to the usage of raw types:
Note: RawTypeDemo.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
5. Problems at Runtime
Lack of type safety on a raw type has a causal effect that can lead to exceptions at runtime.
Let’s modify the previous example so that methodA gets the element at index position 1 of our List
public void methodA() {
List<String> parameterizedList = new ArrayList<>();
parameterizedList.add("Hello Folks");
methodB(parameterizedList);
String s = parameterizedList.get(1);
}
public void methodB(List rawList) {
rawList.add(1);
}
The code gets compiled (with the same warning) and throws a ClassCastException when executed. This happens as the method get(int index) returns an Integer, which cannot be assigned to a variable of type String:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
6. Conclusion
Raw types are hard to work with and can introduce bugs in our code.
Using them can lead to consequences that can be disastrous, and unfortunately, most of these disasters happen at run time.
Check out all the snippets in this tutorial over on GitHub.