Search code examples
javaannotationsnullable

Valid annotation for parent is invalid for child


Hello I have parent class with argument with nullable annotation.

class Parent {

  @Nullable
  String name;

  Parent(@Nullable Strign name) {
    this.name = name;
  }

  Driver createDriver() {
    return new CommonDriver(name);
  }
}

I have multiple children classes, for most of them, the "name" argument can be null, but there are some for which it cannot.

class ChildC extends Parent {

  ChildC(@NotNull String name){
    super(name);
  }

  @Override
  Driver createDriver() {
    return new ChildCDriver(name);
  }
}

and now I have issue (code inspection from intelliJ) inside ChildCDriver where name is @NotNull

Can this be solved somehow ?


Solution

  • This is reasonable code, but IntelliJ is not powerful enough to prove that the code is correct. You need to suppress the warning. Click on the line, press Alt+Enter, and find "Suppress" in that menu or a submenu.

    The Nullness Checker can verify your code. The complete code appears below. The @FieldInvariant annotation expresses that the field has a more precise type in the subclass.

    Without the @FieldInvariant annotation, the Nullness Checker issues this warning on line 27:

    error: [argument.type.incompatible] incompatible types in argument.
        return new ChildCDriver(name);
                                ^
      found   : @Initialized @Nullable String
      required: @Initialized @NonNull String
    

    With the @FieldInvariant annotation, the Nullness Checker proves the code is correct.

    The below code example uses the Checker Framework's @NonNull and @Nullable annotations, but the Nullness Checker also supports @NotNull so you can continue using JetBrains annotations in your code.

    import org.checkerframework.checker.nullness.qual.NonNull;
    import org.checkerframework.checker.nullness.qual.Nullable;
    import org.checkerframework.framework.qual.FieldInvariant;
    
    class Parent {
    
      final @Nullable String name;
    
      Parent(@Nullable String name) {
        this.name = name;
      }
    
      Driver createDriver() {
        return new CommonDriver(name);
      }
    }
    
    @FieldInvariant(qualifier = NonNull.class, field = "name")
    class ChildC extends Parent {
    
      ChildC(@NonNull String name) {
        super(name);
      }
    
      @Override
      Driver createDriver() {
        return new ChildCDriver(name);
      }
    }
    
    interface Driver {}
    
    class CommonDriver implements Driver {
      CommonDriver(@Nullable String name) {}
    }
    
    class ChildCDriver implements Driver {
      ChildCDriver(@NonNull String name) {}
    }