Which @NotNull Java annotation should I use?

前端 未结 22 2804
梦如初夏
梦如初夏 2020-11-22 02:44

I\'m looking to make my code more readable as well as use tooling like IDE code inspection and/or static code analysis (FindBugs and Sonar) to avoid NullPointerExceptions.

相关标签:
22条回答
  • 2020-11-22 03:18

    Doesn't sun have their own now? What's this:
    http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Modules-com.sun/istack/com.sun.istack.internal.htm

    This seems to be packaged with all the versions of Java I've used within the last few years.

    Edit: As mentioned in the comments below, you probably don't want to use these. In that case, my vote is for the IntelliJ jetbrains annotations!

    0 讨论(0)
  • 2020-11-22 03:21

    Distinguish between static analysis and runtime analysis. Use static analysis for internal stuff, and runtime analysis for the public boundaries of your code.

    For things that should not be null:

    • Runtime check: Use "if (x == null) ..." (zero dependency) or @javax.validation.NotNull (with bean validation) or @lombok.NonNull (plain and simple) or guavas Preconditions.checkNotNull(...)

      • Use Optional for method return types (only). Either Java8 or Guava.
    • Static check: Use an @NonNull annotation

    • Where it fits, use @...NonnullByDefault annotations on class or package level. Create these annotations yourself (examples are easy to find).
      • Else, use @...CheckForNull on method returns to avoid NPEs

    This should give the best result: warnings in the IDE, errors by Findbugs and checkerframework, meaningful runtime exceptions.

    Do not expect static checks to be mature, their naming is not standardized and different libraries and IDEs treat them differently, ignore them. The JSR305 javax.annotations.* classes look like standard, but they are not, and they cause split packages with Java9+.

    Some notes explanations:

    • Findbugs/spotbugs/jsr305 annotations with package javax.validation.* clash with other modules in Java9+, also possibly violate Oracle license
    • Spotbugs annotations still depends on jsr305/findbugs annotations at compiletime (at the time of writing https://github.com/spotbugs/spotbugs/issues/421)
    • jetbrains @NotNull name conflicts with @javax.validation.NotNull.
    • jetbrains, eclipse or checkersframework annotations for static checking have the advantage over javax.annotations that they do not clash with other modules in Java9 and higher
    • @javax.annotations.Nullable does not mean to Findbugs/Spotbugs what you (or your IDE) think it means. Findbugs will ignore it (on members). Sad, but true (https://sourceforge.net/p/findbugs/bugs/1181)
    • For static checking outside an IDE, 2 free tools exist: Spotbugs(formerly Findbugs) and checkersframework.
    • The Eclipse library has @NonNullByDefault, jsr305 only has @ParametersAreNonnullByDefault. Those are mere convenience wrappers applying base annotations to everything in a package (or class), you can easily create your own. This can be used on package. This may conflict with generated code (e.g. lombok).
    • Using lombok as an exported dependency should be avoided for libraries that you share with other people, the less transitive dependencies, the better
    • Using Bean validation framework is powerful, but requires high overhead, so that's overkill just to avoid manual null checking.
    • Using Optional for fields and method parameters is controversial (you can find articles about it easily)
    • Android null annotations are part of the Android support library, they come with a whole lot of other classes, and don't play nicely with other annotations/tools

    Before Java9, this is my recommendation:

    // file: package-info.java
    @javax.annotation.ParametersAreNonnullByDefault
    package example;
    
    
    // file: PublicApi
    package example;
    
    public interface PublicApi {
    
        Person createPerson(
            // NonNull by default due to package-info.java above
            String firstname,
            String lastname);
    }
    
    // file: PublicApiImpl
    public class PublicApiImpl implements PublicApi {
        public Person createPerson(
                // In Impl, handle cases where library users still pass null
                @Nullable String firstname, // Users  might send null
                @Nullable String lastname // Users might send null
                ) {
            if (firstname == null) throw new IllagalArgumentException(...);
            if (lastname == null) throw new IllagalArgumentException(...);
            return doCreatePerson(fistname, lastname, nickname);
        }
    
        @NonNull // Spotbugs checks that method cannot return null
        private Person doCreatePerson(
                 String firstname, // Spotbugs checks null cannot be passed, because package has ParametersAreNonnullByDefault
                 String lastname,
                 @Nullable String nickname // tell Spotbugs null is ok
                 ) {
             return new Person(firstname, lastname, nickname);
        }
    
        @CheckForNull // Do not use @Nullable here, Spotbugs will ignore it, though IDEs respect it
        private Person getNickname(
             String firstname,
             String lastname) {
             return NICKNAMES.get(firstname + ':' + lastname);
        }
    }
    

    Note that there is no way to make Spotbugs raise a warning when a nullable method parameter is dereferenced (at the time of writing, version 3.1 of Spotbugs). Maybe checkerframework can do that.

    Sadly these annotations do not distinguish between the cases of a public method of a library with arbitrary callsites, and non-public methods where each callsite can be known. So the double meaning of: "Indicate that null is undesired, but prepare for null being passed nevertheless" is not possible in a single declaration, hence the above example has different annotations for the interface and the implementation.

    For cases where the split interface approach is not practical, the following approach is a compromise:

            public Person createPerson(
                    @NonNull String firstname,
                    @NonNull String lastname
                    ) {
                // even though parameters annotated as NonNull, library clients might call with null.
                if (firstname == null) throw new IllagalArgumentException(...);
                if (lastname == null) throw new IllagalArgumentException(...);
                return doCreatePerson(fistname, lastname, nickname);
            }
    

    This helps clients to not pass null (writing correct code), while returning useful errors if they do.

    0 讨论(0)
  • 2020-11-22 03:23

    If you are working on a big project, you may be better of creating your own @Nullable and/or @NotNull annotations.

    For example:

    @java.lang.annotation.Documented
    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)
    @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD,
                                  java.lang.annotation.ElementType.METHOD,    
                                  java.lang.annotation.ElementType.PARAMETER,
                                  java.lang.annotation.ElementType.LOCAL_VARIABLE})
    public @interface Nullable 
    {
    }
    

    If you use the correct retention policy, then the annotations won't be available at runtime. From that point of view, it is just an internal thing.

    Even though this is not a strict science, I think it makes most sense to use an internal class for it.

    • It is an internal thing. (no functional or technical impact)
    • With many many many usages.
    • IDE's like IntelliJ support custom @Nullable/@NotNull annotations.
    • Most frameworks prefer to use their own internal version as well.

    Additional Questions (see comments):

    How to configure this in IntelliJ ?

    Click the "police officer" in the lower right corner of the IntelliJ status bar. And click "Configure inspections" in the popup. Next ...

    0 讨论(0)
  • 2020-11-22 03:25

    Unfortunately, JSR 308 will not add more values than this project local Not Null suggestion here

    Java 8 will not come with a single default annotation or its own Checker framework. Similar to Find-bugs or JSR 305, this JSR is poorly maintained by a small bunch of mostly academic teams.

    No commercial power behind it, thus JSR 308 launches EDR 3 (Early Draft Review at JCP) NOW, while Java 8 is supposed to ship in less than 6 months:-O Similar to 310 btw. but unlike 308 Oracle has taken charge of that now away from its founders to minimize harm it'll do to the Java Platform.

    Every project, vendor and academic class like the ones behind the Checker Framework and JSR 308 will create its own proprietary checker annotation.

    Making source code incompatible for years to come, until a few popular compromises could be found and maybe added to Java 9 or 10, or via frameworks like Apache Commons or Google Guava;-)

    0 讨论(0)
  • 2020-11-22 03:25

    If you're developing for android, you're somewhat tied to Eclipse (edit: at time of writing, not anymore), which has its own annotations. It's included in Eclipse 3.8+ (Juno), but disabled by default.

    You can enable it at Preferences > Java > Compiler > Errors/Warnings > Null analysis (collapsable section at the bottom).

    Check "Enable annotation-based null analysis"

    http://wiki.eclipse.org/JDT_Core/Null_Analysis#Usage has recommendations on settings. However, if you have external projects in your workspace (like the facebook SDK), they may not satisfy those recommendations, and you probably don't want to fix them with each SDK update ;-)

    I use:

    1. Null pointer access: Error
    2. Violation of null specification: Error (linked to point #1)
    3. Potential null pointer access: Warning (otherwise facebook SDK would have warnings)
    4. Conflict between null annotations and null inference: Warning (linked to point #3)
    0 讨论(0)
  • 2020-11-22 03:26

    Android

    This answer is Android specific. Android has support package called support-annotations. This provides dozens of Android specific annotations and also provides common ones like NonNull, Nullable etc.

    To add support-annotations package, add the following dependency in your build.gradle:

    compile 'com.android.support:support-annotations:23.1.1'
    

    and then use:

    import android.support.annotation.NonNull;
    
    void foobar(@NonNull Foo bar) {}
    
    0 讨论(0)
提交回复
热议问题