Предотвратить Android Studio Inspection от пометки цепочки Java Stream как содержащей возможное исключение NullPointerException - PullRequest
0 голосов
/ 25 января 2019

Я пишу служебный метод для извлечения массива обернутых неразборчивых объектов:

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 предупреждает меня, что мой вызов .map(UnparcelableHolder::getUnparcelable) может вызвать NullPointerException.Это не должно быть возможно из-за моего предыдущего вызова filter(Objects::nonNull).Как мне сообщить инспектору Android Studio, что мой код чист?

Это MCVE , доступный на github , созданный с 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);

    }
}

Та же проблема возникает при вызове .map(Foo::getString).По иронии судьбы, студия Android не жалуется на мой .map(Bar::getFoo) звонок, несмотря на то, что он определенно выбрасывает NullPointerException.

1 Ответ

0 голосов
/ 28 января 2019

Это ошибка Android Studios, поскольку ни одно из предложений Android Studios здесь не работает.

Без предупреждения:

No Warning

Примени предложение и получи предупреждение:

Warning

Также предлагается вставить шаг .filter(Objects::nonNull), когда он уже там.

Insert step

Так что это определенная ошибка AS.

Это истинное M CVE для этой проблемы:

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);
    }
}
...