2

I am new to Java 8 and trying out Null type annotations and Optional.

For my example below, I have used String rather than my class and am calling toUpperCase just to call something, in my case I actually call a function passing in a parameter (so don't think I can use :: operator and/or maps).

In Eclipse I have the Java - Compiler - Errors/Warnings - Null Analysis Errors turned on.

My test code below:

public void test1(@Nullable String s) {
    // the 2nd s2 has a Potential null pointer access error. 
    // I was hoping ifPresent would imply NonNull
    Optional.ofNullable(s).ifPresent(s2 -> s2.toUpperCase());
}

@Nullable 
public String getSomeString() {
    return null;
}

public void test2() {
    String s = getSomeString();

    // This is fine, unlike the first example, I would have assumed that
    // it would know s was still nullable and behave the same way.
    Optional.ofNullable(s).ifPresent(s2 -> s2.toUpperCase());
}

It would seem that using Eclipse type null annotations and Optional.ifPresent doesn't go well together.

Am I wasting my time trying to get something like this to work? Should I just revert back to assigning the getter to a temp var then checking if null, and if not call my function?

Nathan
  • 8,093
  • 8
  • 50
  • 76
cfnz
  • 196
  • 3
  • 14

2 Answers2

2

JDT's null analysis cannot know about the semantics of each and every method in JRE and other libraries. Therefore, no conclusions are drawn from seeing a call to ifPresent. This can be remedied by adding external annotations to Optional so that the analysis will see method ofNullable as

<T> Optional<@NonNull T> ofNullable(@Nullable T value)

External annotations are supported starting with Eclipse Mars, released June, 24, 2015. See Help: Using external null annotations.

The difference between the two variants in the question is due to how null analysis is integrated with Java 8 type inference: In variant (1) s has type @Nullable String. When this type is used during type inference, it is concluded that the argument to ifPresent is nullable, too. In variant (2) s has type String (although flow analysis can see that is may be null after the initialization from getSomeString). The unannotated type String is not strong enough to aid type inference to the same conclusion as variant (1) (although this could possibly be improved in a future version of JDT).

Stephan Herrmann
  • 7,963
  • 2
  • 27
  • 38
  • Thanks for that, it does add to my understanding. I have got Mars RC4 going, and configured the external null annotation files (thanks for that tip). I have added a non null annotation to ifPresent as you have shown, something seems to have worked as now the Eclipse error/warning has changed, but I still can't get it to work without errors/warnings. It now says "Contradictory null annotations: method was inferred as 'void ifPresent(Consumer<#NonNull ? super #Nullable String>)', but only one of '#NonNull' and '#Nullable' can be effective at any location". (I can't get rid of that #Nullable?) – cfnz Jun 19 '15 at 09:10
  • (Note, just to be clear, I used # for the 'at' sign since the 'at' symbol is not allowed in comments.) – cfnz Jun 19 '15 at 09:11
  • Sorry, I hadn't tried my suggestions. Indeed no nonnull type can be a super type of a nullable type. I've updated my answer to suggest a better way to annotate Optional. – Stephan Herrmann Jun 21 '15 at 10:53
  • Thanks, that worked great. (I still need to spend some time learning exactly how and why - have not got to grips with Type Annotations yet, but this has certainly helped me on my way.) – cfnz Jun 22 '15 at 02:23
1

First of: @Nullable seams not to be part of the public Java 8 SDK. Have a look at the package you imported: com.sun.istack.internal.Nullable.

Second: I have run both of your methods: test1(null) and test2() and nothing out of the ordinary happened. Everything was fine (as expected). So what did you observe?

run test1(null) => no execution of lambda expression.

run test2() => no execution of lambda expression.

I changed your code for testing in the following way:

public void test1(@Nullable String s) {
    Optional.ofNullable(s).ifPresent(s2 -> System.out.println("executed"));
}

public void test2() {
    String s = getSomeString();
    Optional.ofNullable(s).ifPresent(s2 -> System.out.println("executed"));
}
Harmlezz
  • 7,972
  • 27
  • 35
  • 1
    @Nullable not part of the public Java 8 SDK... true, I imported org.eclipse.jdt.annotation related classes (is there a more standard alternative?). – cfnz Jun 18 '15 at 21:14
  • In terms of the problem with the code, I assume it is more to do with the IDE. With the IDE settings I mentioned, the line below where I commented has a red underline on the 2nd s2 and when trying to run, Eclipse says there is a problem, are you sure you want to proceed. If you choose to proceed, then it runs, and as you mentioned does what it should. So it is more the IDE that seems wrong (unless I turn off the annotation null checking which seems to defeat the whole purpose). – cfnz Jun 18 '15 at 21:24
  • 1
    Also just a note on your example, Eclipse does not show any IDE error in your case, it only shows when you dereference the s2... so println("executed s2 = " + s2.toUpperCase()) shows an error that s2 may be null, but it can't be because ifPresent takes care of that (I believe). – cfnz Jun 18 '15 at 21:29