Friday, February 25, 2011

Chapter 51: Generic Types

In the previous chapters about collections we saw that as per the new specifications of Java, we can actually specify the type of objects that a collection is going to take. It's a totally new concept when compared to people who are used to coding using the old fashion java way (before java 5) and so we are going to get into this topic in great detail. Before we begin, there is both good news and bad news. The bad news is that, this chapter is going to be very long and the good news is that you can expect lots of questions involving generics. So buckle up, sit tight and enjoy the chapter.

Lets, get started!!!

Exam Tip:
The Java 5 exam covers both pre-Java 5 (non-generic) and Java 5 style collections, and you’ll see questions that expect you to understand the tricky problems that can come from mixing non-generic and generic code together. So pay attention!!!

The Legacy Way of Using Collections

Here’s a review of a pre-Java 5 ArrayList intended to hold Strings. (We say “intended” because that’s about all you had—good intentions—to make sure that the ArrayList would hold only Strings).

List myList = new ArrayList(); // can't declare a type

myList.add("Rocky"); // OK, it will hold Strings

myList.add(new Car()); // and it will hold Cars too

myList.add(new Integer(42)); // and Integers...

A non-generic collection can hold any kind of object! A non-generic collection is quite happy to hold anything that is NOT a primitive.

This means that, it is the programmers responsibility to be careful. Having no way to guarantee collection type wasn’t very programmer-friendly for such a strongly typed language. We’re so used to the compiler stopping us from, say, assigning an int to a boolean reference or a String to a Car reference, but with collections, it was, “All objects are welcome here any time!”
And since a collection could hold anything, the methods that get objects out of the collection could have only one kind of return type—java.lang.Object. That meant that getting a String back out of our only-Strings-intended list required a cast:

String s = (String) myList.get(0);

And since you couldn’t guarantee that what was coming out really was a String (since you were allowed to put anything in the list), the cast could fail at runtime.

So, generics takes care of both ends (the putting in and getting out) by enforcing the type of your collections. Let’s update the String list:

List< String > myList = new ArrayList< String >();
myList.add("Rocky"); // OK, it will hold Strings
myList.add(new Car()); // compiler error!!

Perfect. That’s exactly what we want. By using generics syntax—which means putting the type in angle brackets "String", we’re telling the compiler that this collection can hold only String objects. The type in angle brackets is referred to as either the “parameterized type,” “type parameter,” or of course just old-fashioned “type.” In this chapter, we’ll refer to it both new ways.

So, now that what you put IN is guaranteed, you can also guarantee what comes OUT, and that means you can get rid of the cast when you get something from the collection. Instead of

String s = (String)myList.get(0);

// pre-generics, when a String wasn't guaranteed

we can now just say
String s = myList.get(0);

The compiler already knows that myList contains only things that can be assigned to a String reference, so now there’s no need for a cast. So far, it seems pretty simple. And with the new for loop, you can of course iterate over the guaranteed-to-be-String list:

for (String s : myList) {
int x = s.length();
// no need for a cast before calling a String method! The
// compiler already knew that "s" was a String coming from MyList
}

And of course you can declare a type parameter for a method argument, which then makes the argument a type safe reference:

void takeListOfStrings(List< String > strings) {
strings.add("xxx"); // no problem adding a String
}

The method above would NOT compile if we changed it to
void takeListOfStrings(List< String > strings) {
strings.add(new Integer(42)); // NO!! Will not work!!!
}

Return types can obviously be declared type safe as well:

public List< Car > getCarList() {
List< Car > Cars = new ArrayList< Car >();
// more code to insert Cars
return Cars;
}

The compiler will stop you from returning anything not compatible with a List< Car >. And since the compiler guarantees that only a type safe Car List is returned, those calling the method won’t need a cast to take Cars from the List:

Car d = getCarList().get(0); // Only a Car will come out

With pre-Java 5, non-generic code, the getCarList() method would be

public List getCarList() {
List Cars = new ArrayList();
// code to add only Cars... fingers crossed...
return Cars; // a List of ANYTHING will work here
}

and the caller would need a cast:
Car d = (Car) getCarList().get(0);

But what about the benefit of a completely heterogeneous collection? In other words, what if you liked the fact that before generics you could make an ArrayList that could hold any kind of object?

List myList = new ArrayList(); // old-style, non-generic

is almost identical to
List< Object > myList = new ArrayList< Object >();

Declaring a List with a type parameter of < Object > makes a collection that works in almost the same way as the original pre-Java 5, non-generic collection—you can put any Object type into the collection. You’ll see a little later that non-generic collections and collections of type "Object" aren’t entirely the same, but most of the time the differences do not matter.

Generics and Legacy Code

The easiest generics thing you’ll need to know for the exam is how to update non-generic code to make it generic. You just add a type in angle brackets (<>) immediately following the collection type in BOTH the variable declaration and the constructor call, including any place you declare a variable (so that means arguments and return types too). A pre-Java 5 List meant to hold only Integers:

List myList = new ArrayList();

becomes

List< Integer > myList = new ArrayList< Integer >();

and a list meant to hold only Strings goes from
public List changeStrings(ArrayList s) { }

