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.
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.
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
.
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 );
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With