Certification Objective Generic Types (Objectives 6.3 and 6.4)


Certification Objective —Generic Types (Objectives 6.3 and 6.4)

6.3 Write code that uses the generic versions of the Collections API, in particular the Set, List, and Map interfaces and implementation classes. Recognize the limitations of the non-generic Collections API and how to refactor code to use the generic versions.

6.4 Develop code that makes proper use of type parameters in class/interface declarations, instance variables, method arguments, and return types; and write generic methods or methods that make use of wildcard types and understand the similarities and differences between these two approaches.

Arrays in Java have always been type safe—an array declared as type String (string[]) can't accept Integers (or ints), Dogs, or anything other than Strings. But remember that before Java 5 there was no syntax for declaring a type safe collection. To make an ArrayList of Strings, you said,

 ArrayList myList = new ArrayList(); 

or, the polymorphic equivalent:

 List myList = new ArrayList(); 

There was no syntax that let you specify that myList will take Strings and only Strings. And with no way to specify a type for the ArrayList, the compiler couldn't enforce that you put only things of the specified type into the list. As of Java 5, we can use generics, and while they aren't only for making type safe collections, that's just about all most developers use generics for. So, while generics aren't just for collections, think of collections as the overwhelming reason and motivation for adding generics to the language.

And it was not an easy decision, nor has it been an entirely welcome addition. Because along with all the nice happy type safety, generics come with a lot of baggage—most of which you'll never see or care about, but there are some gotchas that come up surprisingly quickly. We'll cover the ones most likely to show up in your own code, and those are also the issues that you'll need to know for the exam.

The biggest challenge for Sun in adding generics to the language (and the main reason it took them so long) was how to deal with legacy code built without generics. Sun's Java engineers obviously didn't want to break everyone's existing Java code, so they had to find a way for Java classes with both type safe (generic) and non-type safe (non-generic/pre-Java 5) collections to still work together. Their solution isn't the friendliest, but it does let you use older non-generic code, as well as use generic code that plays with non-generic code. But notice we said "plays," and not "plays WELL."

While you can integrate Java 5 generic code with legacy non-generic code, the consequences can be disastrous, and unfortunately, most of the disasters happen at runtime, not compile time. Fortunately, though, most compilers will generate warnings to tell you when you're using unsafe (meaning non-generic) collections.

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. And like some of the other topics in this book, you could fill an entire book if you really wanted to cover every detail about generics, but the exam (and this book) covers more than most developers will ever need to use.

The Legacy Way to Do 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("Fred");              // OK, it will hold Strings myList.add(new Dog());           // and it will hold Dogs 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 meant it was entirely up to the programmer to becareful. 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 Dog reference, but with collections, it was, "Come on in! The door is always open! 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("Fred");        // OK, it will hold Strings myList.add(new Dog());     // 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 "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("foo");  // 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!! strings is type safe } 

Return types can obviously be declared type safe as well:

 public Set<Dog> getDogList()     Set<Dog> dogs = new HashSet<Dog>();     //  more code to insert dogs     return dogs; } 