to this:

public List< String > changeStrings(ArrayList< String > s) { }

Easy. And if there’s code that used the earlier non-generic version and performed a cast to get things out, that won’t break anyone’s code:

Integer i = (Integer) list.get(0);

// cast is no longer needed but it won't cause any problems

Mixing Generic and Non-generic Collections

Now here’s where it starts to get interesting...imagine we have an ArrayList, of type Integer, and we’re passing it into a method from a class whose source code we don’t have access to. Will this work?

// a Java 5 class using a generic collection

import java.util.*;
public class TestOldCollections {
public static void main(String[] args) {
List< Integer > myList = new ArrayList< Integer >();
// type safe collection

myList.add(4);
myList.add(6);

UnknownClass UnknownClass = new UnknownClass();
int total = UnknownClass.addValues(myList);
// pass it to an untyped argument
System.out.println(total);
}
}

The older, non-generics class we want to use:

import java.util.*;
class UnknownClass {
int addValues(List list) {
// method with a non-generic List argument,
// but assumes (with no guarantee) that it will be Integers
Iterator it = list.iterator();
int total = 0;
while (it.hasNext()) {
int i = ((Integer)it.next()).intValue();
total += i;
}
return total;
}
}

Yes, this works just fine. You can mix correct generic code with older non-generic code, and everyone is happy.

In the previous example, the addValues() legacy method assumed (or rather hoped) that the list passed in was indeed restricted to Integers, even though when the code was written, there was no guarantee. It was up to the programmers to be careful.

Since the addValues() method wasn’t doing anything except getting the Integer (using a cast) from the list and accessing its value, there were no problems. In that example, there was no risk to the caller’s code, but the legacy method might have exploded if the list passed in contained anything apart from Integers (which would cause a ClassCastException).
But now imagine that you call a legacy method that doesn’t just read a value but adds something to the ArrayList? Will this work?

import java.util.*;
public class TestLegacyBadExample {
public static void main(String[] args) {
List< Integer > myList = new ArrayList< Integer >();
myList.add(4);
myList.add(6);
ModifyMe in = new ModifyMe();
in.insert(myList); // pass List< Integer > to legacy code
}
}
class ModifyMe {
// method with a non-generic List argument
void insert(List list) {
list.add(new Integer(42)); // adds to the incoming list
}
}

Sure, this code works. It compiles, and it runs. The insert() method puts an Integer into the list that was originally typed as < Integer >, so no problem.

But...what if we modify the insert() method like this:
void insert(List list) {
list.add(new String("42"));
// put a String in the list passed in
}

Will that work? Yes, sadly, it does! It both compiles and runs. No runtime exception. Yet, someone just stuffed a String into a supposedly type safe ArrayList of type < Integer >. How can that be?

Remember, the older legacy code was allowed to put anything at all (except primitives) into a collection. And in order to support legacy code, Java 5 and Java 6 allows your newer type safe code to make use of older code.

So, the Java 5 or Java 6 compiler is forced into letting you compile your new type safe code even though your code invokes a method of an older class that takes a non-type safe argument and does who knows what with it.

However, just because the Java 5 compiler allows this code to compile doesn’t mean it has to be happy about it. In fact the compiler will warn you that you’re taking a big risk by sending your type protected ArrayList< Integer > into a dangerous method that can screw up your list and put in Floats, Strings, or even Cars.

When you called the addValues() method in the earlier example, it didn’t insert anything to the list (it simply added up the values within the collection), so there was no risk to the caller that his list would be modified in some horrible way. It compiled and ran just fine. But in the second version, with the legacy insert() method that adds a String, the compiler generated a warning:

javac TestLegacyBadExample.java
Note: TestLegacyBadExample.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

Remember that compiler warnings are NOT considered a compiler failure. The compiler generated a perfectly valid class file from the compilation, but it was smart enough to warn you by saying, “I seriously hope you know what you are doing because I cannot control what this old code is going to do with your new type-safe list”

Exam Tip: Be sure you know the difference between “compilation fails” and “compiles without error” and “compiles without warnings” and “compiles with warnings.” In most questions on the exam, you care only about compiles vs. compilation fails—compiler warnings don’t matter for most of the exam. But when you are using generics, and mixing both typed and untyped code, warnings matter.

Back to our example with the legacy code that does an insert, keep in mind that for BOTH versions of the insert() method (one that adds an Integer and one that adds a String) the compiler issues warnings. The compiler does NOT know whether the insert() method is adding the right thing (Integer) or wrong thing (String). The reason the compiler produces a warning is because the method is ADDING something to the collection! In other words, the compiler knows there’s a chance the method might add the wrong thing to a collection the caller thinks is type safe.

Exam Tip: For the purposes of the exam, unless the question includes an answer that mentions warnings, then even if you know compilation will produce warnings, that is still a successful compile! Compiling with warnings is NEVER considered a compilation failure.
The bottom line is this: code that compiles with warnings is still a successful compile. If the exam question wants to test your knowledge of whether code will produce a warning (or what you can do to the code to ELIMINATE warnings), the question (or answer) will explicitly include the word “warnings.”

