Should I use @NotNull and @NonNull together?

PLee :

I am using javax.validation.constraints.NotNull for hiberbate checking null fields. Now I am using lombok to simplefy constructor codes, but I need to use @NonNull annotation to make the field included in the required fields constructor. Should I use them together?

rzwitserloot :

There are at least 4 completely different implications of what a nonnull annotation does. Most annotations only imply one of these different things. Therefore, yes, it can make sense to apply more than one such annotation. Unfortunately, the actual meanings of these annotations are quite muddied, because in my experience virtually no developers have considered the fact that they mean different things; in fact, most developers I've met are completely oblivious to the fact that nullity annotations are incredibly complicated and vaguely defined.

The type system interpretation ('cannot be!')

This interpretation is a type assertion that is trying to be as strong as the String in String x; is: It is impossible for that x variable to reference an object that isn't a string, and because it is impossible, naturally, it would be a completely useless idea to check this. Given that declaration, something like String z = (String) x is so useless it's a compiler warning, in fact.

And so it is with such an annotation. Examples of this are checkerframework, eclipse, and intellij's: org.checkerframeworkchecker.nullness.qual.NonNull, org.jetbrains.annotations.NotNull and eclipse's org.eclipse.jdt.annotation.NonNull all primarily imply this meaning (although the intellij one applies to params and methods, whereas checkerframework and eclipse are type_use annotations. I told you this is complicated :P).

These annotations, by implying 'cannot be', are used for write-time checking. For the same reason writing String x = 5; is an immediate, as you write that code and before you even save the file, red wavy underline in your IDE, so is writing, say, someMethod(map.get(key)), where someMethod's parameter is annotated with one of these NonNull annotations. It's not so much a " you shouldn't", it is a "you cannot do that; I won't even let you".

Of course, javac the compiler doesn't actually work that way, it's the IDEs and tools that are trying to make it look like that. Just like you would never cast a variable of type String to String, you actually don't consider such nullity annotations as implying that you should check. No; the mean: It isn't. It implies the check's already done and you don't need to check again.

It's complicated, of course: To dance around the fact that whether a java compiler will in fact stop you from breaking the implication of these nonnull annotations... some tools (such as kotlinc) will inject explicit nullchecks anyway. A bit like how generics can cause javac to inject some typechecks in places you never wrote an explicit cast, to work around the fact that generics are a compile-time bolt-on to retain backwards compatibility. The idea is to consider these implementation details you shouldn't think about.

The DB interpretation

When hibernate uses a class as a template in order to produce a CREATE TABLE SQL statement, it's useful to be able to use annotations to set up constraints and rules and the like. For the same reason you might want to mark a field as: "When turning this into an SQL column, tell the DB engine to put a unique index on this", you may want to mark it as: "When turning this into an SQL column, tell the DB engine to put a non-null constraint on it".

This is as related to the type system interpretation as guns and grandmas: You can have objects that represent rows in a DB but which wouldn't successfully save due to breaking a constraint all the time; for example, any auto-counting unid field usually is 0, and it wouldn't save like that (the DB engine upgrades that 0 into a 'nevermind that; insert without it and let the DB engine's sequence fill in this number). So it is with these: It really doesn't mean anything to java at all; the SQL engine will take care of indicating that the insert/update fails due to a failing constraint. Of course, to save a roundtrip to the DB and because this just isn't allowed to be simple, most DB frameworks will nullcheck as you call the equivalent of .save() or .store(). But that's just a shortcut to that DB constraint.

The validation interpretation

Sometimes objects in java represent an external thing. Such as a DB row (overlapping with the previous meaning somewhat), or, say, a web form as submitted by a user. Such objects SHOULD represent precisely the actual state of the actual external thing it represents. Warts and invalid gobbledygook and all.

And usually you have a framework that lets you validate these. That's what validation nonnull would mean: This field can be null, and if you try to set it to null, no exceptions will occur (hence, they can be). However, as long as that field remains null, if you ask me if the object is valid, the answer is: No.

The lombok interpretation

Unfortunately, lombok muddies the waters somewhat.. just like all the other frameworks do. What lombok's @NonNull means depends on where it shows up. If on a parameter, it means: Lombok, please generate an explicit nullcheck as first line in the method, unless I wrote it already myself.

On fields, it means: "For the sake of @RequiredArgsConstructor, consider this field required; nullcheck it (throwing an exception) in the generated constructor. Also, copy the nullity annotation where relevant, and therefore, if a setter is made for this field.. add that nullcheck inside."

Your specific case

Given that nullity annotations mean completely different things, it is therefore potentially not crazy to apply more than one such annotation to the same construct in your code. If you want the DB engine to generate an SQL NON NULL constraint if this class is used as template, and for any object that represents a row in this table to immediately reject any attempt to put it in an invalid state, adding both can make sense. If you prefer that it is acceptable for objects to represent invalid state, which is almost always the case if the general principle is to construct a blank object and then set each field value one at a time with a setter – then don't add lombok's annotation.

Surely you've covered all complexity, right?

Not even a glimmer. For a real brain twister, consider checkerframework's @PolyNull, and consider that this is an oversimplification of what a proper typesystem ought to truly be able to handle!

DISCLAIMER: I'm a core contributor for Project Lombok.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=17043&siteId=1