2011-02-14 10 views
10

Estoy tratando de almacenar una colección de objetos genéricos dentro de un Exception y estoy teniendo problemas para encontrar los genéricos. Específicamente, estoy usando Hibernate Validator y me gustaría guardar la lista recopilada de violaciones dentro de una excepción para el procesamiento en otra capa de la aplicación. He aquí un ejemplo:Uso de genéricos en argumentos de una excepción

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

En Eclipse, la línea throws está mostrando el constructor definido y está sugiriendo que cambie la firma constructora para ValidationException(String, Set<ConstraintViolation<User>>. Aquí está 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; 
    } 
} 

Sin embargo, quiero seguir ValidationException genérico para que pueda usarlo para algo más que User validaciones. He intentado Set<ConstraintViolation<? extends Object>> también, pero obtengo los mismos resultados.

¿Hay alguna manera de lograr lo que estoy tratando de hacer?

+0

¿Por qué necesita el ConstrainsViolation a ser genérica en el primer lugar? ¿No puedes simplemente crear una clase separada heredando una AbstractBase para cada posible violación? – Falcon

+1

Pensé que el uso de un tipo de Excepción genérico sería una buena idea, pero como se menciona en @axtavt, las Excepciones no pueden ser genéricas. Consulte estas preguntas frecuentes: http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ301 – Tauren

+0

@Falcon: 'ConstraintsViolation' es parte del paquete de validación de Hibernate y utiliza genéricos. No es una clase propia. – Tauren

Respuesta

12

Es necesario declarar las violaciónes conjunto de parámetros como Set<? extends ConstraintViolation<?>>:

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

entonces todo debería funcionar como se espera.

Esto tiene el beneficio adicional de copiar de manera defensiva el Set que se le otorga, asegurando que la excepción Set interna no se puede cambiar.

+0

Gracias, esto se ve bien y limpio! – Tauren

1

Un enfoque fea sería el uso de yeso sin control:

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; 
    } 
} 

Por lo que yo entiendo, fundido sin control es completamente seguro en este caso, de manera que @SuppressWarning("unchecked") es absolutamente legal.

Por otro lado, este constructor no se puede llamar con Set<ConstraintViolation<?>> como parámetro.

+0

¡Ugly tiene razón! Pero parece que funciona Ya no se obtienen errores de compilación. ¡Gracias! – Tauren

+0

Si el yeso es seguro depende de dónde (y cómo) se usa el conjunto fuera del objeto de excepción. 'Set > no es un supertipo de' Set > ', ya que este último solo admite elementos del tipo' ConstraintViolation '(y sus usuarios pueden depender de que así sea), mientras que el anterior admite 'ConstrainViolation's de cualquier tipo (incluso mixto) (lo que significa que podría poner' ConstraintViolation 'en este conjunto). En este uso para la excepción supuestamente los creadores de la excepción lo crean y el receptor solo itera, por lo que realmente no importa. –

0

Creo que el problema es que un Set<ConstraintViolation<User>> solo puede contener objetos del tipo ConstraintViolation<User>, mientras que su tipo de argumento Set<ConstraintViolation<?>> puede contener infracciones de cualquier tipo (por lo tanto, es una colección mixta). Por lo tanto, esto no es un subtipo. Creo (no probé) que podría declarar el constructor como

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

pero todavía tiene el problema de que la variable en su excepción no puede tener tal tipo. Puede copiar el contenido del parámetro en un nuevo conjunto, usar Collections.unmodifiedSet() para envolverlo, o hacer el molde feo mencionado por axtavt para evitar esto. Aquí mi forma preferida:

public <T> ValidationException(String msg, Set<ConstraintViolation<T>> violations) { 
    this.violations = Collections.unmodifiableSet(violations); 
} 
1

Supongamos que el requisito es que el conjunto debe ser homogéneo: violations debe ser del tipo Set<ConstraintViolation<X>> para algunos X.

La forma más natural de hacerlo es hacer ValidationException genérica:

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

Por supuesto, Java no permitirá que para los subtipos de Throwable, por razones ajenas sistema de tipos.Esto no es culpa nuestra, por lo que no son culpables de inventar alguna solución:

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(); 
} 

Nota: el reparto de getViolations() sólo es seguro, si el sitio llamado suministra correcta T, como en el caso de v2. En el caso de v3, el reparto está mal: el compilador no nos advirtió en vano.

El sitio de llamada probablemente no sabe, y no le importa, el exacto T, como en el caso de v4. El sitio de llamada puede convertir las violaciones, una colección homogénea de cierto tipo desconocido, en un tipo de lectura más general con comodines. Eso es bastante incomodo. Si el caso v4 es el caso de uso más frecuente, debemos proporcionar un método que simplemente devuelve Set<ConstraintViolation<?>>. No podemos devolver directamente violations, eso no es seguro. Se requiere una copia. Si v4 es el único caso de uso, esta solución realmente se convierte en la misma solución propuesta por los que respondieron anteriormente.

+0

gracias por la respuesta muy completa! Su solución parece agregar complejidad excesiva. ¿Hay alguna razón por la cual es un mejor enfoque que algo más simple como el sugerido por @ColinD? – Tauren

+0

Impuse el requisito homogéneo, para el experimento mental, nada más. Probablemente no sea lo que te importa. – irreputable

0

Creo que un Java throwable no puede hacerse genérico, porque debido a la borradura de tipo un bloque catch no puede probar los parámetros genéricos de las excepciones que capta. Tendrás que hacerlo a la vieja usanza, con yesos y demás.

Se puede añadir un método para hacerlo automágicamente del reparto:

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

… 
UserConstraint uc = theViolation.getConstraint(); 

El compilador le advertirá que en realidad no puede realizar el reparto que usted está pidiendo que haga, pero eso está bien.

0

Se podría hacer:

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