So far, we’ve looked at how the compiler will generate warnings if it sees that there’s a chance your type safe collection could be messed up by older, non-type-safe code. But one of the questions developers often ask is, “Okay, sure, it compiles, but why does it RUN? Why does the code that inserts the wrong thing into my list work at runtime?” In other words, why does the JVM let old code stuff a String into your ArrayList< Integer >, without any problems at all? No exceptions, nothing. Just a quiet, behind-the-scenes, total violation of your type safety that you might not discover until the worst possible moment

There’s one Big Truth you need to know to understand why it runs without problems—the JVM has no idea that your ArrayList was supposed to hold only Integers. The typing information does not exist at runtime! All your generic code is strictly for the compiler. Through a process called “type erasure,” the compiler does all of its verifiFerrariions on your generic code and then strips the type information out of the class bytecode. At runtime, ALL collection code—both legacy and new Java 5 code you write using generics—looks exactly like the pre-generic version of collections. None of your typing information exists at runtime. In other words, even though you wrote

List< Integer > myList = new ArrayList< Integer >();

By the time the compiler is done with it, the JVM sees what it always saw before Java 5 and generics:
List myList = new ArrayList();

The compiler even inserts the casts for you—the casts you had to do to get things out of a pre-Java 5 collection.

Think of generics as strictly a compile-time protection. The compiler uses generic type information (the < type > in the angle brackets) to make sure that your code doesn’t put the wrong things into a collection, and that you do not assign what you get from a collection to the wrong reference type. But NONE of this protection exists at runtime.

This is a little different from arrays, which give you BOTH compile-time protection and runtime protection. Why did they do generics this way? Why is there no type information at runtime? The Answer is “To support legacy code”. At runtime, collections are collections just like the old days. What you gain from using generics is compile-time protection that guarantees that you won’t put the wrong thing into a typed collection, and it also eliminates the need for a cast when you get something out, since the compiler already knows that only an Integer is coming out of an Integer list.

The fact is, you don’t NEED runtime protection...until you start mixing up generic and non-generic code, as we did in the previous example. Then you can have disasters at runtime. The only advice we have is to pay very close attention to those compiler warnings:

javac TestLegacyBadExample.java
Note: TestLegacyBadExample.java uses unchecked or unsafe operations.

Note: Recompile with -Xlint:unchecked for details.

This compiler warning isn’t very descriptive, but the second note suggests that you recompile with -Xlint:unchecked. If you do, you’ll get something like this:
javac -Xlint:unchecked TestLegacyBadExample.java
TestLegacyBadExample.java:17: warning: [unchecked] unchecked call to
add(E) as a member of the raw type java.util.List
list.add(new String("42"));
^
1 warning

When you compile with the -Xlint:unchecked flag, the compiler shows you exactly which method(s) might be doing something dangerous. In this example, since the list argument was not declared with a type, the compiler treats it as legacy code and assumes no risk for what the method puts into the “raw” list.

On the exam, you must be able to recognize when you are compiling code that will produce warnings but still compile. And any code that compiles (even with warnings) will run! No type violations will be caught at runtime by the JVM, until those type violations mess with your code in some other way. In other words, the act of adding a String to an < Integer > list won’t fail at runtime until you try to treat that String you think is an Integer as an Integer.
For example, imagine you want your code to pull something out of your supposedly type safe ArrayList< Integer > that older code put a String into. It compiles (with warnings). It runs and the code actually adds the String to the list. But when you take the String that wasn’t supposed to be there in the first place, out of the list, and try to assign it to an Integer reference or invoke an Integer method, you’re doomed.

Keep in mind, then, that the problem of putting the wrong thing into a typed (generic) collection does not show up at the time you actually do the add() to the collection. It only shows up later, when you try to use something in the list and it doesn’t match what you were expecting. In the old (pre-Java 5) days, you always assumed that you might get the wrong thing out of a collection (since they were all non-type safe), so you took appropriate defensive steps in your code. The problem with mixing generic with non-generic code is that you won’t be expecting those problems if you have been misled into a false sense of security by having written type safe code. Just remember that the moment you turn that type safe collection over to older, non-type safe code, all your protection vanishes.

Again, pay very close attention to compiler warnings, and be prepared to see issues like this come up on the exam.

Polymorphism and Generics

Generic collections give you the same benefits of type safety that you’ve always had with arrays, but there are some crucial differences that can hurt you if you aren’t prepared. Most of these have to do with polymorphism.

You’ve already seen that polymorphism applies to the “base” type of the collection:
List< Integer > myList = new ArrayList< Integer >();

In other words, we were able to assign an ArrayList to a List reference, because List is a supertype of ArrayList. Nothing special there—this polymorphic assignment works the way it always works in Java, regardless of the generic typing.

But what about this?

class Car { }
class Ferrari extends Car { }
List< Car > myList = new ArrayList< Ferrari >();

No, it doesn’t work. There’s a very simple rule here—the type of the variable declaration must match the type you pass to the actual object type. If you declare List then whatever you assign to the reference MUST be of the generic type . Not a subtype of . Not a supertype of .

