What is a raw type and why shouldn't we use it?
What is a raw type?
The Java Language Specification defines a raw type as follows:
JLS 4.8 Raw Types
A raw type is defined to be one of:
The reference type that is formed by taking the name of a generic type declaration without an accompanying type argument list.
An array type whose element type is a raw type.
A non-
static
member type of a raw typeR
that is not inherited from a superclass or superinterface ofR
.
Here's an example to illustrate:
public class MyType<E> {
class Inner { }
static class Nested { }
public static void main(String[] args) {
MyType mt; // warning: MyType is a raw type
MyType.Inner inn; // warning: MyType.Inner is a raw type
MyType.Nested nest; // no warning: not parameterized type
MyType<Object> mt1; // no warning: type parameter given
MyType<?> mt2; // no warning: type parameter given (wildcard OK!)
}
}
Here, MyType<E>
is a parameterized type (JLS 4.5). It is common to colloquially refer to this type as simply MyType
for short, but technically the name is MyType<E>
.
mt
has a raw type (and generates a compilation warning) by the first bullet point in the above definition; inn
also has a raw type by the third bullet point.
MyType.Nested
is not a parameterized type, even though it's a member type of a parameterized type MyType<E>
, because it's static
.
mt1
, and mt2
are both declared with actual type parameters, so they're not raw types.
What's so special about raw types?
Essentially, raw types behaves just like they were before generics were introduced. That is, the following is entirely legal at compile-time.
List names = new ArrayList(); // warning: raw type!
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // not a compilation error!
The above code runs just fine, but suppose you also have the following:
for (Object o : names) {
String name = (String) o;
System.out.println(name);
} // throws ClassCastException!
// java.lang.Boolean cannot be cast to java.lang.String
Now we run into trouble at run-time, because names
contains something that isn't an instanceof String
.
Presumably, if you want names
to contain only String
, you could perhaps still use a raw type and manually check every add
yourself, and then manually cast to String
every item from names
. Even better, though is NOT to use a raw type and let the compiler do all the work for you, harnessing the power of Java generics.
List<String> names = new ArrayList<String>();
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // compilation error!
Of course, if you DO want names
to allow a Boolean
, then you can declare it as List<Object> names
, and the above code would compile.
See also
- Java Tutorials/Generics
How's a raw type different from using <Object>
as type parameters?
The following is a quote from Effective Java 2nd Edition, Item 23: Don't use raw types in new code:
Just what is the difference between the raw type
List
and the parameterized typeList<Object>
? Loosely speaking, the former has opted out generic type checking, while the latter explicitly told the compiler that it is capable of holding objects of any type. While you can pass aList<String>
to a parameter of typeList
, you can't pass it to a parameter of typeList<Object>
. There are subtyping rules for generics, andList<String>
is a subtype of the raw typeList
, but not of the parameterized typeList<Object>
. As a consequence, you lose type safety if you use raw type likeList
, but not if you use a parameterized type likeList<Object>
.
To illustrate the point, consider the following method which takes a List<Object>
and appends a new Object()
.
void appendNewObject(List<Object> list) {
list.add(new Object());
}
Generics in Java are invariant. A List<String>
is not a List<Object>
, so the following would generate a compiler warning:
List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!
If you had declared appendNewObject
to take a raw type List
as parameter, then this would compile, and you'd therefore lose the type safety that you get from generics.
See also
- What is the difference between
<E extends Number>
and<Number>
? - java generics (not) covariance
How's a raw type different from using <?>
as a type parameter?
List<Object>
, List<String>
, etc are all List<?>
, so it may be tempting to just say that they're just List
instead. However, there is a major difference: since a List<E>
defines only add(E)
, you can't add just any arbitrary object to a List<?>
. On the other hand, since the raw type List
does not have type safety, you can add
just about anything to a List
.
Consider the following variation of the previous snippet:
static void appendNewObject(List<?> list) {
list.add(new Object()); // compilation error!
}
//...
List<String> names = new ArrayList<String>();
appendNewObject(names); // this part is fine!
The compiler did a wonderful job of protecting you from potentially violating the type invariance of the List<?>
! If you had declared the parameter as the raw type List list
, then the code would compile, and you'd violate the type invariant of List<String> names
.
A raw type is the erasure of that type
Back to JLS 4.8:
It is possible to use as a type the erasure of a parameterized type or the erasure of an array type whose element type is a parameterized type. Such a type is called a raw type.
[...]
The superclasses (respectively, superinterfaces) of a raw type are the erasures of the superclasses (superinterfaces) of any of the parameterizations of the generic type.
The type of a constructor, instance method, or non-
static
field of a raw typeC
that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding toC
.
In simpler terms, when a raw type is used, the constructors, instance methods and non-static
fields are also erased.
Take the following example:
class MyType<E> {
List<String> getNames() {
return Arrays.asList("John", "Mary");
}
public static void main(String[] args) {
MyType rawType = new MyType();
// unchecked warning!
// required: List<String> found: List
List<String> names = rawType.getNames();
// compilation error!
// incompatible types: Object cannot be converted to String
for (String str : rawType.getNames())
System.out.print(str);
}
}
When we use the raw MyType
, getNames
becomes erased as well, so that it returns a raw List
!
JLS 4.6 continues to explain the following:
Type erasure also maps the signature of a constructor or method to a signature that has no parameterized types or type variables. The erasure of a constructor or method signature
s
is a signature consisting of the same name ass
and the erasures of all the formal parameter types given ins
.The return type of a method and the type parameters of a generic method or constructor also undergo erasure if the method or constructor's signature is erased.
The erasure of the signature of a generic method has no type parameters.
The following bug report contains some thoughts from Maurizio Cimadamore, a compiler dev, and Alex Buckley, one of the authors of the JLS, on why this sort of behavior ought to occur: https://bugs.openjdk.java.net/browse/JDK-6400189. (In short, it makes the specification simpler.)
If it's unsafe, why is it allowed to use a raw type?
Here's another quote from JLS 4.8:
The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of genericity into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.
Effective Java 2nd Edition also has this to add:
Given that you shouldn't use raw types, why did the language designers allow them? To provide compatibility.
The Java platform was about to enter its second decade when generics were introduced, and there was an enormous amount of Java code in existence that did not use generics. It was deemed critical that all this code remains legal and interoperable with new code that does use generics. It had to be legal to pass instances of parameterized types to methods that were designed for use with ordinary types, and vice versa. This requirement, known as migration compatibility, drove the decision to support raw types.
In summary, raw types should NEVER be used in new code. You should always use parameterized types.
Are there no exceptions?
Unfortunately, because Java generics are non-reified, there are two exceptions where raw types must be used in new code:
- Class literals, e.g.
List.class
, notList<String>.class
-
instanceof
operand, e.g.o instanceof Set
, noto instanceof Set<String>
See also
- Why is
Collection<String>.class
Illegal?
What are raw types in Java, and why do I often hear that they shouldn't be used in new code?
Raw-types are ancient history of the Java language. In the beginning there were Collections
and they held Objects
nothing more and nothing less. Every operation on Collections
required casts from Object
to the desired type.
List aList = new ArrayList();
String s = "Hello World!";
aList.add(s);
String c = (String)aList.get(0);
While this worked most of the time, errors did happen
List aNumberList = new ArrayList();
String one = "1";//Number one
aNumberList.add(one);
Integer iOne = (Integer)aNumberList.get(0);//Insert ClassCastException here
The old typeless collections could not enforce type-safety so the programmer had to remember what he stored within a collection.
Generics where invented to get around this limitation, the developer would declare the stored type once and the compiler would do it instead.
List<String> aNumberList = new ArrayList<String>();
aNumberList.add("one");
Integer iOne = aNumberList.get(0);//Compile time error
String sOne = aNumberList.get(0);//works fine
For Comparison:
// Old style collections now known as raw types
List aList = new ArrayList(); //Could contain anything
// New style collections with Generics
List<String> aList = new ArrayList<String>(); //Contains only Strings
More complex the Compareable interface:
//raw, not type save can compare with Other classes
class MyCompareAble implements CompareAble
{
int id;
public int compareTo(Object other)
{return this.id - ((MyCompareAble)other).id;}
}
//Generic
class MyCompareAble implements CompareAble<MyCompareAble>
{
int id;
public int compareTo(MyCompareAble other)
{return this.id - other.id;}
}
Note that it is impossible to implement the CompareAble
interface with compareTo(MyCompareAble)
with raw types.
Why you should not use them:
- Any
Object
stored in aCollection
has to be cast before it can be used - Using generics enables compile time checks
- Using raw types is the same as storing each value as
Object
What the compiler does: Generics are backward compatible, they use the same java classes as the raw types do. The magic happens mostly at compile time.
List<String> someStrings = new ArrayList<String>();
someStrings.add("one");
String one = someStrings.get(0);
Will be compiled as:
List someStrings = new ArrayList();
someStrings.add("one");
String one = (String)someStrings.get(0);
This is the same code you would write if you used the raw types directly. Thought I'm not sure what happens with the CompareAble
interface, I guess that it creates two compareTo
functions, one taking a MyCompareAble
and the other taking an Object
and passing it to the first after casting it.
What are the alternatives to raw types: Use generics
A raw type is the name of a generic class or interface without any type arguments. For example, given the generic Box class:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
To create a parameterized type of Box<T>
, you supply an actual type argument for the formal type parameter T
:
Box<Integer> intBox = new Box<>();
If the actual type argument is omitted, you create a raw type of Box<T>
:
Box rawBox = new Box();
Therefore, Box
is the raw type of the generic type Box<T>
. However, a non-generic class or interface type is not a raw type.
Raw types show up in legacy code because lots of API classes (such as the Collections classes) were not generic prior to JDK 5.0. When using raw types, you essentially get pre-generics behavior — a Box
gives you Object
s. For backward compatibility, assigning a parameterized type to its raw type is allowed:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox; // OK
But if you assign a raw type to a parameterized type, you get a warning:
Box rawBox = new Box(); // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox; // warning: unchecked conversion
You also get a warning if you use a raw type to invoke generic methods defined in the corresponding generic type:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8); // warning: unchecked invocation to set(T)
The warning shows that raw types bypass generic type checks, deferring the catch of unsafe code to runtime. Therefore, you should avoid using raw types.
The Type Erasure section has more information on how the Java compiler uses raw types.
Unchecked Error Messages
As mentioned previously, when mixing legacy code with generic code, you may encounter warning messages similar to the following:
Note: Example.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
This can happen when using an older API that operates on raw types, as shown in the following example:
public class WarningDemo {
public static void main(String[] args){
Box<Integer> bi;
bi = createBox();
}
static Box createBox(){
return new Box();
}
}
The term "unchecked" means that the compiler does not have enough type information to perform all type checks necessary to ensure type safety. The "unchecked" warning is disabled, by default, though the compiler gives a hint. To see all "unchecked" warnings, recompile with -Xlint:unchecked.
Recompiling the previous example with -Xlint:unchecked reveals the following additional information:
WarningDemo.java:4: warning: [unchecked] unchecked conversion
found : Box
required: Box<java.lang.Integer>
bi = createBox();
^
1 warning
To completely disable unchecked warnings, use the -Xlint:-unchecked flag. The @SuppressWarnings("unchecked")
annotation suppresses unchecked warnings. If you are unfamiliar with the @SuppressWarnings
syntax, see Annotations.
Original source: Java Tutorials
A "raw" type in Java is a class which is non-generic and deals with "raw" Objects, rather than type-safe generic type parameters.
For example, before Java generics was available, you would use a collection class like this:
LinkedList list = new LinkedList();
list.add(new MyObject());
MyObject myObject = (MyObject)list.get(0);
When you add your object to the list, it doesn't care what type of object it is, and when you get it from the list, you have to explicitly cast it to the type you are expecting.
Using generics, you remove the "unknown" factor, because you must explicitly specify which type of objects can go in the list:
LinkedList<MyObject> list = new LinkedList<MyObject>();
list.add(new MyObject());
MyObject myObject = list.get(0);
Notice that with generics you don't have to cast the object coming from the get call, the collection is pre-defined to only work with MyObject. This very fact is the main driving factor for generics. It changes a source of runtime errors into something that can be checked at compile time.
private static List<String> list = new ArrayList<String>();
You should specify the type-parameter.
The warning advises that types that are defined to support generics should be parameterized, rather than using their raw form.
List
is defined to support generics: public class List<E>
. This allows many type-safe operations, that are checked compile-time.