美文网首页
Lambda表达式的一个NullPointerException

Lambda表达式的一个NullPointerException

作者: cntlb | 来源:发表于2018-07-05 19:52 被阅读0次

    NullPointerException(简称NPE)是开发中很常见的一个异常,也比较容易处理, 但有时却也莫名其妙就NPE了, 并且错误根源也不大么容易寻找, 下面看一个关于NPE的例子, 请暂时忘记文章标题, 按常规思路来想问题.

    异常描述

         Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' on a null object reference
            at com.*.HomeMinePresenterImpl.loadData(HomeMinePresenterImpl.java:26)
            at com.*.HomeMineFragment.onResume(HomeMineFragment.java:158)
            at android.support.v4.app.Fragment.performResume(Fragment.java:2401)
            at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1465)
            at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740)
            at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1809)
    

    该app主页是一个MainActivity,嵌套了4个Fragment, 回到桌面启动另一个app, 再切换回来发现app崩了,出现这个异常信息. 代码如下:

        public void loadData() {
            ServiceFactory.get(HomeMineService.class)
                    .getHomeMineData()
                    .compose(TransformerUtil::io2Main)
                    .subscribe(data -> {
                      if(success){
                        mView.onSuccess(data);
                      }else{
                        mView.onFaild(message);
                      }
                    }, mView::onError);
        }
    

    定位到的NPE是loadData()中的26行,日志中点击是定位到了compose那一行, 而关键能解决问题的异常信息是:

    Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' on a null object reference
    

    这里并没有调用getClass(). 开启了调试想断进来看是哪里空了, 但是Fragment崩溃后也断不进来了. 然而基本可以确定是Fragment被回收后造成的问题.

    继续研究问题, getClass通常是框架进行反射时使用的, 接着对代码进行try-catch, 仍然是上面的异常信息, 但这时候可以打断点进来了不是? 真的是mView空了! NPE是在按步执行到compose后进入了catch块, 但正常来讲也应该报类似:

    Attempt to invoke virtual method 'xxx.onError(...)' on a null object reference
    

    这样的异常信息, 怎么会跑到getClass去NPE?

    反编译异常代码

    将apk拖到jadx-gui中, 定位到反编译后的代码(没有任何混淆,很容易找到):

    public class HomeMinePresenterImpl extends BasePresenter<HomeMineView> implements HomeMinePresenter {
        @SuppressLint({"CheckResult"})
        public void loadData() {
            Observable compose = ((HomeMineService) ServiceFactory.get(HomeMineService.class)).getHomeMineData().compose(HomeMinePresenterImpl$$Lambda$0.$instance);
            Consumer consumer = HomeMinePresenterImpl$$Lambda$1.$instance;
            HomeMineView homeMineView = (HomeMineView) this.mView;
            homeMineView.getClass();
            compose.subscribe(consumer, HomeMinePresenterImpl$$Lambda$2.get$Lambda(homeMineView));
        }
    }
    

    看到了一行homeMineView.getClass();设想此时mView是空的, 如果按反编译后的代码来执行, 那就会在compose执行完后得到NPE, 完全符合断点看到的结果. 然后看了其他类似代码的反编译, 有些调用getClass, 有些则没有, 但是有规律可循的(由于框架的缘故, 应用在subscribe中基本都会用mView来交互):

    调用Observable.subscribe(Observer<? super T> observer),反编译后的class代码中都不会调用mView.getClass(), 只要使用到mView相关的方法引用(如 mView::onError)都会调到

    举个栗子来验证下

    public class NpeTest {
        void onError(Runnable r){
            Runnable runnable = r::run;
            runnable.run();
        }
    }
    

    编译,反汇编

    javac NpeTest.java 
    javap -c -p NpeTest.class 
    

    得到

    Compiled from "NpeTest.java"
    public class NpeTest {
      public NpeTest();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      void onError(java.lang.Runnable);
        Code:
           0: aload_1
           1: dup
           2: invokevirtual #2                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
           5: pop
           6: invokedynamic #3,  0              // InvokeDynamic #0:run:(Ljava/lang/Runnable;)Ljava/lang/Runnable;
          11: astore_2
          12: aload_2
          13: invokeinterface #4,  1            // InterfaceMethod java/lang/Runnable.run:()V
          18: return
    }
    
    

    在onError的invokevirtual这里就调用了Object.getClass.

    解决问题之余心有不甘, 进行了一番折腾, 没有深入了解java对lambda的编译原理, 见笑于大方之家, 仅记录耳.

    相关文章

      网友评论

          本文标题:Lambda表达式的一个NullPointerException

          本文链接:https://www.haomeiwen.com/subject/tjcmuftx.html