These are wrong:
List< Object > myList = new ArrayList< String >();
//Though a String is an Object – this wont work.
List< Number > numbers = new ArrayList< Integer >();
// remember that Integer is a subtype of Number

But these are fine:
List< String > myList = new ArrayList< String >();
List< Object > myList = new ArrayList< Object >();
List< Integer > myList = new ArrayList< Integer >();

Check out the below example. Do you think this would work?
List< JButton > myList = new ArrayList< JButton >();

Yes, it would. List and ArrayList are the base type and JButton is the generic type. So an ArrayList can be assigned to a List, but a collection of < JButton > cannot be assigned to a reference of < Object >, even though JButton is a subtype of Object.

The part that feels wrong for most developers is that this is NOT how it works with arrays, where you are allowed to do this

import java.util.*;
class Car { }
class Ferrari extends Car { }
public class TestGenericPolymorphism {
public static void main(String[] args) {
Car[] myArray = new Ferrari[3];
}
}

which means you’re also allowed to do this
Object[] myArray = new JButton[3];

but this is not allowed:
List< Object > list = new ArrayList< JButton >(); // NO!

Why are the rules for typing of arrays different from the rules for generics? Well, just remember that polymorphism does not work the same way for generics as it does with arrays.

Generic Methods

If you weren’t already familiar with generics, you might be feeling very uncomfortable with the implications of the previous no-polymorphic-assignment-for-generic-types thing. And why shouldn’t you be uncomfortable? One of the biggest benefits of polymorphism is that you can declare, say, a method argument of a particular type and at runtime be able to have that argument refer to any subtype—including those you’d never known about at the time you wrote the method with the supertype argument.

For example, imagine a simple polymorphism example of a mechanic (CarMechanic) class with a method checkAndRepair(). And right now, you have three Car subtypes—Ferrari, Bmw, and Porsche—each implementing the abstract checkAndRepair() method from Car:

abstract class Car {
public abstract void checkAndRepair();
}
class Ferrari extends Car {
public void checkAndRepair() {
System.out.println("Ferrari checkAndRepair");
}
}
class Bmw extends Car {
public void checkAndRepair() {
System.out.println("Bmw checkAndRepair");
}
}
class Porsche extends Car {
public void checkAndRepair() {
System.out.println("Porsche checkAndRepair");
} }

Forgetting collections/arrays for a moment, just imagine what the CarMechanic class needs to look like in order to have code that takes any kind of Car and invokes the Car checkAndRepair() method. Trying to overload the CarMechanic class with checkAndRepair() methods for every possible kind of Car is ridiculous, and obviously not extensible. You’d have to change the CarMechanic class every time someone added a new subtype of Car.

So in the CarMechanic class, you’d probably have a polymorphic method:
public void checkCar(Car a) {
a. checkAndRepair();
}

And of course we do want the CarMechanic to also have code that can take arrays of Ferraris, Bmws, or Porsches, for when the mechanic comes to the Ferrari, Bmw, or Porsche car. Again, we don’t want overloaded methods with arrays for each potential Car subtype, so we use polymorphism in the CarMechanic class:
public void checkCars(Car[] Cars) {
for(Car a : Cars) {
a.checkAndRepair();
}
}

Here is the entire example, complete with a test of the array polymorphism that takes any type of Car array (Ferrari[], Bmw[], Porsche[]).

import java.util.*;
abstract class Car {
public abstract void checkAndRepair();
}
class Ferrari extends Car {
public void checkAndRepair() {
System.out.println("Ferrari checkAndRepair");
}
}
class Bmw extends Car {
public void checkAndRepair() {
System.out.println("Bmw checkAndRepair");
}
}
class Porsche extends Car {
public void checkAndRepair() {
System.out.println("Porsche checkAndRepair");
}
}
public class CarMechanic {
// method takes an array of any Car subtype
public void checkCars(Car[] Cars) {
for(Car a : Cars) {
a.checkAndRepair();
}
}
public static void main(String[] args) {
// test it
Ferrari[] Ferraris = {new Ferrari(), new Ferrari()};
Bmw[] Bmws = {new Bmw(), new Bmw(), new Bmw()};
Porsche[] Porsches = {new Porsche()};

CarMechanic doc = new CarMechanic();
doc.checkCars(Ferraris); // pass the Ferrari[]
doc.checkCars(Bmws); // pass the Bmw[]
doc.checkCars(Porsches); // pass the Porsche[]
}
}

This works fine. But here’s why we brought this up as refresher—this approach does NOT work the same way with type safe collections!

In other words, a method that takes, say, an ArrayList< Car > will NOT be able to accept a collection of any Car subtype! That means ArrayList< Ferrari > cannot be passed into a method with an argument of ArrayList< Car >, even though we already know that this works just fine with plain old arrays.

Obviously this difference between arrays and ArrayList is consistent with the polymorphism assignment rules we already looked at—the fact that you cannot assign an object of type ArrayList< JButton > to a List< Object >. But this is where you really start to feel the pain of the distinction between typed arrays and typed collections.

