Generic Types – II


Polymorphism and Generics

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.

Generic Methods

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.

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();
                                             }
                                 }

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 Li st<Dog>
                              doc.checkAnimals(cats); // send a Li st<Cat>
                              doc.checkAnimals(bird s); // send a Li st<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);

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 can do the 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>).

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!

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, I SWEAR that I will not ADD anything into the collection.”

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. 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!

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.”

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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s