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