We know it won’t work correctly, but let’s try changing the CarMechanic code to use generics instead of arrays:
public class CarMechanicGeneric {
// change the argument from Car[] to ArrayList< Car >
public void checkCars(ArrayList< Car > Cars) {
for(Car a : Cars) {
a.checkAndRepair();
}
}
public static void main(String[] args) {
// make ArrayLists instead of arrays for Ferrari, Bmw, Porsche
List< Ferrari > Ferraris = new ArrayList< Ferrari >();
Ferraris.add(new Ferrari());
Ferraris.add(new Ferrari());
List< Bmw > Bmws = new ArrayList< Bmw >();
Bmws.add(new Bmw());
Bmws.add(new Bmw());
List< Porsche > Porsches = new ArrayList< Porsche >();
Porsches.add(new Porsche());
// this code is the same as the Array version
CarMechanicGeneric doc = new CarMechanicGeneric();
// this worked when we used arrays instead of ArrayLists
doc.checkCars(Ferraris);
doc.checkCars(Bmws);
doc.checkCars(Porsches); // send a List< Porsche >
}
}

So what happens here?
javac CarMechanicGeneric.java
CarMechanicGeneric.java:51: checkCars(java.util.
ArrayList< Car >) in CarMechanicGeneric cannot be applied to
(java.util.List< Ferrari >)
doc.checkCars(Ferraris);
^
CarMechanicGeneric.java:52: checkCars(java.util.
ArrayList< Car >) in CarMechanicGeneric cannot be applied to
(java.util.List< Bmw >)
doc.checkCars(Bmws);
^
CarMechanicGeneric.java:53: checkCars(java.util.
ArrayList< Car >) in CarMechanicGeneric cannot be applied to
(java.util.List< Porsche >)
doc.checkCars(Porsches);
^
3 errors

The compiler stops us with errors, not warnings. You CANNOT assign the individual ArrayLists of Car subtypes (< Ferrari >, < Bmw >, or < Porsche >) to an ArrayList of the supertype < Car >, which is the declared type of the argument.

This is one of the biggest problem areas for Java programmers who are so familiar with using polymorphism with arrays, where the same scenario (Car[] can refer to Ferrari[], Bmw[], or Porsche[]) works as you would expect. So we have two real issues:

1. Why doesn’t this work?
2. How do you get around it?

You’ll probably curse me or even start hating me if I said that there is no workaround and we are stuck for good with point 2. thankfully, I don't have to say it and we shall remain friends :)

Before I tell you the workaround, lets look at, why can’t you do it if it works for arrays? Why can’t you pass an ArrayList< Ferrari > into a method with an argument of ArrayList< Car >?
We’ll get there, but first let’s step way back for a minute and consider this perfectly legal scenario:

Car[] Cars = new Car[3];
Cars[0] = new Bmw();
Cars[1] = new Ferrari();

Part of the benefit of declaring an array using a more abstract supertype is that the array itself can hold objects of multiple subtypes of the supertype, and then you can manipulate the array assuming everything in it can respond to the Car interface (in other words, everything in the array can respond to method calls defined in the Car class). So here, we’re using polymorphism not for the object that the array reference points to, but rather what the array can actually HOLD—in this case, any subtype of Car. You can do the same thing with generics:

List< Car > Cars = new ArrayList< Car >();

Cars.add(new Bmw());
Cars.add(new Ferrari());

So this part works with both arrays and generic collections—we can add an instance of a subtype into an array or collection declared with a supertype. You can add Ferraris and Bmws to an Car array (Car[]) or an Car collection (ArrayList< Car >).

And with arrays, this applies to what happens within a method:
public void addCar(Car[] Cars) {
Cars[0] = new Ferrari();

}

So if this is true, and if you can put Ferraris into an ArrayList< Car >, then why can’t you use that same kind of method scenario? Why can’t you do this?
public void addCar(ArrayList< Car > Cars) {
Cars.add(new Ferrari());
}

Actually, you CAN do this under certain conditions. The code above WILL compile just fine IF what you pass into the method is also an ArrayList< Car >. This is the part where it differs from arrays, because in the array version, you COULD pass a Ferrari[] into the method that takes an Car[].

The ONLY thing you can pass to a method argument of ArrayList< Car > is an ArrayList< Car >
The question is still out there—why is this bad? And why is it bad for ArrayList but not arrays? Why can’t you pass an ArrayList< Ferrari > to an argument of ArrayList< Car >? Actually, the problem IS just as dangerous whether you’re using arrays or a generic collection. It’s just that the compiler and JVM behave differently for arrays vs. generic collections.

The reason it is dangerous to pass a collection (array or ArrayList) of a subtype into a method that takes a collection of a supertype, is because you might add something. And that means you might add the WRONG thing! This is probably really obvious, but just in case, let’s walk through some scenarios. The first one is simple:

public void doSomething() {
Ferrari[] Ferraris = {new Ferrari(), new Ferrari()};
addCar(Ferraris); // no problem, send the Ferrari[] to the method
}
public void addCar(Car[] Cars) {
Cars[0] = new Ferrari(); // ok, any Car subtype works
}

This is no problem. We passed a Ferrari[] into the method, and added a Ferrari to the array (which was allowed since the method parameter was type Car[], which can hold any Car subtype). But what if we changed the calling code to

