Utilisation de l'annotation NotNull dans l'argument de méthode

156

Je viens de commencer à utiliser l' @NotNullannotation avec Java 8 et à obtenir des résultats inattendus.

J'ai une méthode comme celle-ci:

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

J'ai écrit un test JUnit en passant la valeur nulle pour l'argument searchList. Je m'attendais à ce qu'une erreur se produise, mais elle s'est déroulée comme si l'annotation n'était pas là. Est-ce un comportement attendu? D'après ce que j'ai compris, c'était pour vous permettre d'éviter d'écrire le code de contrôle nul standard.

Une explication de ce que @NotNull est censé faire exactement serait grandement appréciée.

DavidR
la source
29
@NotNullest juste une annotation. Les annotations ne font rien par elles-mêmes. Ils ont besoin d'un processeur d'annotation au moment de la compilation ou de quelque chose qui le traite au moment de l'exécution.
Sotirios Delimanolis
Exécutez-vous le code dans un serveur d'applications (par exemple en utilisant Arquillian )?
jabu.10245
1
@SotiriosDelimanolis - Alors à quoi ça sert, juste un avertissement à quiconque appelle la méthode pour ne pas passer une valeur nulle? Dans ce cas, vous avez toujours besoin du code de validation du pointeur nul.
DavidR
1
regardez le validateur d'hibernation
arisalexis
@ jabu.10245 - N'utilise aucun serveur d'applications.
DavidR

Réponses:

183

@Nullableet @NotNullne rien faire de leur propre chef. Ils sont censés agir comme des outils de documentation.

L' @Nullableannotation vous rappelle la nécessité d'introduire une vérification NPE lorsque:

  1. Appel de méthodes qui peuvent renvoyer null.
  2. Déréférencer les variables (champs, variables locales, paramètres) pouvant être nulles.

L' @NotNullannotation est, en fait, un contrat explicite déclarant ce qui suit:

  1. Une méthode ne doit pas retourner null.
  2. Une variable (comme les champs, les variables locales et les paramètres) ne peut pas ne doit pas contenir de valeur nulle.

Par exemple, au lieu d'écrire:

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

Vous pouvez utiliser:

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

De plus, il @NotNullest souvent vérifié par ConstraintValidators (par exemple au printemps et en veille prolongée).

L' @NotNullannotation n'effectue aucune validation par elle-même car la définition d'annotation ne fournit aucune ConstraintValidatorréférence de type.

Pour plus d'informations, voir:

  1. Validation du bean
  2. NotNull.java
  3. Constraint.java
  4. ConstraintValidator.java
justAnotherUser ...
la source
3
Donc, juste pour clarifier la partie 2 de la partie NotNull, vraiment il devrait dire "ne devrait pas", pas "ne peut pas" car il ne peut pas être appliqué? Ou si elle peut être appliquée au moment de l'exécution, comment procéderiez-vous?
DavidR
Oui, c'est un "ne devrait pas" ... la mise en œuvre de la méthode devrait appliquer le contrat.
justAnotherUser ...
1
Alternativement, dans Java 8, Optionalpourrait être utilisé à la place des @Nullvaleurs de retour et une surcharge de méthode à la place des @Nulllistes de paramètres: dolszewski.com/java/java-8-optional-use-cases
Chad K
13
Je crois que la confusion vient du document java de l'annotation NotNull: * The annotated element must not be {@code null}. * Accepts any type.et je pense que must word devrait être remplacé par should mais encore une fois cela dépend de la façon dont vous le lisez. Il serait certainement bon d'avoir plus de précisions
Julian
@Julian Je pense que « must» est le bon terme car c'est une règle, pas une recommandation. Si vous utilisez l'annotation là où vous ne devriez pas la transmettre, nullmais qu'elle serait autorisée, vous utilisez l'annotation de manière incorrecte. Le terme n'implique pas qu'il est validé. Cependant, un indice selon lequel il n'est pas validé ne ferait pas de mal. Si vous souhaitez ajouter une validation automatique, vous pouvez utiliser certains outils externes. Par exemple, IntelliJ IDE a une prise en charge intégrée pour injecter des vérifications nulles.
JojOatXGME
27

Comme mentionné ci-dessus, @NotNullne fait rien de lui-même. Une bonne façon d'utiliser @NotNullserait de l'utiliser avecObjects.requireNonNull

public class Foo {
    private final Bar bar;

    public Foo(@NotNull Bar bar) {
        this.bar = Objects.requireNonNull(bar, "bar must not be null");
    }
}
Bollywood
la source
6
Juste un conseil. Vous pouvez également écrire de tels devoirs avec une seule ligne:this.bar = Objects.requireNonNull(bar, "bar must not be null");
lolung
Merci pour le conseil @lolung - J'ai maintenant mis à jour le code ci-dessus extrait en fonction de votre commentaire.
Bollywood
6

SO @NotNull est juste une balise ... Si vous voulez la valider, vous devez utiliser quelque chose comme le validateur d'hibernation jsr 303

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
 Set<ConstraintViolation<List<Searching>> violations = validator.validate(searchingList);
Naruto
la source
Où dois-je mettre cela, au début de la méthode?
DavidR
oui..au début de la méthode ... ce n'est qu'une des implémentations de validation, il pourrait y en avoir d'autres aussi ...
Naruto
D'accord. Mais cette signification de ce que ce code ne changera pas, que j'aie ou non l'annotation @NotNull dans l'argument param?
DavidR
Maintenant que vous avez toute la violation dans l'ensemble, vérifiez sa taille, si elle est supérieure à zéro, puis revenez de la méthode.
Naruto
2

Je fais cela pour créer ma propre annotation de validation et validateur:

ValidCardType.java(annotation à mettre sur les méthodes / champs)

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

Et, le validateur pour déclencher le contrôle 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));
    }
}

Vous pouvez faire quelque chose de très similaire pour vérifier @NotNull.

WesternGun
la source
0

Pour tester la validation de votre méthode dans un test, vous devez l'envelopper d'un proxy dans la méthode @Before.

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

Avec MethodValidationProxyFactory comme:

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

}

Et puis, ajoutez votre test:

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

}
Julien Feniou
la source