Using null type annotations in practice Till Brychcy, Mercateo EclipseCon Europe, 2017
• What they are, why and when to use them • @Nullable vs. java.util.Optional • Configuration choices • Switching from declaration annotations to type annotations • How to get warning free code - Code Patterns and Antipatterns • Free Type variables and extends @Nullable • Arrays • Improvements released with Oxygen • Preview to Photon EclipseCon Europe, 2017
Some Statistics • First commit: Sep 18 2002 • First commit with null annotations: Jul 2 2012 • Switched to java 8 and null type annotations: Apr 15 2015 • Sample size: 7866 java files (approx. 20% of total code base) • 3025 files use @NonNullByDefault (no package annotations) • 10910 @Nullable annotation in 2175 files • 1970 @NonNull annotations in 709 files • 1070 @SuppressWarnings(„null") EclipseCon Europe, 2017
Before null annotations /** * * @param catalogID * @param groupID * @param searchSpec * (may be null) * @param minIndex * @param maxIndex * @param sortBy * @param sortAscending * @return String */ EclipseCon Europe, 2017
Null annotation advantages • One thing less to worry about • Code that is easier to understand, change and debug • No more NullPointerException • Fewer other bugs EclipseCon Europe, 2017
Null annotations disadvantages • Compiler sometimes needs help: Avoid some code patterns • Syntax a bit ugly EclipseCon Europe, 2017
Newer languages: nice syntax • kotlin • var x: String? = null; • swift • var x: String? = nil; • c# (announced for version 8) • string? x = null; EclipseCon Europe, 2017
Null Annotations • Normal use: @NonNullByDefault + @Nullable • @NonNull: only for type parameters and during migration EclipseCon Europe, 2017
Declaration vs. type annotations • Declaration annotation: @Target ({ FIELD , METHOD , PARAMETER , LOCAL_VARIABLE }) public @interface Nullable { } • Type annotation: @Target ({ TYPE_USE }) public @interface Nullable { } • "Mixed" annotations are allowed by Java, but bad as null annotations: @Target ({ TYPE_USE , FIELD , METHOD , PARAMETER , LOCAL_VARIABLE }) public @interface Nullable { } EclipseCon Europe, 2017
Declaration annotation usages @NonNullByDefault public class Example { @Nullable String field; @Nullable String add( @Nullable String arg1, List<String> list, @Nullable String[] array) { // just to show the syntax (local variables usually don't need annotations) @Nullable String local = arg1; list.add(null); // is this OK? if(array != null) { array[0] = null; // or this? } return local; } } EclipseCon Europe, 2017
Equivalent with type annotations @NonNullByDefault ({ FIELD , PARAMETER , RETURN_TYPE }) public class Example { @Nullable String field; @Nullable String add( @Nullable String arg1, List<String> list, String @Nullable [] array) { // just to show the syntax (local variables usually don't need annotations) @Nullable String local = arg1; list.add(null); // is this OK? if(array != null) { array[0] = null; // or this? } return local; } } EclipseCon Europe, 2017
Completely annotated @NonNullByDefault ({ FIELD , PARAMETER , RETURN_TYPE , ARRAY_CONTENTS , TYPE_ARGUMENT }) public class Example { @Nullable String field; @Nullable String add( @Nullable String arg1, List< @Nullable String> list, @Nullable String @Nullable [] array) { // just to show the syntax (local variables usually don't need annotations) @Nullable String local = arg1; list.add(null); // OK! if (array != null) { array[0] = null; // OK! } return local; } } EclipseCon Europe, 2017
@NonNullByDefault defaults public enum DefaultLocation { PARAMETER , RETURN_TYPE , FIELD , TYPE_PARAMETER , TYPE_BOUND , TYPE_ARGUMENT , ARRAY_CONTENTS } @Target ({ ElementType . PACKAGE , ElementType . TYPE , ElementType . METHOD , ElementType . CONSTRUCTOR , ElementType . FIELD , ElementType . LOCAL_VARIABLE }) public @interface NonNullByDefault { DefaultLocation [] value() default { DefaultLocation . PARAMETER , DefaultLocation . RETURN_TYPE , DefaultLocation . FIELD , DefaultLocation . TYPE_ARGUMENT , DefaultLocation . TYPE_BOUND }; } EclipseCon Europe, 2017
Type bounds: "extends" • Think in types: Object x = ""; // OK String s = new Object(); // error @Nullable String s1 = ""; // OK @NonNull String s2 = null; // error ⇒ "String extends Object" corresponds to "@NonNull String extends @Nullable String" • So: "@NonNull String extends @NonNull Object" "@NonNull String extends @Nullable Object" "@Nullable String extends @Nullable Object" EclipseCon Europe, 2017
Free Type variables and extends @Nullable With standard @NonNullByDefault: • class List<T> {…} T can be @Nullable or @NonNull • user chooses List<@Nullable Integer> or List<String> (=List<@NonNull String>) • class NumberList<T extends Number>{…} T must be @NonNull • same as class NumberList<T extends @NonNull Number> • same as NumberList<@NonNull T> • class NumberList<@Nullable Number>: T must be @Nullable • class NumberList<T extends @Nullable Number>: T can be @Nullable or @NonNull EclipseCon Europe, 2017
Principles • Warning free workspace • @NonNullByDefault in new code • Add @NonNullByDefault to existing code with other changes • If necessary, annotate related code • @SuppressWarnings("null") is OK in certain situations • Must be easy to use with maven (now: no maven settings at all, IDE-only) EclipseCon Europe, 2017
@SuppressWarnings • Generated code: hashCode & equals • stream.filter(x->x != null). • optional.orElse(null) • Tests (still add @NonNullByDefault) • Overrides for which we don’t want to add external annotations EclipseCon Europe, 2017
Configuration choices • Enable annotations based analysis in workspace settings (not project) • File template with @NonNullByDefault • DefaultLocation as favorites • External annotations added to the JDK in Workspace • Hide INFO in problems view EclipseCon Europe, 2017
EclipseCon Europe, 2017
EclipseCon Europe, 2017
Why custom annotations • Di ff erent defaults for @NonNullByDefault (e.g. exclude FIELD) • @Retention(RUNTIME) for testing framework • Easier to accept for users of other IDEs EclipseCon Europe, 2017
Workspace-wide EEA for the JDK EclipseCon Europe, 2017
EclipseCon Europe, 2017
EclipseCon Europe, 2017
Challenges when switching to type annotations • Syntax for qualified names java.io.@Nullable File file (easy to fix) • Syntax for Arrays (we used some regular expressions) • Generics: • Map.get() (configure external annotations) • Generics that take a .class literal EclipseCon Europe, 2017
Arrays • @Nullable String @NonNull [] x; • Problem during migration from declaration annotations • Problem: new @NonNull String[10] contains nulls • methods that don't care about nullness about array contents <T extends @Nullable String> @NonNull String concat(T[] strings) EclipseCon Europe, 2017
Observed null parameter handling 1. Don't think about it, let method caller guess 2. if(param==null) return null 3. Objects.requireNonNull(param) 4. try{…}catch(Exception e){…} 5. Assume nonnull, use javadoc for nullable EclipseCon Europe, 2017
Some Antipatterns • No correlation analysis • boolean b=(x != null); if(b) {x.someMethod()} • int length = array == null ? 0 : array.length; for (int i = 0; i < length; i++)… • No intraprocedural analysis: • init(…)-methods in constructor • if(isValid(x)) { x.something() } boolean isValid(Some x) {return x != null && …} • Event callbacks without context: e.g., org.xml.sax.ContentHandler • Struts form beans • some builder patterns EclipseCon Europe, 2017
Some Good Patterns • Empty string / collection / NOP-implementation instead of null • final fields or even completely immutable fields • Avoid null literals except to define class specific NULL constants EclipseCon Europe, 2017
@Nullable vs. java.util.Optional • Two solutions for the same topic. • "Don't care": Convenient mapping to other Optionals • Code with Optional gets reliable with @NonNullByDefault, so use both • Use Optional only for return values • Optional is not Serializable • @Nullable better when overriding methods • Problem Optional#orElse (Guava had: #orNull) EclipseCon Europe, 2017
Improvements in Oxygen • 54 bug fixes and enhancements related to null analysis and null annotations • Quick Fix to move type annotations • @NonNullByDefault • DefaultLocation.ARRAY_CONTENTS implemented • @Target(ElementType.FIELD) implemented • @Target(ElementType.LOCAL_VARIABLE) implemented • No warning for "T extends @Nullable String" • Many quick assists avoid creating redundant @NonNull EclipseCon Europe, 2017
Recommend
More recommend