public void doSomething() {
Bmw[] Bmws = {new Bmw(), new Bmw()};
addCar(Bmws); // no problem, send the Bmw[] to the method
}

and the original method stays the same:
public void addCar(Car[] Cars) {
Cars[0] = new Ferrari();
// Oops! We just put a Ferrari in a Bmw array!
}

The compiler thinks it is perfectly fine to add a Ferrari to an Car[] array, since a Ferrari can be assigned to an Car reference. The problem is, if you passed in an array of an Car subtype (Bmw, Ferrari, or Porsche), the compiler does not know that. The compiler does not realize that out on the heap somewhere is an array of type Bmw[], not Car[], and you’re about to try to add a Ferrari to it. To the compiler, you have passed in an array of type Car, so it has no way to recognize the problem.

This is exactly the scenario we’re trying to prevent, regardless of whether it’s an array or an ArrayList. The difference is, the compiler lets you get away with it for arrays, but not for generic collections.

The reason the compiler won’t let you pass an ArrayList< Ferrari > into a method that takes an ArrayList< Car >, is because within the method, that parameter is of type ArrayList< Car >, and that means you could put any kind of Car into it. There would be no way for the compiler to stop you from putting a Ferrari into a List that was originally declared as < Bmw >, but is now referenced from the < Car > parameter.

We still have two unanswered questions...how do you get around it and why does the compiler allow you to take that risk for arrays but not for ArrayList?

The reason you can get away with compiling this for arrays is because there is a runtime exception (ArrayStoreException) that will prevent you from putting the wrong type of object into an array. If you send a Ferrari array into the method that takes an Car array, and you add only Ferraris (including Ferrari subtypes, of course) into the array now referenced by Car, no problem. But if you DO try to add a Bmw to the object that is actually a Ferrari array, you’ll get the exception.

But there IS no equivalent exception for generics, because of type erasure! In other words, at runtime the JVM KNOWS the type of arrays, but does NOT know the type of a collection. All the generic type information is removed during compilation, so by the time it gets to the JVM, there is simply no way to recognize the disaster of putting a Bmw into an ArrayList< Ferrari > and vice versa.

So this actually IS legal code:
public void addCar(List< Car > Cars) {
Cars.add(new Ferrari());
// this is always legal, since Ferrari can
// be assigned to an Car reference
}
public static void main(String[] args) {
List< Car > Cars = new ArrayList< Car >();
Cars.add(new Ferrari());
Cars.add(new Ferrari());
CarMechanicGeneric doc = new CarMechanicGeneric();
doc.addCar(Cars); // OK, since Cars matches
// the method arg
}

As long as the only thing you pass to the addCars(List< Car >) is an ArrayList< Car >, the compiler is fine—knowing that any Car subtype you add will be valid. But if you try to invoke addCar() with an argument of any OTHER ArrayList type, the compiler will stop you, since at runtime the JVM would have no way to stop you from adding a Ferrari to what was created as a Bmw collection.
For example, this code that changes the generic type to < Ferrari >, but without changing the addCar() method, will NOT compile:

public void addCar(List< Car > Cars) {
Cars.add(new Ferrari()); // still OK as always
}
public static void main(String[] args) {
List< Ferrari > Cars = new ArrayList< Ferrari >();
Cars.add(new Ferrari());
Cars.add(new Ferrari());
CarMechanicGeneric doc = new CarMechanicGeneric();
doc.addCar(Cars); // This is where the compiler cries!
}

The compiler says something like:
javac CarMechanicGeneric.java
CarMechanicGeneric.java:49: addCar(java.util.List< Car >)
in CarMechanicGeneric cannot be applied to (java.util.
List< Ferrari >)
doc.addCar(Cars);
^
1 error

Notice that this message is virtually the same one you’d get trying to invoke any method with the wrong argument. It’s saying that you simply cannot invoke addCar(List< Car >) using something whose reference was declared as List< Ferrari >. (It’s the reference type, not the actual object type that matters—but remember—the generic type of an object is ALWAYS the same as the generic type declared on the reference. List< Ferrari > can refer ONLY to collections that are subtypes of List, but which were instantiated as generic type < Ferrari >.)

Once again, remember that once inside the addCars() method, all that matters is the type of the parameter—in this case, List< Car >. (We changed it from ArrayList to List to keep our “base” type polymorphism cleaner.)

Back to the key question—how do we get around this? If the problem is related only to the danger of adding the wrong thing to the collection, what about the checkAndRepair() method that used the collection passed in as read-only? In other words, what about methods that invoke Car methods on each thing in the collection, which will work regardless of which kind of ArrayList subtype is passed in?

And that’s a clue! It’s the add() method that is the problem, so what we need is a way to tell the compiler, “I know what I am doing. I wont do anything stupid. So just chill and don't worry.” And there IS a mechanism to tell the compiler that you can take any generic subtype of the declared argument type because you won’t be putting anything in the collection. And that mechanism is the wildcard < ? >.

The method signature would change from
public void addCar(List< Car > Cars)

