2.1 Using Type-Safe Lists
One of Java's greatest strengths is its typing. Everything is an object, and,
in fact, every class either explicitly or implicitly descends from Object.
This provides a tremendous
amount of type-safetyyour methods can
take Integers, Strings, Lists, Maps, or your own custom objects as parameters, and know at the outset what they'll have to work with.With all this type-safety, Java has a gaping hole that Tiger finally fillsthe ability to create type-safe arrays
and lists, ensuring that collections of
objects only allow for a certain type to be inserted.
2.1.1 How do I do that?
One of the most annoying tasks in Java is having to cast objects pulled
out of a List, when you already know what's in the List (such as when
you fill it yourself, or a trusted source handles populating it):NOTEGenerics don't apply to primitive types.
List listOfStrings = getListOfStrings( );Remove that cast, thoughpull out (String)and you'll get a compiler
for (Iterator i = listOfStrings.iterator( ); i.hasNext( ); ) {
String item = (String)i.next( );
// Work with that string
}
error:NOTEThis particular code sample is in com.oreilly.
tiger.ch02. GenericsTester.
[javac] Compiling 1 source file to code\classesNo matter how much you trust the getListOfStrings( ) method, the
[javac] code\src\com\oreilly\tiger\ch02\GenericsTester.java:17:
incompatible types
[javac] found : java.lang.Object
[javac] required: java.lang.String
[javac] String item = i.next( );
[javac] ^
[javac] Note: code\src\com\oreilly\tiger\ch02\GenericsTester.java uses
unchecked or unsafe operations.
[javac] Note: Recompile with -Xlint:unchecked for details.
[javac] 1 error
compiler doesn't trust it one bit. It assumes the worst, and if you've ever
had anyone else work with you, you realize the compiler is often right
more than you are.Generics let you finally get around this, by limiting the type that a particular
List will accept:
List<String> listOfStrings;While this syntax probably looks pretty odd, it does the tricklistOfStrings can now only be populated with String instances. You
also need to assign it an instance that only accepts the same type:
List<String> listOfStrings = new LinkedList<String>( );I realize that the syntax just gets weirder, but that's what you have to
work with. Angle brackets everywhere! Now you can add Strings to this
List, but you cannot add any other type:NOTEHere, and in other output dumps, I've made slight formatting changes to fit things on the printed page.
List<String> onlyStrings = new LinkedList<String>( );The compiler will let you know about the problem:
onlyStrings.add("Legal addition");
onlyStrings.add(new StringBuilder("Illegal Addition"));
onlyStrings.add(25);
[javac] code\src\com\oreilly\tiger\ch02\GenericsTester.java:24:
cannot find symbol
[javac] symbol : method add(java.lang.StringBuilder)
[javac] location: interface java.util.List<java.lang.String>
[javac] onlyStrings.add(new StringBuilder("Illegal Addition"));
[javac] ^
[javac] src\com\oreilly\tiger\ch02\GenericsTester.java:25: cannot find
symbol
[javac] symbol : method add(int)
[javac] location: interface java.util.List<java.lang.String>
[javac] onlyStrings.add(25);
[javac] ^
[javac] Note: code\src\com\oreilly\tiger\ch02\GenericsTester.java uses
unchecked or unsafe operations.
[javac] Note: Recompile with -Xlint:unchecked for details.
[javac] 2 errors
2.1.2 What just happened?
In pre-Tiger versions of Java, the method signature for add( ) in List looked like this:
public boolean add(Object obj);In Tiger, though, things have changed:
public boolean add(E o);Before you go looking up E in Javadoc, though, it's just a placeholder. It
indicates that this
method declares a type variable (E) and can be parameterized.
The entire List class is generic:
public interface List<E> extends Collection, Iterable {There's that E again. When
you supply a type in the initialization of a
List, you parameterize the typeyou indicate what type its parameters can accept:
List<String> onlyStrings = new LinkedList<String>( );One way to understand this is to imagine that the compiler replaces
every occurrence of E with the type you suppliedin this case, a String.
Of course, this is just done for this particular instance of List. You can
have multiple Lists, all with different types, and all in the same program
block.The end result of all this is that onlyStrings no longer has a method
add(Object obj); it only has add(String o). So, when the compiler sees
add( ) with anything other than a String parameter, it kicks out an error.
This is the power of generics, and parameterized typesthey provide built-in type safety for your collection types.
2.1.3 What about...
...lists of primitive types? The
types that are allowed by Lists (and other
collection classes) are all objects; as a result, they don't work with primitive
values. The introduction of generics, despite all of its wonder and
magic, doesn't change this. So the following won't compile:
List<int> list = new LinkedList<int>( );However, this will:
List<Integer> list = new LinkedList<Integer>( );If you're thinking that now you've got to do all sorts of annoying conversion
between int and Integer, that's just because you haven't made it to
Chapter 4 yet. In that chapter, you'll see that autoboxing makes this a
particularly useful way to deal with primitives.