A Pretty Specific Generics Problem

So today I’m like coding and stuff (but you already knew that) when I ran into this conundrum with generics. Can you see why this doesn’t compile? Assume Car is the superclass of Ford and Toyota.

List<? extends Car> list = new ArrayList<Car>();
list.add(new Ford());
...
Compile error: cannot find symbol method add(Ford)

What??? Regroup. I have a List of anything that extends Car and I can’t put a Ford in it?  To see why this is actually correct, let’s go back to the basics. Consider:

Car myCar = new Ford();

Here we have a Car reference to a specific Car implementation, namely Ford. We can use this Ford object with the Car reference according to Car’s exposed interface. However, we can’t use that reference to access Ford-specific functionality. Now take another look at the list reference:

List<? extends Car> list = ...;

What this means is that list is a reference to a List of something that extends Car (or is Car), but we don’t really know what that something is. In other words, it could be a reference to a List<Toyota>, List<Ford>, or List<Car>. The only functionality available through the List<? extends Car> reference is the lowest common denominator for all such List objects – basically little more than iterating. Hence when we tried to do

list.add(new Ford());

this had to blow up – the target list could just as well have been a List<Toyota>, which would have been really bad.

—–

Ok, two questions now. First, how do I declare a List that can store any type of Car? Second, how is this unknown type genericising even useful?

The first one is really easy. All you have to do is declare it as a List of Cars:

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

Now the type is known – it’s Car, which includes all its subtypes like Toyota and Ford, and you can add a Ford instance to the list – that works!

The second question is a bit harder to answer because we first have to realize the limitations of the genericised List. Specifically, the following will not compile:

List<Car> list = new ArrayList<Ford>();
...
Compile error:
incompatible types found : java.util.ArrayList<Main.Ford> required: java.util.List<Main.Car>

Even though Car has the inheritance relationship with Ford and Toyota, List<Car> does not share that same relationship with List<Ford> and List<Toyota>. In other words, List<Car> is not a superclass of List<Ford> and List<Toyota> (d-uh) so it cannot reference them. If you want a generic reference to all of these lists, such as passing any of these lists to a method, you have to resort to the even more generic List<? extends Car> variant:

public void doStuffWithListOfSomeCars(List<? extends Car> theList) // accepts List<Car>, List<Toyota> and List<Ford> 

In other words, the “? extends …” construct introduces a new form of polymorphism that’s really specific to generics. A little funky but still much better than List<Object>.


					
Advertisements
Post a comment or leave a trackback: Trackback URL.

Comments

  • katcy  On February 3, 2012 at 11:40 pm

    Question: Does it make sense to say that two objects belonging to completely different hierarchies can share an equality relationship?

    • vvakar  On February 4, 2012 at 11:29 am

      Maybe, but it’s hard enough to get it right even for members of the same hierarchy (think overriding equals() in Java). As a mental exercise we can think about expanding the concept of the Duck test (“If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck”) to derive an equality concept between objects from different hierarchies based on their salient characteristics. In practice, it’s probably something I would never ever want to attempt.

      • katcy  On February 4, 2012 at 8:26 pm

        Well then, here’s part two of this question… what do you think is a possible reason why people left the method in the Object class with a signature like equals(Object)? Given that Generics helps enforce typing, I would rather a utility do something like equals(, )… ? Is it wrong to say that using generics will help avoid casts? Because in over-riding equals, the casting then looks pretty ugly…

      • vvakar  On February 5, 2012 at 8:47 am

        The equals(Object) signature is no doubt related to the fact that generics didn’t exist before 1.5. At the same time, I doubt the language itself would enforce any assumptions about what equals() should do and for that we have best practices. To this day, everyone seems to recommend checking whether the object is of the current class, as in:
        public boolean equals(Object aThat){

        if ( !(aThat instanceof Car) ) return false;

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

%d bloggers like this: