android-studiointellij-ideanullpointerexceptionjava-streamintellij-inspections

Prevent Android Studio Inspection from Flagging Java Stream chain as containing possible NullPointerException


I'm writing a utility method to extract an array of wrapped unparcelable objects:

public interface UnparcelableHolder<U> {
    @Nullable U getUnparcelable();
}

public final class FragmentUtil {
    @Nullable
    public static <U> List<U> getUnparcelableHolderListArgument(
            @Nonnull Fragment fragment,
            @Nonnull Class<UnparcelableHolder<U>> unparcelableHolderClass,
            @Nonnull String key
    ) {
        @Nullable final Bundle arguments = fragment.getArguments();
        if (arguments == null) {
            return null;
        } else {
            @Nullable final Parcelable[] parcelableArray = arguments.getParcelableArray(key);
            if (parcelableArray == null) {
                return null;
            } else {
                return Arrays
                        .stream(parcelableArray)
                        .filter(unparcelableHolderClass::isInstance)
                        .map(unparcelableHolderClass::cast)
                        .filter(Objects::nonNull)
                        .map(UnparcelableHolder::getUnparcelable)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList());
            }
            if (unparcelableHolderClass.isInstance(parcelable)) {
                @Nonnull final UnparcelableHolder<U> unparcelableHolder =
                        Objects.requireNonNull(unparcelableHolderClass.cast(parcelable));
                return unparcelableHolder.getUnparcelable();
            } else {
                return null;
            }
        }
    }
}

Android Studio is warning me that my .map(UnparcelableHolder::getUnparcelable) call might cause a NullPointerException. This shouldn't be possible because of my preceding filter(Objects::nonNull) call. How do I tell Android Studio's inspector that my code is clean?

This is an MCVE is available on github built with Android Studio 3.4 beta 2:

build.gradle:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.github.hborders.streamsnonnulljsr305"
        minSdkVersion 28
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility = '1.8'
        targetCompatibility = '1.8'
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.google.code.findbugs:jsr305:3.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

MainActivity.java:

package com.github.hborders.streamsnonnulljsr305;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class MainActivity extends AppCompatActivity {

    class Foo {
        @Nonnull
        private final String string;

        Foo(@Nonnull String string) {
            this.string = string;
        }

        @Nonnull
        String getString() {
            return string;
        }
    }

    class Bar {
        @Nullable
        private final Foo foo;

        Bar(@Nullable Foo foo) {
            this.foo = foo;
        }

        @Nullable
        Foo getFoo() {
            return foo;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final Bar bar1 = new Bar(new Foo("foo"));
        final Bar bar2 = new Bar(null);
        final Bar[] bars = new Bar[]{
                null,
                bar1,
                bar2,
        };
        final List<String> strings = Arrays
                .stream(bars)
                .map(Bar::getFoo)
                .filter(Objects::nonNull)
                .map(Foo::getString)
                .collect(Collectors.toList());
        System.out.println("strings: " + strings);

    }
}

The same problem occurs on the .map(Foo::getString) call. Ironically, Android studio doesn't complain about my .map(Bar::getFoo) call despite that definitely throwing a NullPointerException.


Solution

  • It's an Android Studios bug as none of Android Studios suggestions work here.

    No warning:

    No Warning

    Apply suggestion and get the warning:

    Warning

    It also suggests to insert .filter(Objects::nonNull) step when it is already there.

    Insert step

    So it's a definite AS bug.

    This is a true MCVE for this issue:

    import android.support.annotation.Nullable; // or any nullable you care to use
    
    import java.util.Arrays;
    import java.util.Objects;
    
    public class MCVE {
    
        class Foo {
        }
    
        class Bar {
            @Nullable
            private final Foo foo;
    
            Bar(@Nullable Foo foo) {
                this.foo = foo;
            }
    
            @Nullable
            Foo getFoo() {
                return foo;
            }
        }
    
        public void mcve() {
            final Bar[] bars = new Bar[]{
                    new Bar(new Foo()),
            };
            Arrays.stream(bars)
                    .map(Bar::getFoo)
                    .filter(Objects::nonNull)
                    .map(Foo::toString);
        }
    }