记一次空指针异常

来源:互联网 发布:理科生 禅师 知乎 编辑:程序博客网 时间:2024/06/05 14:17

今天跑monkey的时候,出现了一个异常:

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

尝试用一个空的引用去获取类对象,很奇怪的错误!

搜索了整个出错的代码,需要使用对象的时候都进行了判空了呀~

最后看到了这么一个地方:

mLocationListener = sLocationInstance.new LocationListener();
这是一个内部类的创建,使用到了外部类的对象,于是猜测是不是因为sLocationInstance这个对象是空的导致的呢?

于是马上写个测试用例:

public class NullTest {    public static NullTest test;    private boolean isOut;    public static class User{        public static void use(){            Inner inner = test.new Inner();// 要创建内部类对象,必须使用外部类的对象+.new的方式。        }    }    public class Inner{        private boolean isIn;        public void test(){            isIn = isOut;// 内部类可以访问外部的所以成员变量和方法        }    }}
果不其然,执行User.use()方法的时候报错了,也就是文章开头的那个错误。

哦,原来是因为我们要创建内部类,但是创建内部类需要一个外部类对象,但是如果外部类对象为空的话,就出问题了。

我的理解,虚拟机在创建内部类对象的时候,需要一个外部类类型,这个外部类类型是由外部类对象得到的,但是如果这个外部类对象为空的话,那就出现上面的异常了。(字面理解,不知道对不对O(∩_∩)O哈哈~)。

内部类对象的创建条件:

要想使用new 生成一个内部类的实例,需要先指向一个外部类的实例,也就是先生成外部类的实例,因为内部类可以调用外部类的人员成员,当没有外部类实例的时候也就没有这些成员的内存空间,内部类在实例化的时候,调用外部类的成员就会出错,所以需要使用外部类的实例 + 点 + new 的方式实例化一个新的内部类。

在内部类(Inner Class),可以随意的访问外部类的成员,这可让我们更好地组织管理我们的代码,增强代码的可读性。

详细可看内部类的定义:

http://android.blog.51cto.com/268543/384844/


找到了问题,那么还要去想想为什么会出现这个问题?

在我们的代码中sLocationInstance是个Location类的单例对象,该类里面定义了一个内部类:LocationListener,还定义了一个静态内部类:

static class MyHandler extends Handler

在MyHandler的handleMessage方法中,会去执行创建LocationListener对象的过程。

public void handleMessage(Message msg) {   case START_LOCATION_MSG: {      mLocationListener = sLocationInstance.new LocationListener();      break;   }   }

在我们的Location类中,定义了MyHandler的静态变量,并进行了初始化(类加载的时候就会去初始化了,这个时候我们的单例对象可能还没创建):

private static MyHandler mHandler = new MyHandler(sLocationThread.getLooper());
所以,handler这个对象是优先于sLocationInstance对象创建的,那么就有可能在sLocationInstance还没创建的时候,我们发送了一个msg,于是会执行到hanleMessage方法中,从而去创建内部类LocationListener的对象,这个时候就肯定会出错了。

部分代码如下:

public class Location {        private static final HandlerThread sLocationThread = new HandlerThread("location");        static {            sLocationThread.start();        }        public static Location sLocationInstance;        public static final int START_LOCATION_MSG = 10002;        private static MyHandler mHandler = new MyHandler(sLocationThread.getLooper());        public static AMapLocationListener mLocationListener;        private static Object mLock = new Object();        public static class MyHandler extends Handler {            public MyHandler(Looper looper) {                super(looper);            }            @Override            public void handleMessage(Message msg) {                int what = msg.what;                switch (what) {                    case START_LOCATION_MSG: {                        mLocationListener = sLocationInstance.new LocationListener();                        break;                    }                    default:                        break;                }            }        }        public synchronized static Location getInstance() {            if (sLocationInstance == null) {                sLocationInstance = new Location();            }            return sLocationInstance;        }        public synchronized void startLocation() {            Log.d(TAG, "startLocation");            mHandler.sendEmptyMessage(START_LOCATION_MSG);        }        public class LocationListener {        }
}

虽然我们mHandler发送一个msg是由sLocationInstance这个对象来调用的,按理说,既然sLocationInstance能调用handler发送一个msg,那必然这个对象是不为空的,也就不可能出现空指针异常了。理想情况当然是这样的,但系统的事情,谁有能说得准呢。或许是我们发送消息后,系统阻塞了这个消息,然后等我们的Location被gc了,而mHandler还健在的时候,收到了这个延迟的消息呢~

就比如说某个app在我们系统中报了这个错误:

Crashjava.lang.NullPointerException09:17:39112:20:35其他未知// at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3128)ActivityThread出错了~~~

无论原因怎样,还是在使用对象的时候进行下判空吧~~


以上纯属个人瞎说,不对之处就当笑话看过,勿喷!

0 0
原创粉丝点击