Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using generics in arguments of an Exception

I'm trying to store a collection of generic objects within an Exception and am having troubles figuring out the generics. Specifically, I'm using Hibernate Validator and would like to save the collected list of violations within an exception for processing in another layer of the application. Here's an example:

Set<ConstraintViolation<User>> violations = validator.validate(user);
if (violations.size() > 0) {
    throw new ValidationException("User details are invalid", violations);
}

In Eclipse, the throws line is showing the constructor undefined and is suggesting I change the constructor signature to ValidationException(String, Set<ConstraintViolation<User>>. Here's ValidationException:

public class ValidationException extends Exception {
    private Set<ConstraintViolation<?>> violations;

    public ValidationException() {
    }
    public ValidationException(String msg) {
        super(msg);
    }
    public ValidationException(String msg, Throwable cause) {
        super(msg, cause);
    }
    public ValidationException(String msg, Set<ConstraintViolation<?>> violations) {
        super(msg);
        this.violations = violations;
    }
    public Set<ConstraintViolation<?>> getViolations() {
        return violations;
    }
}

However, I want to keep ValidationException generic so that I can use it for more than just User validations. I've tried Set<ConstraintViolation<? extends Object>> as well, but get the same results.

Is there a way to accomplish what I'm trying to do?

like image 291
Tauren Avatar asked Feb 14 '11 13:02

Tauren


3 Answers

You need to declare the violations set parameter as Set<? extends ConstraintViolation<?>>:

public ValidationException(String msg, 
                           Set<? extends ConstraintViolation<?>> violations) {
  super(msg);
  this.violations = Collections.unmodifiableSet(
      new HashSet<ConstraintViolation<?>>(violations));
}

Then everything should work as expected.

This has the additional benefit of defensively copying the Set you're given, ensuring that the exception's internal Set can't be changed.

like image 162
ColinD Avatar answered Oct 06 '22 13:10

ColinD


One ugly approach would be to use unchecked cast:

public class ValidationException extends Exception {
    private Set<ConstraintViolation<?>> violations;

    @SuppressWarning("unchecked")
    public <T> ValidationException(String msg, Set<ConstraintViolation<T>> violations) {
        super(msg);
        this.violations = (Set<ConstraintViolation<?>>)(Set<?>) violations;
    } 
}

As far as I understand, unchecked cast is completely safe in this case, so that @SuppressWarning("unchecked") is absolutely legal.

From other hand, this constructor can't be called with Set<ConstraintViolation<?>> as a parameter.

like image 32
axtavt Avatar answered Oct 06 '22 15:10

axtavt


Suppose the requirement is that the set must be homogeneous - violations must be of type Set<ConstraintViolation<X>> for some X.

The most natural way to do that is to make ValidationException generic:

public class ValidationException<T> extends Exception
    Set<ConstraintViolation<T>> violations;
    public ValidationException(String msg, Set<ConstraintViolation<T>> violations)

Of course, Java does not allow that for subtypes of Throwable, for reasons beyond type system. This is not our fault, so we are not guilty of inventing some workaround:

public class ValidationException extends Exception
{
    static class SetConstraintViolation<T> extends HashSet<ConstraintViolation<T>>
    {
        SetConstraintViolation(Set<ConstraintViolation<T>> violations)
        {
            super(violations);
        }
    }

    // this is homogeneous, though X is unknown
    private SetConstraintViolation<?> violations;

    public <T> ValidationException(String msg, Set<ConstraintViolation<T>> violations)
    {
        super(msg);
        this.violations = new SetConstraintViolation<T>(violations);
    }

    public <T> Set<ConstraintViolation<T>> getViolations()
    {
        return (Set<ConstraintViolation<T>>)violations;
    }
}

void test()
{
    Set<ConstraintViolation<User>> v = ...;
    ValidationException e = new <User>ValidationException("", v);
    Set<ConstraintViolation<User>> v2 = e.getViolations();
    Set<ConstraintViolation<Pswd>> v3 = e.getViolations();
    Set<? extends ConstraintViolation<?>> v4 = e.getViolations();
}

Note: the cast in getViolations() is only safe, if call site supplied correct T, as in the case of v2. In the case of v3, the cast is wrong - the compiler didn't warn us in vain.

The call site probably doesn't know, and doesn't care about, the exact T, as in the case of v4. The call site can cast the violations, a homogeneous collection of a certain unknown type, to a more general readonly type with wildcards. That is quite awkward. If case v4 is the most frequent use case, we should provide a method that simply returns Set<ConstraintViolation<?>>. We can't directly return violations, that's unsafe. A copy is required. If v4 is the only use case, the this solution really becomes the same solution proposed by previous responders.

like image 1
irreputable Avatar answered Oct 06 '22 13:10

irreputable