to
public void addCar(List< ? extends Car > Cars)

By saying < ? extends Car >, we’re saying, “I can be assigned a collection that is a subtype of List and typed for < Car > or anything that extends Car. And oh yes, I SWEAR that I will not ADD anything into the collection.”

So of course the addCar() method above won’t actually compile even with the wildcard notation, because that method DOES add something.

public void addCar(List Cars) {
Cars.add(new Ferrari());
// NO! Can't add if we use
}

You’ll get a very strange error that might look something like this:
javac CarMechanicGeneric.java
CarMechanicGeneric.java:38: cannot find symbol
symbol : method add(Ferrari)
location: interface java.util.List
Cars.add(new Ferrari());
^
1 error

which basically says, “you can’t add a Ferrari here.” If we change the method so that it doesn’t add anything, it works.

First, the < ? extends Car > means that you can take any subtype of Car; however, that subtype can be EITHER a subclass of a class (abstract or concrete) OR a type that implements the interface after the word extends. In other words, the keyword extends in the context of a wildcard represents BOTH subclasses and interface implementations. There is no < ? implements Serializable > syntax. If you want to declare a method that takes anything that is of a type that implements Serializable, you’d still use extends like this:

void doSomething(List< ? extends Serializable > list)
// odd, but ok to use "extends"

This looks strange since you would never say this in a class declaration because Serializable is an interface, not a class. But that’s the syntax, so burn it in!

One more time—there is only ONE wildcard keyword that represents both interface implementations and subclasses. And that keyword is extends. But when you see it, think “Is-a”, as in something that passes the instanceof test.

However, there is another scenario where you can use a wildcard AND still add to the collection, but in a safe way—the keyword super.

Imagine, for example, that you declared the method this way:

public void addCar(List Cars) {
Cars.add(new Ferrari()); // adding is sometimes OK with super
}
public static void main(String[] args) {
List< Car > Cars = new ArrayList< Car >();
Cars.add(new Ferrari());
Cars.add(new Ferrari());
CarMechanicGeneric doc = new CarMechanicGeneric();
doc.addCar(Cars); // passing an Car List
}

Now what you’ve said in this line
public void addCar(List< ? super Ferrari > Cars)

is essentially, “Hey compiler, you can accept any List with a generic type that is of type Ferrari, or a supertype of Ferrari.”

You probably already know why this works. If you pass in a list of type Car, then it’s perfectly fine to add a Ferrari to it. If you pass in a list of type Ferrari, it’s perfectly fine to add a Ferrari to it. And if you pass in a list of type Object, it’s STILL fine to add a Ferrari to it. When you use the syntax, you are telling the compiler that you can accept the type on the right-hand side of super or any of its supertypes, since—and this is the key part that makes it work—a collection declared as any supertype of Ferrari will be able to accept a Ferrari as an element. List< Object > can take a Ferrari. List< Car > can take a Ferrari. And List< Ferrari > can take a Ferrari. So passing any of those in will work. So the super keyword in wildcard notation lets you have a restricted, but still possible way to add to a collection.

So, the wildcard gives you polymorphic assignments, but with certain restrictions that you don’t have for arrays. Quick question: are these two identical?

public void doSomething(List< ? > list) { }
public void doSomething(List< Object > list) { }

If there IS a difference (And I am not saying whether there is or isn’t) what is it?
There IS a huge difference. List< ? >, which is the wildcard < ? > without the keywords extends or super, simply means “any type.” So that means any type of List can be assigned to the argument. That could be a List of < Ferrari >, < Integer >, < JButton >, whatever. And using the wildcard alone, without the keyword super (followed by a type), means that you cannot ADD anything to the list referred to as List< ? >.

List< Object > is completely different from List< ? >. List< Object > means that the method can take ONLY a List< Object >. Not a List< Ferrari >, or a List< Bmw >. It does, however, mean that you can add to the list, since the compiler has already made certain that you’re passing only a valid List< Object > into the method.

Based on the previous explanations, figure out if the following will work:

import java.util.*;
public class TestWildCardGenerics {
public static void main(String[] args) {
List< Integer > myList = new ArrayList< Integer >();
Bar bar = new Bar();
bar.doSomeInserts(myList);
}
}
class Bar {
void doSomeInserts(List< ? > list) {
list.add(new Ferrari());
}
}

If not, where is the problem?

The problem is in the list.add() method within doSomeInserts(). The < ? > wildcard allows a list of ANY type to be passed to the method, but the add() method is not valid, for the reasons we explored earlier (that you could put the wrong kind of thing into the collection). So this time, the TestWildCardGenerics class is fine, but the Bar class won’t compile because it does an add() in a method that uses a wildcard (without super). What if we change the doSomeInserts() method to this:

public class TestWildCardGenerics {
public static void main(String[] args) {
List< Integer > myList = new ArrayList< Integer >();
Bar bar = new Bar();
bar.doSomeInserts(myList);
}
}
class Bar {
void doSomeInserts(List< Object > list) {
list.add(new Ferrari());
}
}

Now will it work? If not, why not?

