Saturday, May 31, 2014

Raw Types and Generics [Java] [Notes]

Some Important Concepts:

Source: polygenelubricants's answer here : link.

What is a raw type?

The Java Language Specification defines a raw type as follows:

JLS 4.8 Raw Types

A raw type is define to be either:
  • The name of a generic type declaration used without any accompanying actual type parameters.
  • Any non-static type member 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 second 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 andmanually check every add yourself, and then manually cast to String every item from namesEven better, though is NOT to use a raw type and let the compiler does 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.

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



The Diamond Operator [Java 7] [Source: ColinD's answer here.]

The issue with
List<String> list = new LinkedList();
is that on the left hand side, you are using the generic type List<String> where on the right side you are using the raw type LinkedList. Raw types in Java effectively only exist for compatibility with pre-generics code and should never be used in new code unless you absolutely have to.
Now, if Java had generics from the beginning and didn't have types, such as LinkedList, that were originally created before it had generics, it probably could have made it so that the constructor for a generic type automatically infers its type parameters from the left-hand side of the assignment if possible. But it didn't, and it must treat raw types and generic types differently for backwards compatibility. That leaves them needing to make a slightly different, but equally convenient, way of declaring a new instance of a generic object without having to repeat its type parameters... the diamond operator.
As far as your original example of List<String> list = new LinkedList(), the compiler generates a warning for that assignment because it must. Consider this:
List<String> strings = ... // some list that contains some strings

// Totally legal since you used the raw type and lost all type checking!
List<Integer> integers = new LinkedList(strings);
Generics exist to provide compile-time protection against doing the wrong thing. In the above example, using the raw type means you don't get this protection and will get an error at runtime. This is why you should not use raw types.
// Not legal since the right side is actually generic!
List<Integer> integers = new LinkedList<>(strings);
The diamond operator, however, allows the right hand side of the assignment to be defined as a true generic instance with the same type parameters as the left side... without having to type those parameters again. It allows you to keep the safety of generics with almost the same effort as using the raw type.
I think the key thing to understand is that raw types (with no <>) cannot be treated the same as generic types. When you declare a raw type, you get none of the benefits and type checking of generics. You also have to keep in mind that generics are a general purpose part of the Java language... they don't just apply to the no-arg constructors of Collections!

No comments:

Post a Comment