I have a class that returns an ArrayList of ArrayLists. I would like to return a Collection of Collections to abstract the output datatype.
With a standard ArrayList, its easy - just define the return type as Collection. Unfortunately, with a 2D ArrayList, that doesn't work:
//This does not compile
public Collection<Collection<Object> myFunction() {
return new ArrayList<ArrayList<Object>>();
}
//This does compile (but isn't what I want)
public Collection<ArrayList<Object> myFunction() {
return new ArrayList<ArrayList<Object>>();
}
Obviously one can create a Collection, then add the ArrayLists individually, converting them to Collections, but this is more complicated than I would expect. What is the proper way to abstract a multidimensional return type?
Obviously one can create a Collection, then add the ArrayLists individually, converting them to Collections, but this is more complicated than I would expect. What is the proper way to abstract a multidimensional return type?
That's not how it works. ArrayLists are a collection. That's what class Dog extends Animal
means: All dogs already are animals. There is no 'converting'. You don't convert a dog to an animal, it's already one.
Hence, add the ArrayLists individually, converting them to Collections is nonsense.
// paraphrased ArrayList<ArrayList<Object>> a = new ArrayList<ArrayList<Object>>(); Collection<Collection<Object>> b = a;
no, this does not compile, because that is a nonsensical statement. The thing is, collections have an add
method. You can add things to them. And java is a reference-based system, so in the code above there is only a single list-of-lists (count the new
- I only count one, so there must be only one list here, there cannot possibly be 2), just.. you have 2 variables pointing at the same thing. Like an address book with 2 pages, both with the same address on it.
Via variable b
I could do: b.add(new HashSet<Object>())
. That should be obvious: All HashSets are collections, the .add()
method of variable b
requires a Collection<Object>
, and.. new HashSet<Object>()
is a Collection<Object>
, therefore, that is allowed. And indeed that is valid java here.
Except.. it's the same list. a
also points at this list. Which.. now has a HashSet in it. A variable of type ArrayList<ArrayList<Object>>
has.. a HashSet in it.
Oh dear.
That's all wrong.
Which is why java does not allow Collection<Collection<Object>> b = a;
here - because that is nonsense.
You can opt into covariance by writing:
Collection<? extends Collection<?>> b = a;
This is not too useful - the point of ?
here is to stop you from calling 'add', and then all is well. This assignment works, and indeed b.add(new HashSet<>())
no longer works now, but, b.get(0).add(someObject)
also no longer works.
The correct move is to go to where-ever you make that initial arraylist and make it correct right from the get-go: Make that a new ArrayList<Collection<Object>>
. Because all ArrayLists are collections, you can call .add(new ArrayList<Object>())
on a variable of type ArrayList<Collection<Object>>
, no problem. And you can assign an ArrayList<Collection<Object>>
to a variable of type Collection<Collection<Object>>
.
Generics are invariant - if you need type FourFootedAnimal, only that type will do. You can't provide a subtype or a supertype. But outside of generics, java is covariant - FourFootedAnimals will do, but so will a Dog or a Cat. If you want covariance, use ? extends
(and ?
on its own is short for ? extends Object
), but note that this disables all attempts to add.