尝试在空指针对象上调用异常

时间:2017-01-05 10:44:26

标签: java null

我有以下代码:

view.setPressed(true);
view.postDelayed(new Runnable() {
    @Override
    public void run() {
        view.setPressed(false);
    }
}, 50);

变量声明很简单:

private View view;

我为其分配值的唯一地方是onInterceptTouchEvent:

view = parentView.findChildViewUnder(event.getX(), event.getY());

从Crashlytics我在setPressed(false)调用中得到空指针异常,stack:

Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view
.View.setPressed(boolean)' on a null object reference
       at com.MyApp.common.ui.RecyclerItemClickListener$GestureListener$1.run(SourceFile:119)
       at android.os.Handler.handleCallback(Handler.java:739)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:158)
       at android.app.ActivityThread.main(ActivityThread.java:7229)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

我添加了空检查,一切看起来都不错,Android Studio中没有警告。

将代码移动到某个函数并将视图作为参数发送。 在函数声明中,它声明了最终视图。现在,Android Studio突出显示条件" view!= null"总是假的(好吧,它在这条线上崩溃了,所以......:)

enter image description here

我想知道它是否与垃圾收集有关,以及它如何处理最终变量,或者它只是一个错误的检查错误。

3 个答案:

答案 0 :(得分:1)

view.postDelayed(new Runnable() {
    @Override
    public void run() {
        if (view != null) {
          view.setPressed(false);
        }
    }
}, 50);

在您的代码中,如果viewnull,您将在view.postDelayed()方法调用中使用NPE,并且作为参考是最终的,它不能更改,因此在{{1它永远不能为null,有两个选项:

  1. RunnableView并在null时崩溃,因此您无需在view.postDelayed中查看
  2. Runnable不是View,因此您无需在null
  3. 中查看它

答案 1 :(得分:1)

您没有提供足够的背景来明确诊断。但是,这是一种可能的情况,view方法中的null可能是run()

考虑以下代码:

public class MyClass {
    private View view = ... // not null

    public void someMethod() {
        view.setPressed(true);
        view.postDelayed(new Runnable() {
            @Override
            public void run() {
                view.setPressed(false);
            }
        }, 50);
    }

    public void someOtherMethod() {
       view = null;
    }
}

现在假设实例化MyClass,并调用someMethod(),然后调用someOtherMethod()。如果后一次调用在延迟完成之前发生,则run()方法会将view视为null

原因是匿名类不是捕获 view。而是捕获对MyObject所属view实例的引用。

根据view来电(或代码中的模拟),您的null不是someMethod()的检查可能是

如果您要将view声明为final,则someOtherMethod()中的分配将被阻止,view将保持为非空。

另一种方法是:

    public void someMethod() {
        final View localView = view;
        localView.setPressed(true);
        localView.postDelayed(new Runnable() {
            @Override
            public void run() {
                localView.setPressed(false);
            }
        }, 50);
    }

这里我们强制捕获局部变量而不是(或同样)this。如果分配了this.view字段,则不会更改localView的捕获值。

甚至更简单,将null测试放在run()方法中......但是你需要考虑是否存在涉及分配{{1}的任何内容的竞争条件到null字段。

  

我想知道它是否与垃圾收集有关,以及它如何处理最终变量

没有。 GC不会干扰可到达的对象。 view实例是可访问的,因为它已被捕获,并且捕获对象(匿名类实例)可通过MyClass基础结构访问。

  

或者只是一个错误的检查错误。

这似乎是合理的......但(IMO)并非最可能的解释。

答案 2 :(得分:0)

如果view是最终版并且postDelayed行不在构造函数中,则view保证已经初始化。它永远不会为空,因此view != null将始终解析为true。它警告你这是一个毫无意义的检查。

唯一的例外是如果你有像

这样的东西
private final Integer myInt;

MyCtor()
{
   myInt = null;
}

这完全没有意义(但出于某种原因可能)。