Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Impossible typing when an argument accepts Collection<X<?>>

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)?

like image 432
billc.cn Avatar asked Aug 15 '13 18:08

billc.cn


Video Answer


2 Answers

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)

like image 62
Ben Schulz Avatar answered Oct 23 '22 23:10

Ben Schulz


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.

like image 20
ZhongYu Avatar answered Oct 23 '22 22:10

ZhongYu