18

I have been using a little generic method to create sets from vararg of elements, eg

public <T> Set<T> createSet( T... elements ) { ...

Recently, however, I came across a situation where the compiler did not do what I expected it to do. Of the following createSet() uses only s3 works.

Set<Class<? extends Throwable>> s1 = createSet( Exception.class, RuntimeException.class );

Set<? extends Class<Throwable>> s2 = createSet( Exception.class, RuntimeException.class );

Set<? extends Class<? extends Throwable>> s3 = createSet( Exception.class, RuntimeException.class );

Can anyone give a clear explanation of why s3 works and what might be wrong with my thinking regards s1 -- which was my initial coding? Thanks.

Robert Munteanu
  • 67,031
  • 36
  • 206
  • 278
Andrew Gilmartin
  • 1,776
  • 12
  • 12

4 Answers4

10

The problem is simply with the inference logic. s1 works just fine (well, except for vararg warnings) if you explicitly type the method call:

Set<Class<? extends Throwable>> s1 = 
    this.<Class<? extends Throwable>>createSet( Exception.class, RuntimeException.class );

But by default the return type given your parameters is a Set<Class<? extends Exception>> (I suppose because it's the most specific possibility). You just need to give the compiler a hint here, since without it, it is essentially trying to do this:

Set<Class<? extends Exception>> temp = createSet(Exception.class, RuntimeException.class);
Set<Class<? extends Throwable>> s1 = temp;

Which is not allowed since from the compiler's point of view, you could then put a OutOfMemoryError.class into temp which would violate its type.

Edit

The reason s3 works for you is because a Class<? extends Exception> is assignable to Class<? extends Throwable>:

//this works
Class<? extends Exception> exceptionRef = Exception.class;
Class<? extends Throwable> throwableRef = exceptionRef;

and so the extends keyword affords you the ability to convert from a Set<Class<? extends Exception>> to a Set<? extends Class<? extends Throwable>>:

//this works too
Set<Class<? extends Exception>> exceptionSetRef = ...;
Set<? extends Class<? extends Throwable>> throwableSetRef = exceptionSetRef;

Unfortunately, that's probably not what you want since now you can't put anything into throwableSetRef.

Mark Peters
  • 80,126
  • 17
  • 159
  • 190
4

I think it's because closest common type of Exception and RuntimeException is an Exception, not a Throwable. T in invokation of createSet() is inferred to be Class<? extends Exception>, and it's illegal to assign Set<Class<? extends Exception>> to a variable of type Set<Class<? extends Throwable>

This works:

Set<Class<? extends Exception>> s1 = createSet( Exception.class, RuntimeException.class );
socha23
  • 10,171
  • 2
  • 28
  • 25
  • But if he adds `OutOfMemoryError.class` it won't work anymore, because it doesn't go through the `Exception` hierarchy. – Petar Minchev Nov 02 '11 at 15:49
  • yes, but then the original s1, `Set> s1 = createSet( Exception.class, RuntimeException.class, OutOfMemoryError.class);`, works. – socha23 Nov 02 '11 at 15:52
  • But if he uses both `createSet( Exception.class, RuntimeException.class);` and `createSet( Exception.class, RuntimeException.class, OutOfMemoryError.class);` in his code, you are doomed. – Petar Minchev Nov 02 '11 at 15:53
  • Why? `Set> s3 = createSet( Exception.class, RuntimeException.class); Set> s1 = createSet( Exception.class, RuntimeException.class, OutOfMemoryError.class);` compiles fine. – socha23 Nov 02 '11 at 15:57
  • But it is inconvenient for using. You have to calculate the right type. – Petar Minchev Nov 02 '11 at 16:01
  • +1, you beat me to the meat of the explanation. But being forced to change the variable type (potentially to an unwanted alternative) is probably not as elegant as instructing the compiler to use the type you want. – Mark Peters Nov 02 '11 at 16:07
1

To make possible problems of s1 and s2 more clear, let's replace Class<T> with List<T>.

Then:

List<RuntimeException> runtimeExceptions = new ArrayList<RuntimeException>();

Set<List<RuntimeException>> listsOfRuntimeExceptions =
    new HashSet<Lis<RuntimeException>>();
listsOfRuntimeExceptions.add(runtimeExceptions);

Set<? extends List<? extends Throwable>> listsOfThrowables = 
    listsOfRuntimeExceptions; // This is legal 

Set<List<? extends Throwable>> s1 = listsOfThrowables; // Imagine that this is legal
s1.add(Arrays.asList(new Exception())); // Type safety of listsOfRuntimeExceptions is violated

Set<? extends List<Throwable>> s2 = listsOfThrowables; // Imagine that this is legal
s2.get(0).add(new Exception()); // Type safety of runtimeExceptions is violated
axtavt
  • 239,438
  • 41
  • 511
  • 482
1

I think the problem here comes from how the compiler determines the "upper bound" (in terms of Types) for the varargs you have. If we check http://download.oracle.com/javase/tutorial/java/generics/non-reifiable-varargs-type.html, we can see that for s1, the upper bound that you get is Class<? extends Exception> (and a warning telling that is generated). The error then is because at the end you try to convert Set<Class<? extends Exception>> to Set<Class<? extends Throwable>>. If you rewrite s1 to:

Set<Class<? extends Throwable>> s1 = createSet( Exception.class, RuntimeException.class, Throwable.class);

then the upper bound type is Set<Class<? extends Throwable>>, generating again a warning but no error.

jalopaba
  • 8,039
  • 2
  • 44
  • 57