Verwenden von NotNull Annotation im Methodenargument

156

Ich habe gerade angefangen, das zu benutzen @NotNull Annotation mit Java 8 zu verwenden und einige unerwartete Ergebnisse zu erhalten.

Ich habe eine Methode wie diese:

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

Ich habe einen JUnit-Test geschrieben, der den Nullwert für das Argument searchList übergeben hat. Ich hatte erwartet, dass ein Fehler auftritt, aber er ging durch, als ob die Anmerkung nicht vorhanden wäre. Ist das erwartetes Verhalten? Soweit ich verstanden habe, sollten Sie damit das Schreiben des Nullprüfcodes für das Boilerplate überspringen.

Eine Erklärung, was genau @NotNull tun soll, wäre sehr dankbar.

DavidR
quelle
29
@NotNullist nur eine Anmerkung. Anmerkungen machen nichts für sich. Sie benötigen einen Annotationsprozessor zur Kompilierungszeit oder etwas, das ihn zur Laufzeit verarbeitet.
Sotirios Delimanolis
Führen Sie den Code auf einem Anwendungsserver aus (z. B. mit Arquillian )?
jabu.10245
1
@SotiriosDelimanolis - Worum geht es dann, nur um eine Warnung an alle, die die Methode aufrufen, keinen Nullwert zu übergeben? In diesem Fall benötigen Sie noch den Validierungscode für den Nullzeiger.
DavidR
1
Schauen Sie sich Hibernate Validator
Arisalexis
@ jabu.10245 - Verwenden Sie keinen Anwendungsserver.
DavidR

Antworten:

183

@Nullableund @NotNullnichts alleine machen. Sie sollen als Dokumentationswerkzeuge dienen.

Die @NullableAnmerkung erinnert Sie an die Notwendigkeit, eine NPE-Prüfung einzuführen, wenn:

  1. Aufruf von Methoden, die null zurückgeben können.
  2. Dereferenzieren von Variablen (Felder, lokale Variablen, Parameter), die null sein können.

Die @NotNullAnmerkung ist eigentlich ein ausdrücklicher Vertrag, der Folgendes erklärt:

  1. Eine Methode sollte nicht null zurückgeben.
  2. Eine Variable (wie Felder, lokale Variablen und Parameter) kann nicht nicht Null - Wert halten.

Zum Beispiel anstatt zu schreiben:

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

Sie können verwenden:

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

Darüber hinaus @NotNullwird häufig von ConstraintValidators überprüft (z. B. im Frühjahr und im Ruhezustand).

Die @NotNullAnnotation führt keine eigene Validierung durch, da die Annotationsdefinition keine bereitstelltConstraintValidator .

Weitere Informationen finden Sie unter:

  1. Bean-Validierung
  2. NotNull.java
  3. Constraint.java
  4. ConstraintValidator.java
justAnotherUser ...
quelle
3
Nur um Teil 2 des NotNull-Teils zu verdeutlichen, sollte er wirklich "sollte nicht" sagen, nicht "kann nicht", da er nicht durchgesetzt werden kann? Oder wenn es zur Laufzeit erzwungen werden kann, wie würden Sie das tun?
DavidR
Ja, es ist ein "sollte nicht" ... die Methodenimplementierung sollte den Vertrag durchsetzen.
justAnotherUser ...
1
Alternativ könnte in Java 8 Optionalanstelle von @NullRückgabewerten und Methodenüberladung anstelle von @NullParameterlisten verwendet werden: dolszewski.com/java/java-8-optional-use-cases
Chad K
13
Ich glaube, die Verwirrung kommt aus dem Java-Dokument der NotNull-Annotation: * The annotated element must not be {@code null}. * Accepts any type.und ich denke, das Wort muss durch sollte ersetzt werden, aber es hängt wieder davon ab, wie Sie es lesen. Auf jeden Fall wären weitere Klarstellungen gut zu haben
Julian
@ Julian Ich denke, Muss ist der richtige Begriff, weil es eine Regel ist, keine Empfehlung. Wenn Sie die Annotation dort verwenden, wo Sie nicht übergeben sollten null, dies aber zulässig wäre, verwenden Sie die Annotation falsch. Der Begriff bedeutet nicht, dass er validiert ist. Ein Hinweis, dass es nicht validiert ist, würde jedoch nicht schaden. Wenn Sie eine automatische Validierung hinzufügen möchten, können Sie einige externe Tools verwenden. Beispielsweise verfügt die IntelliJ- IDE über eine integrierte Unterstützung zum Einfügen von Nullprüfungen.
JojOatXGME
27

Wie oben erwähnt @NotNullmacht nichts für sich. Eine gute Art der Verwendung @NotNullwäre die Verwendung mitObjects.requireNonNull

public class Foo {
    private final Bar bar;

    public Foo(@NotNull Bar bar) {
        this.bar = Objects.requireNonNull(bar, "bar must not be null");
    }
}
Bollywood
quelle
6
Nur ein Tipp. Sie können solche Aufgaben auch mit einer Zeile schreiben:this.bar = Objects.requireNonNull(bar, "bar must not be null");
lolung
Vielen Dank für den Tipp @lolung - Ich habe jetzt den obigen Code basierend auf Ihrem Kommentar aktualisiert.
Bollywood
6

SO @NotNull ist nur ein Tag ... Wenn Sie es validieren möchten, müssen Sie so etwas wie den Ruhezustand-Validator jsr 303 verwenden

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
 Set<ConstraintViolation<List<Searching>> violations = validator.validate(searchingList);
Naruto
quelle
Wo lege ich das am Anfang der Methode hin?
DavidR
Ja ... zu Beginn der Methode ... dies ist nur eine der Validierungsimplementierungen, möglicherweise gibt es auch andere ...
Naruto
OK. Aber diese Bedeutung dessen, was dieser Code bewirkt, ändert nichts daran, ob ich die Annotation @NotNull im param-Argument habe oder nicht.
DavidR
Jetzt haben Sie alle Verstöße im Set, überprüfen Sie die Größe, wenn sie größer als Null ist, und kehren Sie dann von der Methode zurück.
Naruto
3

Wenn Sie Spring verwenden, können Sie die Validierung erzwingen, indem Sie die Klasse mit @Validatedfolgenden Anmerkungen versehen :

import org.springframework.validation.annotation.Validated;

Weitere Informationen finden Sie hier: Verwendung der Javax-Validierung @NotNull-Annotation

sisanared
quelle
2

Ich mache das, um meine eigene Validierungsanmerkung und Validierung zu erstellen:

ValidCardType.java(Anmerkung zum Anlegen von Methoden / Feldern)

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

Und, um der Prüfer die Prüfung auslösen: 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));
    }
}

Sie können etwas sehr Ähnliches tun, um dies zu überprüfen @NotNull.

WesternGun
quelle
0

Um Ihre Methodenvalidierung in einem Test zu testen, müssen Sie einen Proxy in die @ Before-Methode einbinden.

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

Mit MethodValidationProxyFactory als:

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

}}

Und dann fügen Sie Ihren Test hinzu:

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

}
Julien Feniou
quelle