This time, class Bar, with the doSomeInserts() method, compiles just fine. The problem is that the TestWildCardGenerics code is trying to pass a List< Integer > into a method that can take ONLY a List< Object >. And nothing else can be substituted for < Object >.

By the way, List and List< ? > are absolutely identical! They both say, “I can refer to any type of object.” But as you can see, neither of them are the same as List< Object >. One way to remember this is that if you see the wildcard notation (a question mark ?), this means “many possibilities”. If you do NOT see the question mark, then it means the < type > in the brackets, and absolutely NOTHING ELSE. List< Ferrari > means List< Ferrari > and not List, List, or any other subtype of Ferrari. But List could mean List, List, and so on. Of course List< ? > could be... anything at all.
Keep in mind that the wildcards can be used only for reference declarations (including arguments, variables, return types, and so on). They can’t be used as the type parameter when you create a new typed collection. Think about that—while a reference can be abstract and polymorphic, the actual object created must be of a specific type. You have to lock down the type when you make the object using new.

Generic Declarations

We’ve talked about how to create type safe collections, and how to declare reference variables including arguments and return types using generic syntax.

But here are a few questions:
a. How do we even know that we’re allowed/ supposed to specify a type for these collection classes?
b. And does generic typing work with any other classes in the API?
c. And finally, can we declare our own classes as generic types? In other words, can we make a class that requires that someone pass a type in when they declare it and instantiate it?
First, the one you obviously know the answer to—the API tells you when a parameterized type is expected.
For example, this is the API declaration for the java.util.List interface:
public interface List< E >

The < E > is a placeholder for the type you pass in. The List interface is behaving as a generic “template”, and when you write your code, you change it from a generic List to a List< Car > or List< Integer >, and so on.

The E, by the way, is only a convention. Any valid Java identifier would work here, but E stands for “Element,” and it’s used when the template is a collection. The other main convention is T (stands for “type”), used for, well, things that are NOT collections.

Now that you’ve seen the interface declaration for List, what do you think the add() method looks like?
boolean add(E o)

In other words, whatever E is when you declare the List, that’s what you can add to it. So imagine this code:
List< Car > list = new ArrayList< Car >();

The E in the List API suddenly has its waveform collapsed, and goes from the abstract , to a List of Cars. And if it’s a List of Cars, then the add() method of List must obviously behave like this:

boolean add(Car a)

When you look at an API for a generics class or interface, pick a type parameter (Car, JButton, even Object) and do a mental find and replace on each instance of E (or whatever identifier is used as the placeholder for the type parameter).

Creating Generic Methods

Until now, every example we’ve seen uses the class parameter type—the type declared with the class name.

Imagine you want to create a method that takes an instance of any type, instantiates an ArrayList of that type, and adds the instance to the ArrayList. The class itself doesn’t need to be generic; basically we just want a utility method that we can pass a type to and that can use that type to construct a type safe collection. Using a generic method, we can declare the method without a specific type and then get the type information based on the type of the object passed to the method. For example:

import java.util.*;
public class CreateGenericList {
public < T > void makeArrayList(T t) {
List< T > list = new ArrayList< T >();
list.add(t);
}
}

In the preceding code, if you invoke the makeArrayList() method with a Car instance, the method will behave as though it looked like this all along:
public void makeArrayList(Car t) {
List< Car > list = new ArrayList< Car >();
list.add(t);
}

And of course if you invoke the method with an Integer, then the T is replaced by Integer.
The strangest thing about generic methods is that you must declare the type variable BEFORE the return type of the method:
public < T > void makeArrayList(T t)

The < T > before void simply defines what T is before you use it as a type in the argument. You MUST declare the type like that unless the type is specified for the class. In CreateGenericList, the class is not generic, so there’s no type parameter placeholder we can use.

You’re also free to put boundaries on the type you declare, for example, if you want to restrict the makeArrayList() method to only Number or its subtypes (Integer, Float, and so on) you would say

public < T extends Number > void makeArrayList(T t)

98% of what you’re likely to do with generics is simply declare and use type safe collections, including using them as arguments. But now you know a lot more about the way generics works.

Exam Tip:
One of the most common mistakes programmers make when creating generic classes or methods is to use a < ? > in the wildcard syntax rather than a type variable < T >, < E >, and so on. This code might look right, but isn’t:
public class NumberHolder< ? extends Number > { }

While the question mark works when declaring a reference for a variable, it does NOT work for generic class and method declarations. This code is not legal:
public class NumberHolder< ? > { ? aNum; } // Wont Work!

But if you replace the < ? > with a legal identifier, you’re good:
public class NumberHolder< T > { T aNum; } // Fine!


Previous Chapter: Chapter 50 - Methods Overview for Collections

Next Chapter: Quick Recap - Chapters 38 to 51

2 comments:

  1. Thanks for your comment on my post 10 points on Java Generics. I see you have also covered the topic with great details. good work man.

    Thanks
    Javin

    ReplyDelete
  2. Awesome article though it is little too lengthy.

    ReplyDelete

© 2013 by www.inheritingjava.blogspot.com. All rights reserved. No part of this blog or its contents may be reproduced or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without prior written permission of the Author.

ShareThis

Google+ Followers

Followers