What is a raw type?
The Java Language Specification defines a raw type as follows:
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 type R
that is not inherited from a superclass or superinterface of R
.
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
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 type List<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 a List<String>
to a parameter of type List
, you can't pass it to a parameter of type List<Object>
. There are subtyping rules for generics, and List<String>
is a subtype of the raw type List
, but not of the parameterized type List<Object>
. As a consequence, you lose type safety if you use raw type like List
, but not if you use a parameterized type like List<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
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 type C
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 to C
.
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 as s
and the erasures of all the formal parameter types given in s
.
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
, not List<String>.class
instanceof
operand, e.g. o instanceof Set
, not o instanceof Set<String>
See also
Best Solution
RobNapier's answer is (as usual) quite good, but just for an alternate perspective that might prove further enlightening...
On Associated Types
A protocol is an abstract set of requirements — a checklist that a concrete type must fulfill in order to say it conforms to the protocol. Traditionally one thinks of that checklist of being behaviors: methods or properties implemented by the concrete type. Associated types are a way of naming the things that are involved in such a checklist, and thereby expanding the definition while keeping it open-ended as to how a conforming type implements conformance.
When you see:
What that means is that, for a type to claim conformance to
SimpleSetType
, not only must that type containinsert(_:)
andcontains(_:)
functions, those two functions must take the same type of parameter as each other. But it doesn't matter what the type of that parameter is.You can implement this protocol with a generic or non-generic type:
Notice that nowhere does
BagOfBytes
orSetOfEquatables
define the connection betweenSimpleSetType.Element
and the type used as the parameter for their two methods — the compiler automagically works out that those types are associated with the right methods, so they meet the protocol's requirement for an associated type.On Generic Type Parameters
Where associated types expand your vocabulary for creating abstract checklists, generic type parameters restrict the implementation of a concrete type. When you have a generic class like this:
It doesn't say that there are lots of different ways to make a
ViewController
(as long as you have aview
), it says aViewController
is a real, concrete thing, and it has aview
. And furthermore, we don't know exactly what kind of view any givenViewController
instance has, but we do know that it must be aView
(either a subclass of theView
class, or a type implementing theView
protocol... we don't say).Or to put it another way, writing a generic type or function is sort of a shortcut for writing actual code. Take this example:
This has the same effect as if you went through all the
Equatable
types and wrote:As you can see, we're creating code here, implementing new behavior — much unlike with protocol associated types where we're only describing the requirements for something else to fulfill.
TLDR
Associated types and generic type parameters are very different kinds of tools: associated types are a language of description, and generics are a language of implementation. They have very different purposes, even though their uses sometimes look similar (especially when it comes to subtle-at-first-glance differences like that between an abstract blueprint for collections of any element type, and an actual collection type that can still have any generic element). Because they're very different beasts, they have different syntax.
Further reading
The Swift team has a nice writeup on generics, protocols, and related features here.