在方法参数中使用NotNull注解

239

我刚开始使用Java 8中的@NotNull注解并且得到了一些意料之外的结果。

我有这样一个方法:

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

我写了一个JUnit测试,将参数searchingList的值设置为null。我原本期望会发生某种类型的错误,但是它却没有报错,就好像注释并不存在一样。这是预期行为吗?据我的理解,@NotNull注释的作用是允许你跳过编写样板式的null检查代码。

非常希望能够解释一下@NotNull到底应该做什么。


48
@NotNull 只是一个注解。注解本身并没有作用,需要在编译时有一个注解处理器,或者在运行时进行处理。 - Sotirios Delimanolis
3
@SotiriosDelimanolis - 那么这有什么意义呢,只是提醒调用该方法的人不要传递空值吗?在这种情况下,您仍然需要进行空指针验证代码。 - DavidR
1
看一下 Hibernate Validator。 - arisalexis
@jabu.10245 - 不使用任何应用服务器。 - DavidR
需要将以下与编程相关的内容从英语翻译成中文。只返回翻译后的文本是不够的!“某人”需要激活它 - arisalexis
显示剩余4条评论
8个回答

281

@Nullable@NotNull本身并没有任何作用。它们被设计成文档工具。

@Nullable注解提醒您在以下情况下需要引入NPE检查:

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

@NotNull注解实际上是一个明确的合约,声明如下:

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

例如,不要写成:

/**
 * @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(例如在 Spring 和 Hibernate 中)进行检查。 @NotNull 注解本身不执行任何验证,因为 注解定义 不提供任何 ConstraintValidator 类型的引用。

更多信息请参见:

  1. Bean验证
  2. 注释类型NotNull
  3. 注释类型Constraint
  4. 接口ConstraintValidator

4
为了澄清NotNull部分的第二部分,实际上它应该说“不应该”而不是“不能”,因为它无法强制执行?或者如果它可以在运行时执行,你会怎么做? - DavidR
2
是的,这是一个“不应该”...方法实现应该强制执行合同。 - Lucas Oliveira
2
另外,在Java 8中,可以使用Optional替代返回值中的@Null,并在参数列表中使用方法重载来替代@Null: http://dolszewski.com/java/java-8-optional-use-cases/ - Chad K
18
我认为混淆是来自于NotNull注释的Java文档: `* The annotated element must not be {@code null}.
  • Accepts any type.` 我认为“must”这个词应该被替换成“should”,但是这也取决于你如何阅读它。明确一些更多的解释会很有帮助。
- Julian
@Julian 我认为“必须”是正确的术语,因为它是一条规则,而不是建议。如果您使用注释,在那里您“不应该”传递null但允许它,那么您正在错误地使用注释。该术语并不意味着它已经过验证。然而,暗示它未经验证也无妨。如果您想添加自动验证,可以使用一些外部工具。例如,IntelliJ IDE内置支持注入空检查。 - JojOatXGME
1
@Julian,“must not”似乎是正确的。它不能包含null,否则调用可能无法正常工作或产生错误的结果。“cannot”会表明它将被拒绝,如果注释强制执行它,则使用这个词是正确的,但实际上并没有这样的限制。因此,使用的术语是正确的。 - Frank Hopkins

42

如上所述,@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");
    }
}

如果bar字段被NotNull注释,对Objects.requireNonNull的调用仍将生成警告,因为requireNonNull方法不返回NotNull引用。 - cquezel
我需要这个!!! - normidar

29

7
如果您正在使用Spring,可以通过在类上注释@Validated来强制验证:
import org.springframework.validation.annotation.Validated;

更多信息请参见: Javax @NotNull注释用法 您也可以使用projectlombok(lombok.NonNull)中的@NonNull

6

因此,@NotNull只是一个标签...如果您想验证它,那么您必须使用类似于Hibernate Validator JSR 303的工具。

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

1
我应该把这个放在哪里,方法的开头吗? - DavidR
是的,在方法开头处...这只是验证实现之一,可能还有其他的... - Naruto
好的。但是,无论我在参数参数中是否有@NotNull注释,代码的意义都不会改变? - DavidR
1
现在您已经拥有了集合中的所有违规项,请检查其大小,如果大于零,则从方法返回。 - Naruto

4

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

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

1
为了在测试中验证您的方法,您需要在@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));

}

-3
I resolved it with

@JsonSetter(nulls = Nulls.AS_EMPTY)
@NotBlank
public String myString;

Request Json:
{
  myString=null
}
 Response:
 error must not be blank

你能提供更多的解释来帮助解决提问者的问题吗? - josaphatv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接