Использование обобщений в аргументах исключения - PullRequest
10 голосов
/ 14 февраля 2011

Я пытаюсь сохранить коллекцию общих объектов в Exception, и у меня возникают проблемы с выяснением универсальных объектов.В частности, я использую Hibernate Validator и хотел бы сохранить собранный список нарушений в исключении для обработки на другом уровне приложения.Вот пример:

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

В Eclipse строка throws показывает неопределенный конструктор и предлагает изменить сигнатуру конструктора на 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 универсальным, чтобы я мог использовать его не только для User проверок.Я тоже пробовал Set<ConstraintViolation<? extends Object>>, но получаю те же результаты.

Есть ли способ выполнить то, что я пытаюсь сделать?

Ответы [ 6 ]

12 голосов
/ 14 февраля 2011

Вам необходимо объявить параметр набора нарушений как Set<? extends ConstraintViolation<?>>:

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

Тогда все должно работать так, как ожидается.

Это дает дополнительное преимущество защитного копирования Setвы получаете гарантию того, что внутреннее исключение Set не может быть изменено.

1 голос
/ 14 февраля 2011

Предположим, что требование должно быть однородным - violations должно иметь тип Set<ConstraintViolation<X>> для некоторых X.

Самый естественный способ сделать это - сделать ValidationException универсальным:

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. Сайт вызова может преобразовать нарушения, однородную коллекцию определенного неизвестного типа, в более общий тип только для чтения с использованием групповых символов. Это довольно неловко. Если case v4 является наиболее частым случаем использования, мы должны предоставить метод, который просто возвращает Set<ConstraintViolation<?>>. Мы не можем напрямую вернуть violations, это небезопасно. Требуется копия. Если v4 является единственным вариантом использования, то это решение действительно становится тем же решением, которое предлагали предыдущие респонденты.

1 голос
/ 14 февраля 2011

Одним из уродливых подходов было бы использование непроверенного приведения:

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

0 голосов
/ 17 июля 2014

Вы можете сделать:

Set<ConstraintViolation<User>> violations = validator.validate(user);
if (violations.size() > 0) {
    throw new ValidationException("User details are invalid", (Set<ConstraintViolation<?>>) (Set<? extends ConstraintViolation<?>>) violations);
}
0 голосов
/ 16 февраля 2011

Я считаю, что Java throwable нельзя сделать универсальным, потому что из-за стирания типа блок catch не может проверить универсальные параметры исключений, которые он перехватывает.Вам придется сделать это старомодным способом, с приведением типов и прочим.

Вы можете добавить метод для автоматического выполнения приведения:

class ConstraintViolation extends SomeException {
  Constraint c;
  «T extends Constraint» T getConstraint() { return (T) c};
} 

…
UserConstraint uc = theViolation.getConstraint();

Компилятор предупредит вас, чтона самом деле не могу выполнить актерский состав, о котором вы его просили, но это круто.

0 голосов
/ 14 февраля 2011

Мне кажется, проблема в том, что 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...