The compiler will stop you from returning anything not compatible with a Set<Dog> (although what is and is not compatible is going to get very interesting in a minute). And since the compiler guarantees that only a type safe Dog Set is returned, those calling the method won't need a cast to take Dogs from the Set:

 Dog d = getDogList().get(0);   // we KNOW a Dog is coming out 

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

 public Set getDogList() {     Set dogs = new HashSet();      // code to add only Dogs... fingers crossed...     return dogs; // a Set of ANYTHING will work here } 

and the caller would need a cast:

 Dog d = (Dog) getDogList().get(0) 

(The cast in this example applies to what comes from the Set's get() method; we aren't casting what is returned from the getDogList() method, which is a Set.)

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>(); // holds ANY object type 

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.

Oh, if only this were the end of the storybut there are still a few tricky issues with methods arguments, polymorphism, and integrating generic and non-generic code, so we're just getting warmed up here.

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 no longer needed,                                    // but it won't hurt 

Mixing Generic and Non-Generic Collections

Now here's where it starts to get interestingimagine 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 TestLegacy {     public static void main(String[] args) {         List<Integer> myList = new ArrayList<Iriteger>();                                      // type safe collection         myList.add(4);         myList.add(6);         Adder adder = new Adder();         int total = adder.addAll(myList) ;                               // pass it to an untyped argument         System.out.println(total);       } } 

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

 import Java.util.*; class Adder {    int addAll(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 addAll() legacy method assumed (trusted? 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 addAll() 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 blown up if the list passed in contained anything but 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 TestBadLegacy {     public static void main(String[] args) {         List<Integer> myList = new ArrayList<Integer>();         myList.add(4);         myList.add(6);         Inserter in  = new Inserter();         in.insert(myList);  // pass List<Integer> to legacy code       } } class Inserter {      // 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.

Butwhat 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 allows your newer type safe code to make use of older code (the last thing Sun wanted to do was ask several million Java developers to modify all their existing code).

So, the Java 5 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, big risk sending your nice protected ArrayList<1nteger> into a dangerous method that can have its way with your list and put in Floats, Strings, or even Dogs.

When you called the addAll() 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 TestBadLegacy.java Note: TestBadLegacy.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 kind enough to tell you by saying, in so many words, "I seriously hope you know what you are doing because this old code has NO respect (or even knowledge) of your <Integer> typing, and can do whatever the heck it wants to your precious ArrayList< Integer>."

image from book
Exam Watch

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.

image from book

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.

image from book
Exam Watch

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.

One more time—if you see code that you know will compile with warnings, you must NOT choose "Compilation fails." as an answer. 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."

image from book

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 harmed 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 verifications 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? 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 protectionuntil 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 TestBadLegacy.java Note: TestBadLegacy.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 TestBadLegacy.java TestBadLegacy.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 runsor at least the code that actually adds the String to the list runs. But when you take the String-that-wasn't-supposed-to-be-there out of the list, and try to assign it to an Integer reference or invoke an Integer method, you're dead.

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 lulled 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, your protection vanishes.

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

image from book
Exam Watch

When using legacy (non-type safe) collections—watch out for unboxing problems! If you declare a non-generic collection, the get() methodALWAYS returns a reference of type java.lang.Object. Remember that unboxing can't convert a plain old Object to a primitive, even if that Object reference points to an Integer (or some other primitive) on the heap. Unboxing converts only from a wrapper class reference (like an Integer or a Long) to a primitive.

image from book

image from book
Exam Watch

Unboxing gotcha, continued:

 List test = new ArrayList() ; test.add(43); int x = (Integer)test.get(0);    // you must cast !! List<Integer> test2 = new ArrayList<Integer>(); test2.add(343); int x2 = test2.get(0);           //   cast not necessary 

Watch out for missing casts associated with pre-Java 5, non-generic collections.

image from book

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 bite 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 Parent { } class Child extends Parent { } List<Parent> myList = new ArrayList<Child>(); 

Think about it for a minute.
Keep thinking

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<Foo> foo then whatever you assign to the foo reference MUST be of the generic type <Foo>. Not a subtype of <Foo>. Not a supertype of <Foo>.Just <Foo>.

These are wrong:

 List<object> myList = new ArrayList<JButton>();  // NO! List<Number> numbers = new ArrayList<Integer>(); // NO! // remember that Integer is a subtype of Number 

But these are fine:

 List<JButton> myList = new ArrayList<JButton>(); // yes List<Object> myList = new ArrayList<Object>();   // yes List<Integer> myList = new ArrayList<Integer>(); // yes 

So far so good. Just keep the generic type of the reference and the generic type of the object to which it refers identical. In other words, polymorphism applies here to only the "base" type. And by "base," we mean the type of the collection class itself—the class that can be customized with a type. In this code,

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

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 Parent { } class Child extends Parent { } public class TestPoly {     public static void main(String[] args) {         Parent[] myArray = new Child[3];  // yes     } } 

which means you're also allowed to do this

 Object[] myArray = new JButton[3];  // yes 

but not this:

 List<Object> list = new ArrayList<JButton>();  // NO! 

Why are the rules for typing of arrays different from the rules for generic typing? We'll get to that in a minute. For now, just burn it into your brain 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 classic (simplified) polymorphism example of a veterinarian (AnimalDoctor) class with a method checkup(). And right now, you have three Animal subtypes—Dog, Cat, and Bird—each implementing the abstract checkup() method from Animal:

 abstract class Animal {     public abstract void checkup(); } class Dog extends Animal {     public void checkup() {   // implement Dog-specific code       System.out.println("Dog checkup");     } } class Cat extends Animal {     public void checkup() {   // implement Cat-specific code       System.out.println("Cat checkup");     } } class Bird extends Animal {     public void checkup() {   // implement Bird-specific code       System.out.println("Bird checkup"); }   } 

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

So in the AnimalDoctor class, you'd probably have a polymorphic method:

 public void checkAnimal(Animal a) {      a.checkup(); // does not matter which animal subtype each                   // Animal's overridden checkup() method runs } 

And of course we do want the AnimalDoctor to also have code that can take arrays of Dogs, Cats, or Birds, for when the vet comes to the dog, cat, or bird kennel. Again, we don't want overloaded methods with arrays for each potential Animal subtype, so we use polymorphism in the AnimalDoctor class:

 public void checkAnimals(Animal[] animals) {     for(Animal a : animals) {       a.checkup();     } } 

Here is the entire example, complete with a test of the array polymorphism that takes any type of animal array (Dog[], Cat[], Bird[]).

 import java.util.*; abstract class Animal {      public abstract void checkup(); } class Dog extends Animal {     public void checkup() {   // implement Dog-specific code       System.out.println("Dog checkup");     } } class Cat extends Animal {     public void checkup() {    // implement Cat-specific code        System.out.println("Cat checkup");     } } class Bird extends Animal {    public void checkup() {    // implement Bird-specific code       System.out.println("Bird checkup");    } } public class AnimalDoctor  {     // method takes an array of any animal subtype     public void checkAnimals(Animal[] animals) {       for(Animal a : animals) {         a.checkup();       }     }     public static void main(String[] args)    {         //   test it        Dog[] dogs = (new Dog(), new Dog()};        Cat[] cats = (new Cat(), new Cat(), new Cat());        Bird[] birds = (new Bird());        AnimalDoctor doc = new AnimalDoctor();        doc.checkAnimals(dogs);  //  pass the Dog[]        doc.checkAnimals(cats);  //  pass the Cat[]        doc.checkAnimals(birds); //  pass the Bird[]     } } 

This works fine, of course (we know, we know, this is old news). 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<Animal> will NOT be able to accept a collection of any Animal Subtype! That means ArrayList<Dog> cannot be passed into a method with an argument of ArrayList<Animal>, 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 AnimalDoctor code to use generics instead of arrays:

 public class AnimalDoctorGeneric  {     // change the argument from Animal[] to ArrayList<Animal>     public void checkAnimals(ArrayList<Animal> animals) {       for(Animal a : animals) {         a.checkup();       }     }     public static void main(String[] args) {        // make ArrayLists instead of arrays for Dog, Cat, Bird        List<Dog> dogs = new ArrayList<Dog>();        dogs.add(new Dog());        dogs.add(new Dog());        List<Cat> cats = new ArrayList<Cat>();        cats.add(new Cat());        cats.add(new Cat());        List<Bird> birds = new ArrayList<Bird>();        birds.add(new Bird());        // this code is the same as the Array version        AnimalDoctorGeneric doc = new AnimalDoctorGeneric();        // this worked when we used arrays instead of ArrayLists        doc.checkAnimals(dogs);  // send a List<Dog>        doc.checkAnimals(cats);  // send a List<Cat>        doc.checkAnimals(birds); // send a List<Bird>     } } 

So what does happen?

 javac AnimalDoctorGeneric.Java AnimalDoctorGeneric.Java:51: checkAnimals(Java.util. ArrayList<Animal>) in AnimalDoctorGeneric cannot be applied to (java.util.List<Dog>)        doc.checkAnimals(dogs);           ^ AnimalDoctorGeneric.Java: 52: checkAnimals(java.util. ArrayList<Animal>) in AnimalDoctorGeneric cannot be applied to (java.util.List<Cat>)        doc.checkAnimals(cats);           ^ AnimalDoctorGeneric.Java:53: checkAnimals(java.util. ArrayList<Animal>) in AnimalDoctorGeneric cannot be applied to (java.util.List<Bird>)        doc.checkAnimals(birds);           ^ 3 errors 

The compiler stops us with errors, not warnings. You simply CANNOT assign the individual ArrayLists of Animal subtypes (<Dog>, <Cat>, or <Bird>) to an ArrayList of the supertype <Animal>, which is the declared type of the argument.

This is one of the biggest gotchas for Java programmers who are so familiar with using polymorphism with arrays, where the same scenario (Animal[] can refer to Dog[], Cat[], or Bird[]) 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'd hate us and all of the Sun engineers if we told you that there wasn't a way around it—that you had to accept it and write horribly inflexible code that tried to anticipate and code overloaded methods for each specific <type>. Fortunately, there is a way around it.

But first, why can't you do it if it works for arrays? Why can't you pass an ArrayList<Dog> into a method with an argument of ArrayList<Animal>?

We'll get there, but first let's step way back for a minute and consider this perfectly legal scenario:

 Animal[] animals = new Animal[3]; animals[0] = new Cat(); animals[1] = new Dog(); 

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 Animal interface (in other words, everything in the array can respond to method calls defined in the Animal 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 Animal. You can do the same thing with generics:

 List<Animal> animals = new ArrayList<Animal>(); animals.add(new Cat());  // OK animals.add(new Dog());  // OK 

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 Dogs and Cats to an Animal array (Animal[]) or an Animal collection (ArrayList<Animal>).

And with arrays, this applies to what happens within a method:

 public void addAnimal(Animal[] animals) {       animals[0] = new Dog();  // no problem, any Animal works                                // in Animal [] } 

So if this is true, and if you can put Dogs into an ArrayList<Animal>, then why can't you use that same kind of method scenario? Why can't you do this?

 public void addAnimal(ArrayList<Animal> animals) {      animals.add(new Dog()); // sometimes allowed... } 

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<Animal>. This is the part where it differs from arrays, because in the array version, you COULD pass a Dog[] into the method that takes an Animal[].

The ONLY thing you can pass to a method argument of ArrayList<Animal> is an ArrayList<Animal>! (Assuming you aren't trying to pass a subtype of ArrayList, since remember—the "base" type can be polymorphic.)

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<Dog> to an argument of ArrayList<Animal>? 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 (and to reinforce), let's walk through some scenarios. The first one is simple:

 public void foo() {   Dog[] dogs = {new Dog(), new Dog()};   addAnimal(dogs);  // no problem, send the Dog[] to the method } public void addAnimal(Animal[] animals) {   animals[0] = new Dog();  // ok, any Animal subtype works } 

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

 public void foo() {   Cat[] cats = {new Cat(), new Cat()};   addAnimal(cats);  // no problem, send the Cat[] to the method } 

and the original method stays the same:

 public void addAnimal(Animal[] animals) {    animals[0] = new Dog();  // Eeek! We just put a Dog                             // in a Cat array! } 

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

THIS is 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<Dog> into a method that takes an ArrayList<Animal>, is because within the method, that parameter is of type ArrayList<Animal>, and that means you could put any kind of Animal into it. There would be no way for the compiler to stop you from putting a Dog into a List that was originally declared as <Cat>, but is now referenced from the <Animal> parameter.

We still have two questionshow do you get around it and why the heck does the compiler allow you to take that risk for arrays but not for ArrayList (or any other generic collection)?

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 Dog array into the method that takes an Animal array, and you add only Dogs (including Dog subtypes, of course) into the array now referenced by Animal, no problem. But if you DO try to add a Cat to the object that is actually a Dog 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 Cat into an ArrayList<Dog> and vice versa (and it becomes exactly like the problems you have when you use legacy, non-type safe code).

So this actually IS legal code:

 public void addAnimal(List<Animal> animals) {     animals.add(new Dog());  // this is always legal,                              // since Dog can                              // be assigned to an Animal                              // reference  }   public static void main(String[] args) {      List<Animal> animals = new ArrayList<Animal>();      animals.add(new Dog());      animals.add(new Dog());      AnimalDoctorGeneric doc = new AnimalDoctorGeneric();      doc.addAnimal(animals); // OK, since animals matches                              // the method arg   } 

As long as the only thing you pass to the addAnimals (List<Animal>) is an ArrayList<Animal>, the compiler is pleased—knowing that any Animal subtype you add will be valid (you can always add a Dog to an Animal collection, yada, yada, yada). But if you try to invoke addAnimal() 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 Dog to what was created as a Cat collection.

For example, this code that changes the generic type to <Dog>, but without changing the addAnimal() method, will NOT compile:

 public void addAnimal(List<Animal> animals) {   animals.add(new Dog());  // still OK as always } public static void main(String[] args) {   List<Dog> animals = new ArrayList<Dog>();   animals.add(new Dog());   animals.add(new Dog());   AnimalDoctorGeneric doc = new AnimalDoctorGeneric();   doc.addAnimal(animals); // THIS is where it breaks! } 

The compiler says something like:

 javac AnimalDoctor.Generic.java AnimalDoctorGeneric.java:49: addAnimal(java.util.List<Animal>) in AnimalDoctorGeneric cannot be applied to (java.util. List<Dog>)        doc.addAriimal (animals) ;           ^ 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 addAnimal(List<Animal>) using something whose reference was declared as List<Dog>. (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<Dog> can refer ONLY to collections that are subtypes of List, but which were instantiated as generic type <Dog>.)

Once again, remember that once inside the addAnimals() method, all that matters is the type of the parameter—in this case, List<Animal>. (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 checkup() method that used the collection passed in as read-only? In other words, what about methods that invoke Animal 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, "Hey, I'm using the collection passed in just to invoke methods on the elements—and I promise not to ADD anything into the collection." 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 addAnimal(List<Animal> animals) 

to

 public void addAnimal(List<? extends Animal> animals) 

By saying <? extends Animal>, we're saying, "I can be assigned a collection that is a subtype of List and typed for <Animal> or anything that extends Animal. And oh yes, ISWEAR that I will not ADD anything into the collection." (There's a little more to the story, but we'll get there.)

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

 public void addAnimal(List<? extends Animal> animals) {       animals.add(new Dog());  // NO! Can't add if we                                // use <? extends Animal> } 

You'll get a very strange error that might look something like this:

 javac AnimalDoctorGeneric.java AnimalDoctorGeneric.java:38: cannot find symbol symbol  : method add(Dog) location: interface java.util.List<capture of ? extends Animal>       animals.add(new Dog());              ^ 1 error 

which basically says, "you can't add a Dog here." If we change the method so that it doesn't add anything, it works.

But wait—there's more. (And by the way, everything we've covered in this generics section is likely to be tested for on the exam, with the exception of "type erasure," for which you aren't required to know any details.)

First, the <? extends Animal> means that you can take any subtype of Animal; 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 foo(List<? extends Serializable> list) // odd, but correct                                             // 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 addAnimal(List<? super Dog> animals) {   animals.add(new Dog());  // adding is sometimes OK with super } public static void main(String[] args) {   List<Animal> animals = new ArrayList<Animal>();   animals.add(new Dog());   animals.add(new Dog());   AnimalDoctorGeneric doc = new AnimalDoctorGeneric();   doc.addAnimal(animals); // passing an Animal List } 

Now what you've said in this line

 public void addAnimal(List<? super Dog> animals) 

is essentially, "Hey compiler, please accept any List with a generic type that is of type Dog, or a supertype of Dog. Nothing lower in the inheritance tree can come in, but anything higher than Dog is OK."

You probably already recognize why this works. If you pass in a list of type Animal, then it's perfectly fine to add a Dog to it. If you pass in a list of type Dog, it's perfectly fine to add a Dog to it. And if you pass in a list of type Object, it's STILL fine to add a Dog to it. When you use the <? super > 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 Dog will be able to accept a Dog as an element. List<Object> can take a Dog. List<Animal> can take a Dog. And List<Dog> can take a Dog. 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 foo(List<?> list) { } public void foo(List<Object> list) { } 

If there IS a difference (and we're not yet saying there is), 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 <Dog>, <Integer>, <JButton>, <Socket>, 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<Dog>, or a List<Cat>. 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 TestWildcards {   public static void main(String[] args) {      List<Integer> myList = new ArrayList<Integer>();      Bar bar = new Bar();      bar.doInsert(myList);   } } class Bar {   void doInsert(List<?> list) {      list.add(new Dog());   } } 

If not, where is the problem?

The problem is in the list.add() method within doInsert(). 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 TestWildcards 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 doInsert() method to this:

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

Now will it work? If not, why not?

This time, class Bar, with the doInsert() method, compiles just fine. The problem is that the TestWildcards 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<? extends object> 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<Dog> means List<Dog> and not List<Beagle>, List<Poodle>, or any other subtype of Dog. But List<? extends Dog> could mean List<Beagle>, List<Poodle>, 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.

As a little review before we move on with generics, look at the following statements and figure out which will compile:

 1) List<?> list = new ArrayList<Dog>(); 2) List<? extends Animal> aList = new ArrayList<Dog>(); 3) List<?> foo = new ArrayList<? extends Animal>(); 4) List<? extends Dog> cList = new ArrayList<Integer>(); 5) List<? super Dog> bList = new ArrayList<Animal>(); 6) List<? super Animal> dList = new ArrayList<Dog>(); 

The correct answers (the statements that compile) are 1, 2, and 5. The three that won't compile are

  • Statement: List<?> foo = new ArrayList<? extends Animal>();

    Problem: you cannot use wildcard notation in the object creation. So the new ArrayList<? extends Animal>() will not compile.

  • Statement: List<? extends Dog> cList = new ArrayList<Integer>();

    Problem: You cannot assign an Integer list to a reference that takes only a Dog (including any subtypes of Dog, of course).

  • Statement: List<? super Animal> dList = new ArrayList<Dog>();

    Problem: You cannot assign a Dog to <? super Animal>. The Dog is too "low" in the class hierarchy. Only <Animal> or <Object> would have been legal.

Generic Declarations

Until now, 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: How do we even know that we're allowed/ supposed to specify a type for these collection classes? And does generic typing work with any other classes in the API? 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" (sort of like C++ templates), and when you write your code, you change it from a generic List to a List<Dog> 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<Animal> list = new ArrayList<Animal>(); 

The E in the List API suddenly has its waveform collapsed, and goes from the abstract <your type goes here>, to a List of Animals. And if it's a List of Animals, then the add () method of List must obviously behave like this:

 boolean add(Animal a) 

When you look at an API for a generics class or interface, pick a type parameter (Dog, 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).

Making Your Own Generic Class

Let's try making our own generic class, to get a feel for how it works, and then we'll look at a few remaining generics syntax details. Imagine someone created a class Rental, that manages a pool of rentable items.

 public class Rental {    private List rentalPool;    private int maxNum;    public Rental(int maxNum, List rentalPool) {       this.maxNum = maxNum;       this.rentalPool = rentalPool;    }    public Object getRental() {       // blocks until there's something available       return rentalPool.get(0);    }    public void returnRental(Object o) {       rentalPool.add(o);    } } 

Now imagine you wanted to make a subclass of Rental that was just for renting cars. You might start with something like this:

 import java.util.* ; public class CarRental extends Rental {    public CarRental(int maxNum, List<Car> rentalPool) {       super(maxNum, rentalPool);    }    public Car getRental() {       return (Car) super.getRental();    }    public void returnRental(Car c) {       super.returnRental(c);    }    public void returnRental(Object o) {      if (o instanceof Car) {        super.returnRental(o);      } else {         System.out.println("Cannot add a non-Car");         // probably throw an exception }  } } 

But then the more you look at it, the more you realize:

  1. You are doing your own type checking in the returnRental() method. You can't change the argument type of returnRental() to take a Car, since it's an override (not an overload) of the method from class Rental. (Overloading would take away your polymorphic flexibility with Rental).

  2. You really don't want to make separate subclasses for every possible kind of rentable thing (cars, computers, bowling shoes, children, and so on).

But given your natural brilliance (heightened by this contrived scenario), you quickly realize that you can make the Rental class a generic type—a template for any kind of Rentable thing—and you're good to go.

(We did say contrivedsince in reality, you might very well want to have different behaviors for different kinds of rentable things, but even that could be solved cleanly through some kind of behavior composition as opposed to inheritance (using the Strategy design pattern, for example). And no, design patterns aren't on the exam, but we still think you should read our design patterns book. Think of the kittens.) So here's your new and improved generic Rental class:

 import java.util.*; public class RentalGeneric<T> {  // "T" is for the type                                  // parameter    private List<T> rentalPool;   // Use the class type for the                                  // List type    private int maxNum;    public RentalGeneric(      int maxNum, List<T> rentalPool) { // constructor takes a                                        // List of the class type      this.maxNum = maxNum;      this.rentalPool = rentalPool;    }    public T getRental() {                    // we rent out a T      // blocks until there's something available      return rentalPool.get(0);    }    public void returnRental(T returnedThing) { // and the renter                                                // returns a T      rentalPool.add(returnedThing);    } } 

Let's put it to the test:

 class TestRental {   public static void main (String[] args) {      //make some Cars for the pool      Car cl = new Car();      Car c2 = new Car();      List<Car> carList = new ArrayList<Car>();      carList.add(cl);      carList.add(c2);      RentalGeneric<Car> carRental = new                          RentalGeneric<Car>(2, carList);      // now get a car out, and it won't need a cast      Car carToRent = carRental.getRental();      carRental.returnRental(carToRent) ;      // can we stick something else in the original carList?      carList.add(new Cat("Fluffy"));   } } 

We get one error:

 kathy% javacl.5 RentalGeneric.java RentalGeneric.Java:38: cannot find symbol symbol  : method add(Cat) location: interface java.util.List<Car>      carList.add(new Cat("Fluffy"));             ^ 1 error 

Now we have a Rental class that can be typed to whatever the programmer chooses, and the compiler will enforce it. In other words, it works just as the Collections classes do. Let's look at more examples of generic syntax you might find in the API or source code. Here's another simple class that uses the parameterized type of the class in several ways:

 public class TestGenerics<T> {  // as the class type    T anInstance;                // as an instance variable type    T [] anArrayOfTs;            // as an array type    TestGenerics(T anInstance) {     // as an argument type       this.anInstance = anInstance;    }    T getT{} {                       // as a return type       return anInstance;    } } 

Obviously this is a ridiculous use of generics, and in fact you'll see generics only rarely outside of collections. But, you do need to understand the different kinds of generic syntax you might encounter, so we'll continue with these examples until we've covered them all.

You can use more than one parameterized type in a single class definition:

 public class UseTwo<T, X> {   T one;   X two;   UseTwo(T one, X two) {     this.one = one;     this.two = two;   }   T getT() { return one; }   X getX() { return two; } // test it by creating it with <String, Integer>   public static void main (String[] args) {     UseTwo<String, Integer> twos =                     new UseTwo<String, Integer> ("foo", 42);     String theT = twos.getT(); // returns a String     int theX = twos.getX{};   // returns Integer, unboxes to int   } } 

And you can use a form of wildcard notation in a class definition, to specify a range (called "bounds") for the type that can be used for the type parameter:

 public class AnimalHolder<T extends Animal> { // use "T" instead                                               // of "?"  T animal;  public static void main(String[] args) {    AnimalHolder<Dog> dogHolder = new AnimalHolder<Dog>(); // OK    AnimalHolder<Integer> x = new AnimalHolder<Integer>(); // NO!  } } 

Creating Generic Methods

Until now, every example we've seen uses the class parameter type—the type declared with the class name. For example, in the UseTwo<T,X> declaration, we used the T and X placeholders throughout the code. But it's possible to define a parameterized type at a more granular level—a method.

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.uti1.*; public class CreateAnArrayList {   public <T> void makeArrayList(T t) { // take an object of an                                     // unknown type and use a                                     // "T" to represent the type     List<T> list = new ArrayList<T>(); // now we can create the                                        // list using "T"     list.add(t);    } } 

In the preceding code, if you invoke the makeArrayList() method with a Dog instance, the method will behave as though it looked like this all along:

 public void makeArrayList(Dog t) {   List<Dog> list = new ArrayList<Dog>();   list.add(t); } 

And of course if you invoke the method with an Integer, then the T is replaced by Integer (not in the bytecode, remember—we're describing how it appears to behave, not how it actually gets it done).

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 CreateAnArrayList, 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) 

image from book
Exam Watch

It's tempting to forget that the method argument is NOT where you declare the type parameter variable T. In order to use a type variable like T, you must have declared it either as the class parameter type or in the method, before the return type. The following might look right,

 public void makeList(T t) { } 

But the only way for this to be legal is if there is actually a class named T, in which case the argument is like any other type declaration for a variable. And what about constructor arguments? They, too, can be declared with a generic type, but then it looks even stranger since constructors have no return type at all:

 public class Radio {    public <T> Radio(T t) { }  // legal constructor } 

image from book

image from book
Exam Watch

If you REALLY want to get ridiculous (or fired), you can declare a class with a name that is the same as the type parameter placeholder:

 class X { public <X> X(X x) { } } 

Yes, this works.The X that is the constructor name has no relationship to the <X> type declaration, which has no relationship to the constructor argument identifier, which is also, of course, X. The compiler is able to parse this and treat each of the different uses of X independently. So there is no naming conflict between class names, type parameter placeholders, and variable identifiers.

image from book

image from book
Exam Watch

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; }    // NO! 

But if you replace the <?> with a legal identifier, you're good:

 public class NumberHolder<T> ( T aNum; }    // Yes 

image from book

98% of what you're likely to do with generics is simply declare and use type safe collections, including using (and passing) them as arguments. But now you know much more (but by no means everything) about the way generics works.

If this was clear and easy for you, that's excellent. If it waspainfuljust know that adding generics to the Java language very nearly caused a revolt among some of the most experienced Java developers. Most of the outspoken critics are simply unhappy with the complexity, or aren't convinced that gaining type safe collections is worth the ten million little rules you have to learn now. It's true that with Java 5, learning Java just got harder. But trust uswe've never seen it take more than two days to "get" generics. That's 48 consecutive hours.




SCJP Sun Certified Programmer for Java 5 Study Guide Exam 310-055
SCJP Sun Certified Programmer for Java 5 Study Guide (Exam 310-055) (Certification Press)
ISBN: 0072253606
EAN: 2147483647
Year: 2006
Pages: 131

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net