Question

I was trying to write some code that looked like this:

public List<IObject> getObject(){
  ArrayList<ConcreteObject> objects = new ArrayList<ConcreteObject>();
  return objects;
}

(Where ConcreteObject implements IObject)

This doesn't work at all. It gives a compiler error. Does Java have plans to support this in the future? What is the best workaround until then? What I ended up doing was:

public List<IObject> getObject(){
  List<IObject> objects = new ArrayList<IObject>();
  return objects;
}

This works and maybe there aren't really any bad side effects of doing it this way. Is this the generally accepted best approach?

Was it helpful?

Solution

Java already supports this feature, you just need to use it. For a primer, read the Wildcards tutorial from Sun.

What you want is the following:

public List<? extends IObject> getObject(){
  ArrayList<ConcreteObject> objects = new ArrayList<ConcreteObject>();
  return objects;
}

Alternatively, may use an unsafe cast:

public <T extends IObject> List<T> getObject(){
  ArrayList<T> objects = (ArrayList<T>) new ArrayList<ConcreteObject>();
  return objects;
}

… but this method is rather brittle and will throw a runtime exception (except of signalling a compile error) when you try to access its elements with an invalid type:

@SuppressWarnings("unchecked")
public <T extends IObject> List<T> getObject(){
  ArrayList<T> objects = (ArrayList<T>) new ArrayList<ConcreteObject>();
  objects.add(new ConcreteObject());
  return objects;
}

…
List<OtherConcreteObject> objects = getObject(); // Works.
OtherConcreteObject obj = OtherConcreteObject.get(0); // Throws CCE.

This will result in the followin ClassCastException at runtime: “ConcreteObject cannot be cast to OtherConcreteObject” – which is rather bad because as the code stands above, it should succeed.

For that reason you should try to avoid this method.

OTHER TIPS

It's illegal for reasons best described in Java Generics Tutorial

Taking an example from there and applying it to your case:

List<ConcreteObject> concreteObjects = new ArrayList<ConcreteObject>();
List<IObject> objects = concreteObjects; // assume it's legal
objects.add(new IObject() { // anonymous or some other (incompatible with ConcreteObject) implementation
});
ConcreteObject co = concreteObjects.get(0); // Profit! er... I mean error

In order to support covariance in the way you expected, Java needs "reified" generics. A reified List<ConcreteObject> would know that its elements need to be instances of ConcreteObject. So if a caller with a reference to that list declared as an List<IObject> tried to add AnAlternateImplObject, the operation would fail at runtime with an exception—just as the analogous case with arrays throws an ArrayStoreException today.

At the time generics were added, no one could figure out a way to reify types without breaking compatibility with existing code. "Bounded" generic types, using wildcards, were provided instead. However, if I recall correctly, a compatible method for reification has been devised since then, so this change may be back on the table for the distant future (Java 8?).

In the meantime, the solution you've used is a customary way to handle this case.

Just for interest, if you want really pedantic and rigorous type handling in a language somewhat similar to Java, you can hardly do better than Scala. Baroquely intricate type definitions are what Scala is all about. Prof. Odersky, the author of Scala, is the same guy who originally built generics (more timidly) into Java.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top