Uso de la anotación NotNull en el argumento del método

156

Acabo de comenzar a usar la @NotNullanotación con Java 8 y obtener algunos resultados inesperados.

Tengo un método como este:

public List<Found> findStuff(@NotNull List<Searching> searchingList) {
    ... code here ...
}

Escribí una prueba JUnit pasando el valor nulo para el argumento searchList. Esperaba que ocurriera algún tipo de error, pero sucedió como si la anotación no estuviera allí. ¿Es este comportamiento esperado? Por lo que entendí, esto fue para permitirle omitir la escritura del código de verificación nulo repetitivo.

Una explicación de lo que se supone que debe hacer exactamente @NotNull sería muy apreciada.

DavidR
fuente
29
@NotNullEs solo una anotación. Las anotaciones no hacen nada por sí mismas. Necesitan un procesador de anotaciones en tiempo de compilación, o algo que lo procese en tiempo de ejecución.
Sotirios Delimanolis
¿Está ejecutando el código dentro de un servidor de aplicaciones (por ejemplo, utilizando Arquillian )?
jabu.10245
1
@SotiriosDelimanolis - Entonces, ¿cuál es el punto, solo una advertencia para cualquiera que llame al método para que no pase un valor nulo? En cuyo caso aún necesita el código de validación de puntero nulo.
DavidR
1
mira el validador de hibernación
arisalexis
@ jabu.10245: sin utilizar ningún servidor de aplicaciones.
DavidR

Respuestas:

183

@Nullabley @NotNullno hacer nada por su cuenta. Se supone que actúan como herramientas de documentación.

La @Nullableanotación le recuerda la necesidad de introducir una verificación NPE cuando:

  1. Llamar a métodos que pueden devolver nulo.
  2. Desreferenciar variables (campos, variables locales, parámetros) que pueden ser nulos.

La @NotNullAnotación es, en realidad, un contrato explícito que declara lo siguiente:

  1. Un método no debe devolver nulo.
  2. Una variable (como campos, variables locales y parámetros) no puede no debe llevar a cabo valor nulo.

Por ejemplo, en lugar de escribir:

/**
 * @param aX should not be null
 */
public void setX(final Object aX ) {
    // some code
}

Puedes usar:

public void setX(@NotNull final Object aX ) {
    // some code
}

Además, a @NotNullmenudo es verificado por Validadores de restricción (por ejemplo, en primavera e hibernación).

La @NotNullanotación no realiza ninguna validación por sí sola porque la definición de la anotación no proporciona ninguna ConstraintValidatorreferencia de tipo.

Para más información ver:

  1. Validación de frijol
  2. NotNull.java
  3. Constraint.java
  4. ConstraintValidator.java
justAnotherUser ...
fuente
3
Entonces, para aclarar la parte 2 de la parte NotNull, ¿realmente debería decir "no debería", no "no puede", ya que no se puede forzar? O si se puede hacer cumplir en tiempo de ejecución, ¿cómo lo haría?
DavidR
Sí, es un "no debería" ... la implementación del método debe hacer cumplir el contrato.
justAnotherUser ...
1
Alternativamente, en Java 8, Optionalpodría usarse en lugar de @Nullvalores de retorno y sobrecarga de métodos en lugar de @Nullen listas de parámetros: dolszewski.com/java/java-8-optional-use-cases
Chad K
13
Creo que la confusión proviene del documento de Java de la anotación NotNull: * The annotated element must not be {@code null}. * Accepts any type.y creo que la palabra debe reemplazarse con debería pero nuevamente depende de cómo la leas. Definitivamente, sería bueno tener algunas aclaraciones más
Julián
@Julian creo que debe ser el término correcto porque es una regla, no una recomendación. Si usa la anotación donde no debe pasar, nullpero se permitiría, está usando la anotación incorrecta. El término no implica que esté validado. Sin embargo, una pista de que no está validado no estaría de más. Si desea agregar validación automática, puede usar algunas herramientas externas. Por ejemplo, el IDE IntelliJ tiene soporte incorporado para inyectar cheques nulos.
JojOatXGME
27

Como se mencionó anteriormente @NotNull, no hace nada por sí solo. Una buena forma de usarlo @NotNullsería usarlo conObjects.requireNonNull

public class Foo {
    private final Bar bar;

    public Foo(@NotNull Bar bar) {
        this.bar = Objects.requireNonNull(bar, "bar must not be null");
    }
}
Bollywood
fuente
66
Solo un consejo. También puede escribir tales tareas con una línea:this.bar = Objects.requireNonNull(bar, "bar must not be null");
lolung
Gracias por el consejo @lolung: ahora he actualizado el código anterior recortado en función de su comentario.
Bollywood
6

SO @NotNull solo es una etiqueta ... Si desea validarlo, debe usar algo como hibernate validator jsr 303

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
 Set<ConstraintViolation<List<Searching>> violations = validator.validate(searchingList);
Naruto
fuente
¿Dónde pongo esto, al comienzo del método?
DavidR
sí ... al comienzo del método ... esta es solo una de las implementaciones de validación, podría haber otras también ...
Naruto
Okay. ¿Pero este significado de lo que ese código no cambiará si tengo o no la anotación @NotNull en el argumento param?
DavidR
Ahora tiene toda la Infracción en el conjunto, verifique su tamaño, si es mayor que cero, luego regrese del método.
Naruto
2

Hago esto para crear mi propia anotación de validación y validador:

ValidCardType.java(anotación para poner en métodos / campos)

@Constraint(validatedBy = {CardTypeValidator.class})
@Documented
@Target( { ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidCardType {
    String message() default "Incorrect card type, should be among: \"MasterCard\" | \"Visa\"";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Y, el validador para activar la verificación CardTypeValidator.java:

public class CardTypeValidator implements ConstraintValidator<ValidCardType, String> {
    private static final String[] ALL_CARD_TYPES = {"MasterCard", "Visa"};

    @Override
    public void initialize(ValidCardType status) {
    }
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return (Arrays.asList(ALL_CARD_TYPES).contains(value));
    }
}

Puedes hacer algo muy similar a comprobar @NotNull.

WesternGun
fuente
0

Para probar la validación de su método en una prueba, debe envolverlo como proxy en el método @Before.

@Before
public void setUp() {
    this.classAutowiredWithFindStuffMethod = MethodValidationProxyFactory.createProxy(this.classAutowiredWithFindStuffMethod);
}

Con MethodValidationProxyFactory como:

import org.springframework.context.support.StaticApplicationContext;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

public class MethodValidationProxyFactory {

private static final StaticApplicationContext ctx = new StaticApplicationContext();

static {
    MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
    processor.afterPropertiesSet(); // init advisor
    ctx.getBeanFactory()
            .addBeanPostProcessor(processor);
}

@SuppressWarnings("unchecked")
public static <T> T createProxy(T instance) {

    return (T) ctx.getAutowireCapableBeanFactory()
            .applyBeanPostProcessorsAfterInitialization(instance, instance.getClass()
                    .getName());
}

}

Y luego, agregue su prueba:

@Test
public void findingNullStuff() {
 assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> this.classAutowiredWithFindStuffMethod.findStuff(null));

}
Julien Feniou
fuente