Search code examples
javajava-streamchecker-framework

Java collect method incompatible with Checker framework


I have introduced the Checker Framework v2.1.6 to a java8 project and have fixed all nullness errors but am stuck on the following error

Main.java:52: error: [assignment.type.incompatible] incompatible types in assignment.

List<String> collectedStrings = strings.stream().collect(Collectors.toList());
                                                                    ^
  found   : @Initialized @NonNull List<@Initialized @Nullable Object>
  required: @UnknownInitialization @Nullable List<@Initialized @NonNull String>

The following (simplified) example code throws the error

List<String> strings = new ArrayList<>();
strings.add("test");
List<String> collectedStrings = strings.stream().collect(Collectors.toList());
collectedStrings.forEach(System.out::println);

Now I can work around this by making the result @Nullable

List<@Nullable String> collectedStrings = strings.stream().collect(Collectors.toList());

But this only cascades the error onto the next call which now thinks that collectedStrings is suspicious

Does anyone have a good work around for this?


Solution

  • The issue is that the Checker Framework assumes that Collectors.toList() returns a List<@Nullable...>. This is a safe, conservative assumption, but in your context you want List<@NonNull...>. The Checker Framework's type inference is currently too weak to infer the type you want.

    The cleanest way to solve the problem is by changing Collectors.toList() to Collectors.<String>toList(). (Writing just String is equivalent to @NonNull String.)

    An alternative is to suppress the warning, writing @SuppressWarnings("nullness") on the assignment.

    The code below shows all these possibilities.

    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    import org.checkerframework.checker.nullness.qual.Nullable;
    
    public class CollectorsToList {
    
      void m(List<String> strings) {
        Stream<String> s = strings.stream();
    
        // This works:
        List<String> collectedStrings1 = s.collect(Collectors.<String>toList());
        // This works:
        List<@Nullable String> collectedStrings2 = s.collect(Collectors.toList());
        // This works:
        @SuppressWarnings("nullness")
        List<String> collectedStrings3 = s.collect(Collectors.toList());
    
        // This assignment issues a warning due to incompatible types:
        List<String> collectedStrings = s.collect(Collectors.toList());
    
        collectedStrings.forEach(System.out::println);
      }
    }