Usando a anotação NotNull no argumento do método

156

Comecei a usar a @NotNullanotação com Java 8 e a obter resultados inesperados.

Eu tenho um método como este:

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

Eu escrevi um teste JUnit passando o valor nulo para o argumento searchList. Eu esperava que algum tipo de erro acontecesse, mas ocorreu como se a anotação não estivesse lá. Esse comportamento é esperado? Pelo que entendi, isso foi para permitir que você pule a gravação do código de verificação nulo padrão.

Uma explicação do que exatamente o @NotNull deveria fazer seria muito apreciada.

DavidR
fonte
29
@NotNullé apenas uma anotação. As anotações não fazem nada por si mesmas. Eles precisam de um processador de anotação no momento da compilação ou de algo que o processe no tempo de execução.
Sotirios Delimanolis
Você está executando o código dentro de um servidor de aplicativos (por exemplo, usando o Arquillian )?
precisa saber é o seguinte
1
@SotiriosDelimanolis - Então, qual é o objetivo, apenas um aviso para quem chama o método para não passar um valor nulo? Nesse caso, você ainda precisa do código de validação de ponteiro nulo.
precisa
1
veja o validador de hibernação
arisalexis
@ jabu.10245 - Não está usando nenhum servidor de aplicativos.
DavidR

Respostas:

183

@Nullablee @NotNullnão fazem nada por conta própria. Eles devem atuar como ferramentas de documentação.

A @Nullableanotação lembra você sobre a necessidade de introduzir uma verificação NPE quando:

  1. Chamando métodos que podem retornar nulos.
  2. Desreferenciando variáveis ​​(campos, variáveis ​​locais, parâmetros) que podem ser nulos.

A @NotNullanotação é, na verdade, um contrato explícito que declara o seguinte:

  1. Um método não deve retornar nulo.
  2. Uma variável (como campos, variáveis locais e parâmetros) não pode não deve ter valor nulo.

Por exemplo, em vez de escrever:

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

Você pode usar:

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

Além disso, @NotNullgeralmente é verificado pelos ConstraintValidators (por exemplo, na primavera e no hibernar).

A @NotNullanotação não faz nenhuma validação por si só porque a definição da anotação não fornece nenhuma ConstraintValidatorreferência de tipo.

Para mais informações, consulte:

  1. Validação de Bean
  2. NotNull.java
  3. Constraint.java
  4. ConstraintValidator.java
justAnotherUser ...
fonte
3
Então, apenas para esclarecer a parte 2 da parte NotNull, realmente deveria dizer "não deveria", não "não pode", pois não pode ser imposta? Ou se ele pode ser aplicado em tempo de execução, como você faria isso?
precisa
Sim, é um "não deveria" ... a implementação do método deve fazer cumprir o contrato.
justAnotherUser ...
1
Alternativamente, em Java 8, Optionalpode ser usado no lugar de @Nullem valores de retorno, e método de sobrecarga no lugar de @Nullem listas de parâmetros: dolszewski.com/java/java-8-optional-use-cases
Chad K
13
Acredito que a confusão vem do java doc da anotação NotNull: * The annotated element must not be {@code null}. * Accepts any type.e acho que a palavra deve ser substituída por should, mas novamente depende de como você a lê. Definitivamente, seria bom ter mais esclarecimentos #
Julian
@ Julian Eu acho que deve ser o termo certo, porque é uma regra, não uma recomendação. Se você usar a anotação em que não deve passar, nullmas seria permitido, está usando a anotação incorreta. O termo não implica que seja validado. No entanto, uma dica de que não é validada não faria mal. Se você deseja adicionar a validação automática, pode usar algumas ferramentas externas. Por exemplo, o IntelliJ IDE possui suporte interno para injetar verificações nulas.
JojOatXGME
27

Como mencionado acima @NotNull, nada faz por si só. Uma boa maneira de usar @NotNullseria usá-lo comObjects.requireNonNull

public class Foo {
    private final Bar bar;

    public Foo(@NotNull Bar bar) {
        this.bar = Objects.requireNonNull(bar, "bar must not be null");
    }
}
Bollywood
fonte
6
Apenas uma dica. Você também pode escrever tais atribuições com uma linha:this.bar = Objects.requireNonNull(bar, "bar must not be null");
lolung
Obrigado pela dica @lolung - Agora atualizei o código acima, com base no seu comentário.
Bollywood
6

SO @NotNull é apenas uma tag ... Se você deseja validá-lo, deve usar algo como o hibernate validator jsr 303

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
 Set<ConstraintViolation<List<Searching>> violations = validator.validate(searchingList);
Naruto
fonte
Onde eu coloco isso, no começo do método?
precisa
yes..at o início do método ... esta é apenas uma das implementações de validação, pode haver outros também ...
Naruto
Está bem. Mas esse significado do que esse código não muda se eu tenho ou não a anotação @NotNull no argumento param?
DavidR
Agora você tem toda a violação no conjunto, verifique seu tamanho, se for maior que zero, depois retorne do método
Naruto
3

Se você estiver usando o Spring, poderá forçar a validação anotando a classe com @Validated:

import org.springframework.validation.annotation.Validated;

Mais informações disponíveis aqui: Validação Javax @NotNull uso da anotação

sisanared
fonte
2

Eu faço isso para criar minha própria anotação e validador de validação:

ValidCardType.java(anotação para colocar em 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 {};
}

E, o validador para acionar a verificação 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));
    }
}

Você pode fazer algo muito semelhante para verificar @NotNull.

WesternGun
fonte
0

Para testar sua validação de método em um teste, você deve agrupar um proxy no método @Before.

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

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

}

E então, adicione seu teste:

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

}
Julien Feniou
fonte