tags:

views:

124

answers:

5

If I use unbounded wildcard types for two collections (each collection will have a different type) as the arguments for a method:

private void doAssertion(List<?> testList, List<?> generatedList)

Inside this method, can I first check the type of objects in these collections, and then cast the collection to a parameterized type? This just smells bad, and I get an unchecked cast warning.

if (testList.get(0) instanceof X) {
  List<X> xList = (List<X>) testList;
  // call methods specific to X for each object
}

else if (testList.get(0) instanceof Y){
  List<Y> yList = (List<Y>) testList;
  // call methods specific to Y for each object
}

Part of my problem is that I don't have the ability to touch the code that defines classes X or Y. Otherwise, I know I can have them implement a common interface, and use a bounded type parameter. I can't overload assertEqual because both methods have the same erasure.

In my case, X and Y are always going to be children of other classes, and I'm not modifying the objects in anyway, just calling the get() methods of the objects.

+4  A: 

No, you shouldn't do this. Take for example:

List<Integer> myIntList = ...;
if (myIntList.get(0) instanceof Number) {
    List<Number> myNumberList = (List<Number>)myIntList;
    myNumberList.put(Double.valueOf(100)); // BAD!
}

Generic arguments are not necessarily substitutable in sub/superclass hierarchies.

Steven Schlansker
Ah, true, this doesn't guarantee the type safety of the collection. In my case, X and Y are always going to be children of other classes, and I'm not modifying the objects in anyway, just calling get() methods.
TheAmpersand
+1  A: 

private <T, U> void doAssertion(List<T> testList, List<U> generatedList);

Leo Izen
If I do this, I still have the unchecked cast warning... what does this gain me over using the unbounded wildcard type?
TheAmpersand
+2  A: 

You can declare multiple methods with concrete types X and Y:

  private void doAssertion(List<X> testList, List<X> generatedList, X x) {
    // call methods specific to X for each object
  }

  private void doAssertion(List<Y> testList, List<Y> generatedList, Y y) {
    // call methods specific to Y for each object
  }

The 3rd parameter is simply used simply to hint method resolution.

Eugene Kuleshov
Ah, sorry, I forgot to mention that each collection will have a different type... I'm testing that if I pass collection #1 to a method that it generates objects of a different type with the same data, and puts it in collection #2. I'm writing a unit test to cover this behavior.
TheAmpersand
@TheAmpersand as long as those types are known you can create method for each of them
Eugene Kuleshov
@Steve please note that X and Y are concrete types as noted in my answer, so there is no erasure for 3rd parameter and signatures are List,List,X and List,List,Y
Eugene Kuleshov
On second thought, I don't think this is the best solution, because because the two list parameters of this method should be different types, so the "hint" parameter will only serve one purpose: to solve the problem of erasure.
TheAmpersand
No the third parameter is needed for the method dispatch. Basically you are getting rid of your if/else
Eugene Kuleshov
In the description of the issue I mention that "each of the collections will have a different type". So testList and generatedList will be related, but of different concrete classes. It just seems arbitrary to have one of the types in the actual parameter list. Cumbersome if we have both. I like the idea of getting rid of the if/else statements, but it just seems very cumbersome to do it this way...
TheAmpersand
+1  A: 

In my case, X and Y are always going to be children of other classes, and I'm not modifying the objects in anyway, just calling get() methods.

If the above statement is true, then are these getXX() methods on the parent class. If so, why can't you do:

private void doAssertion(List<? extends SomeFoo> testList, List<? extends SomeFoo> generatedList)

Assuming SomeFoo has the functions you want then I can call it with any subclass of SomeFoo.

Amir Raminfar
Unfortunately the different objects in these collections have different class hierarchies, and they don't implement any common interfaces. This prevents me from using a bounded type parameter.
TheAmpersand
A: 

Warning:

List<?> 

means list of unknown type. You will be able to get() from this list but will not be able to add() to it, except null.

In case you need to add elements it's best to just use non-generic version and check the type as you proposed. The result will be the same.

private void doAssertion(List testList, List generatedList){

  if (testList.get(0) instanceof X) {
    List<X> xList = (List<X>) testList;
    // call methods specific to X for each object
  } else if (testList.get(0) instanceof Y){
    List<Y> yList = (List<Y>) testList;
    // call methods specific to Y for each object
  }

}

Also make sure that lists are not empty before doing (testList.get(0) instanceof X)

Peter Knego
Ok, fair enough. If I won't be adding anything to the collection, then type safety isn't really a concern.
TheAmpersand
I think this method can be strengthened by adding final to each parameter to hint that nothing should be added to the collections.
TheAmpersand
This solution won't work if testList won't have any elements
Eugene Kuleshov
I said "Also make sure that lists are not empty.."If the list is empty then you can't read any elements from it, can you?
Peter Knego
@TheAmpersand: putting final on parameter just prevents reassigning it (changing what it references), but does not prevent changing the contents of the object (List contents in this case).
Peter Knego