为什么使用默认值报告 "may produce NPE" 的 SharedPreferences.getString() 上的代码检查?

Why does code inspection on SharedPreferences.getString() with default value report "may produce NPE"?

当 运行 "Analyze/Inspect Code" 在 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");
        }
    }
}

它在 "Probable bugs" 下产生以下警告:

Method invocation 'value.equals("default")' at line 16 may produce 'java.lang.NullPointerException'

但是,如果首选项不存在,默认值将被 returned。即使首选项的值被显式设置为空:

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

默认值将被 returned(大概在幕后这实际上是在删除首选项,但这在这里并不重要)。

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

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

但是,如果有人那样做了,他之后遗漏空检查的可能性很小。我确实意识到 null 在这里可能不是文字,可能来自其他变量,但同样,获取首选项的用例不太可能。

我确实意识到 "it's only a warning" 和我 "can ignore it" 这是我的代码,我可以做 "whatever I want" 和 "lint isn't a babysitter" 以及所有其他的,但是它有错误我(请原谅双关语),这个明显微不足道的非错误在代码检查期间显示为 "Probable bug"。

现在问题:

  1. 在我假设的无错误代码中是否存在一些我尚未见过的潜在危险?
  2. 是不是代码检查不够智能?
  3. 在代码检查期间实际触发此警告的是什么?
  4. 如何消除警告?

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

  1. 没什么,只是忽略输出。看起来很丑,而且看起来你根本没有考虑警告。
  2. 禁用整个 class 警告。可能会隐藏其他真正的错误。
  3. classic,将等号左右改为"default".equals(value)。不适用于 switch(value){...}
  4. 只需添加一个单独的空检查 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,但为什么它在尝试使用 return 时不给我们原始警告值?

更新

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

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

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?

是的,它不够聪明。

分析器可以看到一个方法可以 return null 并且 return 值被未经检查地使用,但无法深入推理,因此您的代码中不会出现这种情况。

What can be done to remove the warning?

更喜欢

"default".equals(value)

如您所见,尽可能采用风格。这让您在所有情况下都可以进行无 NPE 的比较,并且还可以防止分析器唠叨。

不可能时,在本地抑制警告,例如:

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