2

Maybe I am overlooking something very easy and obvious...

I have a method interface which goes like

private void render(Collection<Object> rows);

Now, the objects I need to pass is an array (from an enum):

Module[] mods = Module.values(); 
widget.render(mods);

Of course this does not work, but why does this not work:

widget.render(Arrays.asList(mods))

It turns my array to a collection of Module, and Module is an object...

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
transient_loop
  • 5,984
  • 15
  • 58
  • 117
  • To make `Collection` of enums use `EnumSet.allOf(Module.class)` instead. – viktor Dec 09 '11 at 20:34
  • If you had a solution to the problem, you probably should have posted an answer as compared to a comment, that way you could actually get up votes. The other answers give simpler solutions however. – D3_JMultiply Dec 09 '11 at 20:36

4 Answers4

6

Try changing your method signature to:

private void render(Collection<?> rows);

This is is saying your method takes a Collection with any type of element, whereas before it was saying the Collection should specifically have Object as its type parameter.

Using a wildcard like this will place limitations on how you can use the Collection that is passed into the method, especially in regard to modifying it. You might want to show us what your render method is doing if you want more detailed advice.

This post is worth reading with respect to using wildcarded collections in Java: What is PECS (Producer Extends Consumer Super)?

Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • 1
    ah I see...So this means I did not learn an important lesson yet: that in generics there is no implicit inheritance, i.e. a Collection is not a subtype of Collection...Thanks – transient_loop Dec 09 '11 at 20:28
  • all objects in java by default extend object, that's why anything can be passed into a method needing an Object parameter. So the object itself isn't really the problem having objects passed into it, but if the Object objects are not caster to their proper class then none of their special methods and variables and other attributes will be accessible. – D3_JMultiply Dec 09 '11 at 20:29
  • @fablife - That's right. Notice that this is different from arrays, where `Module[]` does inherit from `Object[]`. – Paul Bellora Dec 09 '11 at 20:41
4

Since a Collection<Object> is not a Collection<Module>. A nice tutorial about generics is available in PDF version, and is a must-read when you work with generics.

This specific case if for example explained on page 4, in the Generics and Subtyping part.

Robin
  • 36,233
  • 5
  • 47
  • 99
  • +1 for pointing out what to read in the document. That part is a real "of course, why did I not think of that" when reading it for the first time. – Roger Lindsjö Dec 09 '11 at 20:30
  • the way he has it would work completely fine if he just casted each item from Object to Module inside the render body – D3_JMultiply Dec 09 '11 at 20:34
  • I accepted Khan's response because he was the first to answer a very easy and understandable solution, but +1 for pointing me to a document explaining in detail. – transient_loop Dec 09 '11 at 20:49
0

The reasons for this are based on how Java implements generics. The best way I have found to explain it is by precisely comparing arrays and generic collections.

An Arrays Example

With arrays you can do this:

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

But, what would happen if you try to do this?

Number[0] = 3.14; //attempt of heap pollution

This last line would compile just fine, but if you run this code, you could get an ArrayStoreException.

This means that you can fool the compiler, but you cannot fool the runtime type system. And this is so because arrays are what we call reifiable types. This means that at runtime Java knows that this array was actually instantiated as an array of integers which simply happens to be accessed through a reference of type Number[].

So, as you can see, one thing is the real type of the object, an another thing is the type of the reference that you use to access it, right?

The Problem with Java Generics

Now, the problem with Java generic types is that the type information is discarded by the compiler and it is not available at run time. This process is called type erasure. There are good reason for implementing generics like this in Java, but that's a long story, and it has to do with binary compatibility with pre-existing code.

But the important point here is that since, at runtime there is no type information, there is no way to ensure that we are no committing heap pollution.

For instance,

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);

List<Number> myNums = myInts;
myNums.add(3.14); //heap polution

If the Java compiler does not stop you from doing this at compile time, the runtime type system cannot stop you either, because there is no way, at runtime, to determine that this list was supposed to be a list of integers only. The Java runtime would let you put whatever you want into this list, when it should only contain integers, because when it was created, it was declared as a list of integers.

As such, the designers of Java made sure that you cannot fool the compiler. If you cannot fool the compiler (as we can do with arrays) you cannot fool the runtime type system either.

As such, we say that generic types are non-reifiable.

Evidently, this would hamper pollymorphism as well pointed out. The solution is to learn to use two powerful features of Java generics known as covariance and contravariance.

Covariance

With covariance you can read items from a structure, but you cannot write anything into it. All these are valid declarations.

List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>()
List<? extends Number> myNums = new ArrayList<Double>()

And you can read from myNums:

Number n = myNums.get(0);

Because you can be sure that whatever the actual list contains, it can be upcasted to a Number (after all anything that extends Number is a Number, right?)

However, you are not allowed to put anything into a covariant structure.

myNumst.add(45L);

This would not be allowed, because Java cannot guarantee what is the actual type of the real object. It can be anything that extends Number, but the compiler cannot be sure. So you can read, but not write.

Contravariance

With contravariance you can do the opposite. You can put things into a generic structure, but you cannot read out from it.

List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");

List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);

In this case, the actual nature of the object is a List of Objects, and through contravariance, you can put Numbers into it, basically because numbers have Object as the common ancestor. As such, all Numbers are objects, and therefore this is valid.

However, you cannot safely read anything from this contravariant structure assuming that you will get a number.

Number myNum = myNums.get(0); //compiler-error

As you can see, if the compiler allowed you to write this line, you would get a ClassCastException at runtime.

Get/Put Principle

As such, use covariance when you only intend to take generic values out of a structure, use contravariance when you only intend to put generic values into a structure and use the exact generic type when you intend to do both.

The best example I have is the following that copies any kind of numbers from one list into another list.

public static void copy(List<? extends Number> source, List<? super Number> destiny) {
    for(Number number : source) {
        destiny.add(number);
    }
}

Thanks to the powers of covariance and contravariance this works for a case like this:

List<Integer> myInts = asList(1,2,3,4);
List<Integer> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();

copy(myInts, myObjs);
copy(myDoubles, myObjs);
Edwin Dalorzo
  • 76,803
  • 25
  • 144
  • 205
0

If you casted each Object in the Collection to Module, example:

if(object instanceof Module)
{
    Module m = (Module)object;
    //Do stuff here with m
}

Then it should work as well. Otherwise the other two answers also work just fine.

D3_JMultiply
  • 1,022
  • 2
  • 12
  • 22