This problem comes from the typing of the constructor of javax.validation.ConstraintViolationException
. It accepts Set<ConstraintViolation<?>>
as argument.
While it's very easy to get a set of ConstraintViolation<X> where X is a concrete type, it seems impossible to get a set of "ConstraintViolation<?>" from any well-typed API. And it is not possible to convert the former to the latter without using some convoluted casts. (Casting to Set<? extends ConstraintViolation<?>>
and then to Set<ConstraintViolation<?>>
.)
So do you guys think the API is wrong or I am wrong (and why)?
The API is wrong. Unless implementations need to add new ConstraintViolation<?>
s to the set, it should accept all Set<? extends ConstraintViolation<?>>
.
Here's an example demonstrating why this is more flexible (provided by Paul Bellora, thanks):
public class Main {
interface Foo<T> { }
interface SubFoo<T> extends Foo<T> { }
static class Bar { }
public static void main(String[] args) {
Set<Foo<?>> arg1 = null;
Set<SubFoo<?>> arg2 = null;
Set<Foo<Bar>> arg3 = null;
Set<SubFoo<Bar>> arg4 = null;
Set<Foo<?>> inflexibleParam;
inflexibleParam = arg1; //success
inflexibleParam = arg2; //incompatible types
inflexibleParam = arg3; //incompatible types
inflexibleParam = arg4; //incompatible types
Set<? extends Foo<?>> flexibleParam;
flexibleParam = arg1; //success
flexibleParam = arg2; //success
flexibleParam = arg3; //success
flexibleParam = arg4; //success
}
}
(ideone)
The API is wrong.
Ideally anywhere we want to accept a covariant generic type, we should use ? extends
.
Some generic declarations are inherently covariant, which means they should always be used with wildcard, e.g. Iterator
. If an Iterator
is used without wildcard, it's almost certainly wrong.
The problem is, the wildcard syntax is so verbose, people are turned off and often forgot to use it. This is wide spread even in core libs, e.g. Iterable<T>.iterator()
returns Iterator<T>
, while it should return Iterator<? extends T>
.
Erasure, ironically, can rescue this problem. We know an Iterator<Apple>
can be safely used as Iterator<Fruit>
statically, and we know we can brutely cast Iterator<Apple>
to Iterator<Fruit>
dynamically, thanks to erasure. So just go ahead and do the brute cast.
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