Utilizzo dell'annotazione NotNull nell'argomento del metodo


156

Ho appena iniziato a utilizzare l' @NotNullannotazione con Java 8 e ho ottenuto risultati inaspettati.

Ho un metodo come questo:

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

Ho scritto un test JUnit passando il valore Null per l'argomento researchList. Mi aspettavo che accadesse qualche tipo di errore, ma è passato come se l'annotazione non fosse presente. Questo comportamento è previsto? Da quello che ho capito, questo era per permetterti di saltare la scrittura del codice di controllo nullo della caldaia.

Una spiegazione di cosa dovrebbe fare esattamente @NotNull sarebbe molto apprezzata.


29
@NotNullè solo un'annotazione. Le annotazioni non fanno nulla da sole. Hanno bisogno di un processore di annotazione in fase di compilazione o di qualcosa che lo elabori in fase di esecuzione.
Sotirios Delimanolis,

Stai eseguendo il codice all'interno di un application server (ad esempio usando Arquillian )?
jabu.10245,

1
@SotiriosDelimanolis - Allora qual è il punto, solo un avvertimento a chiunque chiami il metodo per non passare un valore nullo? In tal caso è ancora necessario il codice di convalida del puntatore null.
David R

1
guarda hibernate validator
arisalexis,

@ jabu.10245 - Non si utilizza alcun server applicazioni.
David R

Risposte:


183

@Nullablee @NotNullnon fanno nulla da soli. Dovrebbero fungere da strumenti di documentazione.

L' @Nullableannotazione ricorda la necessità di introdurre un controllo NPE quando:

  1. Chiamare metodi che possono restituire null.
  2. Dereferenziazione di variabili (campi, variabili locali, parametri) che possono essere nulle.

L' @NotNullannotazione è, in realtà, un contratto esplicito che dichiara quanto segue:

  1. Un metodo non deve restituire null.
  2. Una variabile (come campi, variabili locali e parametri) non può non deve tenere il valore nullo.

Ad esempio, invece di scrivere:

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

Puoi usare:

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

Inoltre, @NotNullviene spesso controllato da ConstraintValidators (ad es. In primavera e in letargo).

L' @NotNullannotazione non esegue alcuna convalida da sola perché la definizione dell'annotazione non fornisce alcun ConstraintValidatorriferimento al tipo.

Per maggiori informazioni vedi:

  1. Convalida del fagiolo
  2. NotNull.java
  3. Constraint.java
  4. ConstraintValidator.java

3
Quindi, solo per chiarire la parte 2 della parte NotNull, in realtà dovrebbe dire "non dovrebbe", non "non può" dal momento che non può essere applicato forzatamente? O se può essere applicato in fase di esecuzione, come faresti?
David,

Sì, è un "non dovrebbe" ... l'implementazione del metodo dovrebbe far rispettare il contratto.
justAnotherUser ...

1
In alternativa, in Java 8, Optionalpotrebbe essere usato al posto di @Nullin valori di ritorno e il sovraccarico del metodo al posto di @Nullin elenchi di parametri: dolszewski.com/java/java-8-optional-use-cases
Chad K

13
Credo che la confusione deriva dal doc java dell'annotazione NotNull: * The annotated element must not be {@code null}. * Accepts any type.e penso must parola dovrebbe essere sostituito con dovrebbe , ma ancora una volta dipende di come lo si legge. Sicuramente sarebbe meglio avere qualche altro chiarimento
Julian,

@Julian Penso che debba essere il termine giusto perché è una regola, non una raccomandazione. Se usi l'annotazione in cui non dovresti passare nullma sarebbe consentita, stai usando l'annotazione errata. Il termine non implica che sia validato. Tuttavia, un suggerimento che non è convalidato non farebbe male. Se si desidera aggiungere la convalida automatica, è possibile utilizzare alcuni strumenti esterni. Ad esempio, l' IDE IntelliJ ha il supporto integrato per iniettare controlli null.
JojOatXGME,

27

Come accennato in precedenza @NotNull, non fa nulla da solo. Un buon modo di usare @NotNullsarebbe 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");
    }
}

6
Solo un consiglio Puoi anche scrivere tali incarichi con una riga:this.bar = Objects.requireNonNull(bar, "bar must not be null");
lolung

Grazie per la punta @lolung - Ora ho aggiornato il codice sopra riportato in base al tuo commento.
Bollywood,


6

SO @NotNull è solo un tag ... Se si desidera convalidarlo, è necessario utilizzare qualcosa di simile a hibernate validator jsr 303

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
 Set<ConstraintViolation<List<Searching>> violations = validator.validate(searchingList);

Dove lo metto, all'inizio del metodo?
David R

si ... all'inizio del metodo ... questa è solo una delle implementazioni di validazione, potrebbero essercene anche altre ...
Naruto,

Ok. Ma questo significato di ciò che fa quel codice non cambierà se ho o meno l'annotazione @NotNull nell'argomento param?
David R

Ora hai tutte le violazioni nel set, controlla le sue dimensioni, se è maggiore di zero, quindi ritorna dal metodo.
Naruto,


2

Faccio questo per creare la mia annotazione e validatore di convalida:

ValidCardType.java(annotazione per mettere su metodi / campi)

@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, il validatore per attivare il controllo 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));
    }
}

Puoi fare qualcosa di molto simile da controllare @NotNull.


0

Per testare la convalida del metodo in un test, è necessario racchiuderlo in un proxy nel metodo @Before.

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

Con MethodValidationProxyFactory come:

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 quindi aggiungi il tuo test:

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

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.