android – 为什么对具有默认值报告的SharedPreferences.getString()进行代码检查“可能会产生NPE”?

使用以下代码的默认设置在
Android Studio 1.2.2中运行“分析/检查代码”时:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        String value = sharedPref.getString("somekey", "default");
        if (value.equals("default")) {
            Log.d("MainActivity", "value matches default");
        }
    }
}

它在“可能的错误”下产生以下警告:

Method invocation ‘value.equals(“default”)’ at line 16 may produce ‘java.lang.NullPointerException’

但是,如果首选项不存在,则返回默认值.即使首选项的值明确设置为null,其中:

sharedPref.edit().putString("somekey", null).commit();

将返回默认值(可能在引擎盖下这实际上是删除了首选项,但这在这里并不重要).

上述代码实际生成NPE的唯一方法是在首选项不存在时传递null值作为默认值:

String value = sharedPref.getString("somekey", null);

但是,如果有人这样做,那么他之后很可能不会错过空检查.我确实认识到null在这里可能不是一个字面值,可能来自其他一些变量,但同样也是一个获得偏好的不太可能的用例.

我确实意识到“它只是一个警告”而我“可以忽略它”而且它是我的代码,我可以做“我想要的任何东西”和“lint不是保姆”以及其他所有内容,但它让我烦恼(原谅双关语),这个明显琐碎的非bug在代码检查过程中显示为“可能的错误”.

现在的问题是:

>在我推测的无错误代码中是否存在一些我尚未见过的潜伏危险?
>代码检查是否不够智能?
>在代码检查期间实际触发此警告的是什么?
>可以采取哪些措施来消除警告?

试图回答4.我想出了以下内容:

>什么都没有,只是忽略输出.看起来很丑,看起来你根本没有考虑警告.
>禁用整个警告类.可能隐藏其他真正的错误.
>经典,将等于改为“默认”.equals(值).不适用于switch(value){…}
>只需添加一个单独的空检查if(value!= null){…}.多余的检查,投降的气味.

试图回答3.我已经尝试了一下:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String value = null;
        if (value.equals("default")) {
            Log.d("MainActivity", "value matches default");
        }
    }
}

导致预期警告并按预期爆炸.很公平.

下一个…

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String value = "test";
        if (value.equals("default")) {
            Log.d("MainActivity", "value matches default");
        }
    }
}

没有警告,没有崩溃.明显.

下一个…

public class MainActivity extends Activity {
    private String getString() {
        String value = null;
        if (new Random().nextBoolean())
            value = "test";
        return value;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String value = getString();
        if (value.equals("default")) {
            Log.d("MainActivity", "value matches default");
        }
    }
}

显然随机崩溃(字面意思),但没有警告!哎哟.

所以,让我们在getString()上添加一个@Nullable注释.现在我们得到警告,好.但是,SharedPreferences getString()似乎没有注释.

好吧,让我们尝试相反的事情.在getString()上粘贴@NonNull.新警告:

Expression ‘value’ might evaluate to null but is returned by the method declared as @NotNull (at line 28)

很酷,所以代码检查知道getString()的结果可能为null,但为什么在尝试使用返回值时它不会给我们原始警告?

更新

这里还有一个隐含的问题,第一次我可能没有足够强调.

此特定代码检查的描述如下:

Variables, method parameters and return values marked as @Nullable or @NotNull are treated as nullable (or not-null, respectively) and used
during the analysis to check nullability contracts, e.g. report possible NullPointerException errors.

这似乎意味着那些注释是驱动警告的原因.

事实上,我自己的方法只有在使用@Nullable注释时才会生成警告.但是,SharedPreferences getString()会生成此警告,尽管它没有注释.为什么是这样?

最佳答案

Is there some lurking danger in my presumed bug-free code that I haven’t seen yet?

可能不是.注意分析仪结果仍然是有意义的,并努力通过修复或有选择地抑制它们来消除任何问题.

Is the code inspection just not smart enough?
What actually triggers this warning during code inspection?

是的,它不够聪明.

分析器可以看到一个方法可以返回null并且返回值是未经检查的,但不能足够深入地说明你的代码中不会出现这种情况.

What can be done to remove the warning?

比较喜欢

"default".equals(value)

你已经发现的可能的风格.这样可以在所有情况下进行无NPE比较,并且还可以防止分析仪唠叨.

如果不可能,请在本地禁止警告,例如:

//noinspection ConstantConditions    
switch (value) {
    ...
点赞