Home > Software design >  Why doesn't @NotNull or @Nullable exist in my project?
Why doesn't @NotNull or @Nullable exist in my project?

Time:11-23

I am programming with IntelliJ, and I was just working on a Java project, and I wanted to make a parameter annotated as @NotNull. But IntelliJ says it doesn't exist. I've checked all the java files and it isn't in my project. I am so confused, and I am not using maven or gradle, just the default Java project. I have no idea what's happening.

Here is an example:

package com.company;


    public class Main {
         public static void main(String[] args){
                testF(null);
         }
         public static void testF (@NotNull Integer... numbers){
            for(Integer integer: numbers){
                   System.out.println(integer);
          }
     }
}

An error comes in saying "Cannot resolve symbol NotNul : 10" (again @ in front)

CodePudding user response:

Java itself does not ship with these annotations.

Instead, there are about 10 mutually incompatible takes on the idea, most of which work completely differently and apply different meanings to what NonNull means, where you can put it, and how it works.

Oof. This is very unfortunate as the basic idea of using annotations to add this information is vastly superior to Optional and such, given that it would be entirely backwards compatible and does not relegate existing code into the doldrums of obsolecence, unlike Optional.

So, find one you like, and include it in your project same way you include any third party dependency - usually by including it in your Maven/Gradle/Ant ivy/etc (your build file's list of dependencies).

Intellij has its own take on NonNull and Nullable. It's probably the most convenient. Its ideas about what these annotations mean are inferior1. Checker Framework is best, eclipse is a distant second best, and everything else (including intellij's) shares the third place spot. the best take is to be found in the Checker Framework, or, almost as good, eclipse's take on these annotations. However, I doubt intellij's null checking systems are capable of fully understanding its more advanced additions, such as @PolyNull, so this additional expressive power would be mostly wasted. As a bonus, intellij ships with a bunch of data about what the right nullity annotations would be on major libraries.

That last one is important: Most commonly used java libraries, including java.* itself, does not have these annotations, and working with half-null-annotated code is most definitely a highly frustrating exercise; the costs of doing that vastly outweigh the benefits. The only real solution is to 'fix' the libraries you use with the right nullity info, but this is a ton of work. Fortunately, intellij has done a lot of it for you.

I would expect (eclipse does this), that the quickfix (CMD 1 on macs, CTRL 1 on non-macs, at least, out of the box if my memory of default keyboard shortcuts serves me) includes 'automatically add eclipse's nullity annotations to the classpath' (or in your case, intellij's, of course). If that is somehow not appearing, This page from the intellij docs explain exactly how to add the org.jetbrains.annotations library, which contains their nullity annotations, to your project. In fact, these docs indicate that, indeed, the quickfix menu does offer you the option of automatically adding this library as solution to the error you get on your @NonNull node in your source code.

[1] Most takes on nullity annotations limit themselves considerably by allowing the annotation only on fields, methods (implying: What it returns), and parameters. However, one can have a definitely-not-null List of could-be-null Map instances, which Map definitely not null String to could be null Integer: @NonNull List<@Nullable Map<@NonNull String, @Nullable Integer>>. The annotation system is capable of letting you write that, but only if your annotations are set up solely for TYPE_USE. checker framework's and eclipse's nullity annotations work like that; most others do not, and are therefore less expressive. CheckerFramework goes a step further and lets you write the notion of 'either nullity is fine'. Just like generics has 3 forms (List<Integer>, and List<? super Integer> and List< extends Integer>, once generics are involved, 2 nullities (either never null, or definitely null is allowed) is no longer sufficient, you need more nullities. checker framework has @PolyNull will lets you link nullities: You can for example write this method in checkerframework but you cannot possibly write it properly typed with intellij's, or for that matter eclipse's:

public void duplicateFirstMatch(List<T> elems, Predicate<T> matcher);

where the idea is: This method runs the matcher against each element in the list, and upon a match, that element is added to the end of the list. This method can work if T is considered '@NonNull' (given that no nulls are in, null can never be added by this code, so the non-nullity of its elements cannot be violated by it), but it works just as well if T is @Nullable, provided the matcher is also of @Nullable T of course: Now this code might add null to the list but that's okay.

Thus T is neither Nullable nor NonNull, but the Ts mentioned in the signature do need to match up their nullities. @PolyNull solves this problem.

  • Related