6

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

billc.cn
  • 7,187
  • 3
  • 39
  • 79
  • 3
    Why do you think it is impossible to get a `Set>`? Can you show some concrete example, where you see yourself stuck in this issue? – Rohit Jain Aug 15 '13 at 18:50
  • It does seem like that constructor should take `Set extends ConstraintViolation>>` instead. – Paul Bellora Aug 15 '13 at 18:51
  • 2
    I think it's a duplicate of http://stackoverflow.com/questions/12096846/how-do-i-construct-a-constraintviolationexception – Katona Aug 15 '13 at 19:30
  • @RohitJain It is of course possible to create such set in custom code, but its type is rather incompatible with others. Most _well-typed API_ will probably return `Set>`. – billc.cn Aug 16 '13 at 10:49

2 Answers2

4

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)

Ben Schulz
  • 6,101
  • 1
  • 19
  • 15
  • @OliCharlesworth I think you may have misunderstood the answer. – Paul Bellora Aug 15 '13 at 18:54
  • I guess you can pass any parameterized type to `ConstraintViolation>`. – Rohit Jain Aug 15 '13 at 18:54
  • These downvotes are inexplicable +1 @SotiriosDelimanolis can you please explain? – Paul Bellora Aug 15 '13 at 18:55
  • @PaulBellora: Possibly. My interpretation of the answer is essentially: "it's impossible to pass a `ConstraintViolation` to a method taking `ConstraintViolation>`". Which is clearly not the case. – Oliver Charlesworth Aug 15 '13 at 18:56
  • @OliCharlesworth I don't really know what you are trying to tell me. Yes, one can construct sets shaped `Set>`. But why should one be forced to, when the API could just as well accept a `Set extends G>>`? – Ben Schulz Aug 15 '13 at 18:56
  • 2
    @PaulBellora You cannot add anything to a `Set extends anything>`. – Sotirios Delimanolis Aug 15 '13 at 18:57
  • @SotiriosDelimanolis That's the point - the constructor isn't adding anything, which is why it *should* use `? extends`. – Paul Bellora Aug 15 '13 at 18:58
  • @Paul, Ben: Perhaps this is an answer to a slightly different question, then. The OP is (I think) claiming that it's not possible to obtain a `Set>`; this answer is stating (I think) that it would be *better* if the ctor took a `Set extends G>>`. I'm not sure why it's stating that, though; with some clarification, I might remove my -1. – Oliver Charlesworth Aug 15 '13 at 19:00
  • @OliCharlesworth Yeah, that's what the answer's saying. As it is, the API forces the caller to copy to newly created `Set>`, or do an unchecked cast. – Paul Bellora Aug 15 '13 at 19:03
  • @PaulBellora: You're saying that if the user happened to have a `Set>`, they'd be in trouble with this method? If so, then I agree. – Oliver Charlesworth Aug 15 '13 at 19:05
  • @OliCharlesworth Yeah. The OP's point is that "any well-typed API" is unlikely to return exactly `Set>`. Even `Set>` wouldn't work because of the way nested wildcards behave. – Paul Bellora Aug 15 '13 at 19:06
  • @PaulBellora: Sure, but they could return `Set>`, which would be fine (`X>` the supertype of `X`). Whether or not they'd ever return `Set>`, I have no idea (I'm not familiar with use-cases for this API). – Oliver Charlesworth Aug 15 '13 at 19:09
  • @OliCharlesworth That wouldn't work either - see the edit to my comment. I'm not familiar with this API either, but we're talking PECS. – Paul Bellora Aug 15 '13 at 19:11
  • @SotiriosDelimanolis I added an example - I invite you and Oli to un-downvote this answer :) – Paul Bellora Aug 15 '13 at 19:38
  • @PaulBellora I've +1 a while ago. – Sotirios Delimanolis Aug 15 '13 at 19:38
  • @PaulBellora: Yes, you're right. Whilst one could *add* a `Foo` to a `Set>`, one can't *assign* a `Set>` to a `Set>`. So -1 definitely removed with the edit to the answer. Nevertheless, I'm not sure what the intentions of this API are; note that `ConstraintViolationException` also has a `getConstraintViolation()` method. I'm not sure what the impact would be of having that return a `Set extends ...>` (which it would need to do if the ctor were different). (For one, you wouldn't be able to add anything to the returned `Set`, which may or may not be helpful...) – Oliver Charlesworth Aug 15 '13 at 21:49
  • @OliCharlesworth Point well taken about the getter. The post that Katona commented about points to an issue [BVAL-198](https://hibernate.atlassian.net/browse/BVAL-198) that calls for Ben's solution and keeps the getter's signature by returning `new HashSet>(constraintViolations)`. Thanks for the good discussion. – Paul Bellora Aug 15 '13 at 22:11
  • @PaulBellora Thanks for your tenacity. These should really be your upvotes. – Ben Schulz Aug 16 '13 at 09:11
  • @BenSchulz Ha, no problem. I'm glad we were able to turn that downvote pile-on around. – Paul Bellora Aug 16 '13 at 14:35
2

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.

ZhongYu
  • 19,446
  • 5
  • 33
  • 61
  • +1 But note that it's generally unhelpful to *return* generic types with `? extends` though - method parameters are where they can add flexibility. – Paul Bellora Aug 15 '13 at 19:36
  • It's helpful to subclass implementations to return an `Iterator` as an `Iterator extends Fruit>`. – ZhongYu Aug 15 '13 at 19:38
  • 1
    This is based on a legit use case I found in another SO question, where a subclass implements `Iterable`, yet the subclass has an `Iterator` ready. A conversion is needed. If the `iterator()` method were to return a wildcard type instead, no conversion is needed. – ZhongYu Aug 15 '13 at 21:43
  • Ah, okay, so it's giving flexibility to overriding implementations. That makes sense, though it still seems undesirable for an API. – Paul Bellora Aug 15 '13 at 21:47
  • Another benefit is that subclass `iterator()` return type could be more specific, i.e. `Iterable extends Apple>`. It's undesirable to return wildcard type only because wildcard is verbose. – ZhongYu Aug 16 '13 at 00:53
  • That's a good point about allowing more specific types, if the caller isn't coding to interface. Another downside to returning a type with `? extends` is that the returned object is "read-only" - that's fine for `Iterator`, but not `ListIterator`. It seems like it should be used very strategically. – Paul Bellora Aug 16 '13 at 01:02
  • `ListIterator` is not inherently covariant, so my argument doesn't apply to it. – ZhongYu Aug 16 '13 at 01:43
  • Ah, sorry, forgot covariance was assumed. Again, good points. – Paul Bellora Aug 16 '13 at 01:49