Использование дженериков в аргументах исключения
Я пытаюсь сохранить коллекцию общих объектов в Exception
, и у меня возникают проблемы с выяснением генериков. В частности, я использую Hibernate Validator и хотел бы сохранить собранный список нарушений в рамках исключения для обработки на другом уровне приложения. Вот пример:
Set<ConstraintViolation<User>> violations = validator.validate(user);
if (violations.size() > 0) {
throw new ValidationException("User details are invalid", violations);
}
В Eclipse строка throws
показывает конструктор undefined и предлагает мне изменить подпись конструктора на ValidationException(String, Set<ConstraintViolation<User>>
. Здесь 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;
}
}
Однако, я хочу сохранить ValidationException
generic, чтобы я мог использовать его только для проверки User
. Я также пробовал Set<ConstraintViolation<? extends Object>>
, но получаю те же результаты.
Есть ли способ выполнить то, что я пытаюсь сделать?
Ответы
Ответ 1
Вам нужно объявить параметр набора нарушений как Set<? extends ConstraintViolation<?>>
:
public ValidationException(String msg,
Set<? extends ConstraintViolation<?>> violations) {
super(msg);
this.violations = Collections.unmodifiableSet(
new HashSet<ConstraintViolation<?>>(violations));
}
Тогда все должно работать как ожидалось.
Это имеет дополнительное преимущество для защиты от копирования Set
, предоставленного вам, гарантируя, что внутреннее исключение Set
не может быть изменено.
Ответ 2
Один уродливый подход состоял бы в том, чтобы использовать непроверенный бросок:
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;
}
}
Насколько я понимаю, неконтролируемый бросок в этом случае полностью безопасен, так что @SuppressWarning("unchecked")
абсолютно легален.
С другой стороны, этот конструктор нельзя вызывать с Set<ConstraintViolation<?>>
в качестве параметра.
Ответ 3
Предположим, что требование должно быть однородным - violations
должно быть типа Set<ConstraintViolation<X>>
для некоторого X
.
Самый естественный способ сделать это - сделать ValidationException
generic:
public class ValidationException<T> extends Exception
Set<ConstraintViolation<T>> violations;
public ValidationException(String msg, Set<ConstraintViolation<T>> violations)
Конечно, Java не допускает этого для подтипов Throwable по причинам, отличным от системы типов. Это не наша вина, поэтому мы не виновны в выработке какого-либо способа обхода:
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();
}
Примечание: листинг в getViolations()
является только безопасным, если сайт звонка правильно поставлен T
, как в случае v2
. В случае v3, приведение не соответствует действительности - компилятор не предупредил нас напрасно.
Сайт вызова, вероятно, не знает и не заботится о точном T
, как в случае с v4. Сайт вызова может передавать нарушения, однородную коллекцию определенного неизвестного типа, в более общий тип readonly с помощью подстановочных знаков. Это довольно неудобно. Если случай v4
является наиболее часто используемым случаем, мы должны предоставить метод, который просто возвращает Set<ConstraintViolation<?>>
. Мы не можем напрямую вернуть violations
, что небезопасно. Требуется копия. Если v4
является единственным вариантом использования, это решение действительно становится тем же самым решением, которое предлагалось предыдущими респондентами.
Ответ 4
Я думаю, проблема в том, что Set<ConstraintViolation<User>>
может содержать только объекты типа ConstraintViolation<User>
, тогда как ваш тип аргумента Set<ConstraintViolation<?>>
может содержать нарушения любого типа (так что это смешанная коллекция). Таким образом, это не подтип. Я думаю (не пробовал) вы могли бы объявить конструктор как
public <T> ValidationException(String msg, Set<ConstraintViolation<T>> violations);
но тогда у вас все еще есть проблема, что переменная в вашем исключении не может иметь такой тип. Вы можете скопировать содержимое параметра в новый Set, использовать Collections.unmodifiedSet(), чтобы обернуть его, или сделать уродливое литье, упомянутое axtavt, чтобы обойти это. Здесь мой предпочтительный способ:
public <T> ValidationException(String msg, Set<ConstraintViolation<T>> violations) {
this.violations = Collections.unmodifiableSet(violations);
}
Ответ 5
Я считаю, что Java throwable
нельзя сделать универсальным, потому что из-за стирания типа блок catch
не может проверить общие параметры исключений, которые он ловит. Вам придется сделать это старомодно, с кастами и еще много чего.
Вы можете добавить метод автоматического выполнения акта:
class ConstraintViolation extends SomeException {
Constraint c;
«T extends Constraint» T getConstraint() { return (T) c};
}
…
UserConstraint uc = theViolation.getConstraint();
Компилятор предупредит вас о том, что он не может выполнить бросок, который вы просите, но это круто.
Ответ 6
Вы можете сделать:
Set<ConstraintViolation<User>> violations = validator.validate(user);
if (violations.size() > 0) {
throw new ValidationException("User details are invalid", (Set<ConstraintViolation<?>>) (Set<? extends ConstraintViolation<?>>) violations);
}