在方法参数中使用NotNull注释


156

我刚刚开始在@NotNullJava 8中使用注释,并获得了一些意外的结果。

我有这样的方法:

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

我编写了一个JUnit测试,为参数searchList传递了空值。我原以为会发生某种类型的错误,但好像没有注释就通过了。这是预期的行为吗?据我了解,这是允许您跳过编写样板为空的检查代码。

对于@NotNull应该做什么的解释将不胜感激。


29
@NotNull只是一个注释。注释本身不会执行任何操作。他们需要在编译时使用注释处理器,或者在运行时对其进行处理的处理器。
Sotirios Delimanolis

您是否在应用程序服务器中运行代码(例如,使用Arquillian)?
jabu.10245

1
@SotiriosDelimanolis-那么意义何在,只是警告任何调用该方法的人不要传递空值?在这种情况下,您仍然需要空指针验证代码。
DavidR

1
看一下休眠验证器
arisalexis 2015年

@ jabu.10245-不使用任何应用程序服务器。
DavidR

Answers:


183

@Nullable@NotNull自己做什么。它们应该充当文档工具。

@Nullable注释提醒您在以下情况下必须进行NPE检查:

  1. 可以返回null的调用方法。
  2. 解引用可以为空的变量(字段,局部变量,参数)。

@NotNull注释是,实际上,在声明下面的一个明确的合同:

  1. 方法不应返回null。
  2. 变量(如字段,局部变量和参数)不能 不宜 持有空值。

例如,代替编写:

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

您可以使用:

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

另外,@NotNull通常由ConstraintValidators检查(例如,在春季和冬眠期间)。

@NotNull注释不会做自己的任何验证,因为注释定义不提供任何ConstraintValidator类型的参考。

有关更多信息,请参见:

  1. Bean验证
  2. NotNull.java
  3. Constraint.java
  4. ConstraintValidator.java

3
因此,仅为了澄清NotNull部分的第2部分,实际上它应该说“应该”,而不是“不能”,因为它不能强制执行?或者,如果可以在运行时实施它,您将如何处理?
DavidR 2015年

是的,它是“不应” ...方法实现应强制执行合同。
justAnotherUser ... 15/12/7

1
或者,在Java 8中,Optional可以使用in代替@Null返回值,而方法重载代替@Nullin参数列表:dolszewski.com/java/java-8-optional-use-cases
Chad K

13
我认为混淆来自NotNull批注的Java文档: * The annotated element must not be {@code null}. * Accepts any type.我认为必须将 word替换为should应该,但这又取决于您的阅读方式。肯定还有更多的澄清方法
朱利安(Julian)

@Julian我认为必须使用正确的术语,因为这是一个规则,而不是建议。如果在不应该通过null但可以通过的地方使用注释,则说明注释使用错误。该术语并不意味着它已得到验证。但是,暗示它未经验证不会有任何伤害。如果要添加自动验证,则可以使用一些外部工具。例如,IntelliJ IDE内置了注入空检查的支持。
JojOatXGME

27

如上所述@NotNull,它本身没有任何作用。一个很好的使用@NotNull方法是将它与Objects.requireNonNull

public class Foo {
    private final Bar bar;

    public Foo(@NotNull Bar bar) {
        this.bar = Objects.requireNonNull(bar, "bar must not be null");
    }
}

6
只是一个小费。您也可以用一行写这样的作业:this.bar = Objects.requireNonNull(bar, "bar must not be null");
lolung

感谢您的技巧@lolung-我现在根据您的评论更新了上面的代码。
宝莱坞


6

所以@NotNull只是一个标记...如果要验证它,则必须使用类似休眠验证器jsr 303的名称

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

在方法的开头,该放在哪里?
DavidR

是的..在方法开始时...这只是验证实现之一,可能还会有其他实现...
火影忍者

好。但是,无论我在param参数中是否具有@NotNull批注,该代码没有什么意义?
DavidR 2015年

现在,您已在集合中包含所有违规,检查其大小,如果其大于零,则从方法中返回。
火影忍者2015年

3

如果您使用的是Spring,则可以通过以下方式对类进行注释来强制验证@Validated

import org.springframework.validation.annotation.Validated;

此处提供更多信息: Javax验证@NotNull批注用法


2

我这样做是为了创建自己的验证批注和验证器:

ValidCardType.java(标注方法/字段)

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

并且,验证器触发检查 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));
    }
}

您可以执行与检查非常相似的操作@NotNull


0

要在测试中测试方法验证,您必须将其包装为@Before方法中的代理。

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

使用MethodValidationProxyFactory为:

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

}

然后,添加您的测试:

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

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.