标签

Android 44

Android Network security configuration Android 网络配置 Android Camera Preview gradlew 源码分析 Android 动态修改菜单 Android RelativeLayout 之 Gravity 的使用 Android Studio Gradle Download Error Android加载子View 【转】Android打开与关闭软键盘 Android EditText软键盘显示隐藏以及“监听” Android mipmap文件夹 Android 用命令行更新SDK Android Service学习之AIDL, Parcelable和远程服务 Android 5.0设备中,Notification小图标是白色的 Android最佳实践 Android Keystore 文件的密码修改 Android Studio 中加载so库文件 Android 中方法重载遇到的问题 ListView & RecyclerView Google Volley如何缓存HTTP请求文件 Creating logs in Android applications Advanced Android TextView TextView高亮URL地址解析 TextView 高亮URL地址,并实现跳转 Best practices in Android development Android Sdk Manager无法更新问题解决办法 Android ViewPager滑动事件 Google Volley 网络请求框架(一) Andorid UI注入工具的使用(ButterKnife) Android 项目中出现的奇葩bug, 数据NullPointExcption Android Drawable Animation Android 图片的毛玻璃效果 Android之使用Log打印日志 使用Fidder来拦截Android发送的HTTP请求 Android之Webview使用 Android之Notification的使用(二) Android之Notification的使用(一) Android Keyboard Show&Hiden Android 粘贴板的使用 Android中使用.9.png 使用Fidder来拦截Android发送的HTTP请求 Andorid JUnit 单元测试 Activity之间的切换动画 Android ListView中Adapter的使用

Android 项目中出现的奇葩bug, 数据NullPointExcption

2014年11月03日

问题描述

在一个自定义的Form表单中,有各种控件,如文本输入框,时间选择器,城市选择器等。 在使用城市选择器的时候,城市的数据存储在List中 在定义的时候,我使用如下代码定义:

private List<CityNode> mCitys = null;

经过数据初始化过后,在构造方法中使用mCitys是正常的,但是在其它方法中使用, mCitys便成了空值.

这个结果让我非常的费解。

解决问题

一开始出现了这个空指针异常,排查了好久,根本就找不到任何原因, 因为代码本身没有任何的逻辑错误。不知道是运气好还是怎么的。我把那个List的 定义写成如下,代码竟然可以正常运行:

private List<CityNode> mCitys;

看到上面贴出来的代码,我顿时就无语了,太奇怪了,这两个不应该是一样的么。 经过一系列的测试,以下是问题解决的步骤:

  • 反射

先上反射代码:

Class<? extends BaseFormElement> clazz = model.getType().getValue();
BaseFormElement element = null;
try {
    Class[] parameterTypes = { Context.class, FormElementModel.class };
    //根据参数类型获取相应的构造函数
    Constructor<? extends BaseFormElement> constructor
    		= clazz.getConstructor(parameterTypes);
    //参数数组
    Object[] parameters = { mContext, model };
    //根据获取的构造函数和参数,创建实例
    element = (BaseFormElement)constructor.newInstance(parameters);
} catch (Exception e) {
    e.printStackTrace();
}

一开始,我以为是由于java的反射机制引起,导致局域变量初始化被执行了两次, 可是,有关反射的相关信息,查找了一遍,包括类加载的机制等信息,还是没有能 解释代码为什么执行了两次,所以觉得不怎么像是他引起的。后面和一个同事说到, 他看了一下代码,开始也没有找到原因,后面他说对象强转型引起的。

  • 强制转型

在使用反射的时候,newInstance创建一个对象,返回了一个父类型的变量,可是 根本就没有进行强制转换。

  • 构造方法的执行顺序

还是抱着局域变量的初始化被执行了两次,打印了一下日志,终于发现了问题的所在。 在初始化一个对象的时候,首先是执行父类的构造方法,如果还有父类,继续向上查找。

示例:

public class A {
	{
		System.out.println("class a prarms init");
	}
	public A() {
		System.out.println("A constructor method excute");
	}
}
public class B extends A {
	{
		System.out.println("class B params init");
	}
	public B() {
		System.out.println("B constructor method excute");
	}
}
public class Main {

	public static void main(String[] args) {
		A a = new B();
	}
}

运行结果:

class a prarms init
A constructor method excute
class B params init
B constructor method excute

总结

这一次的空指针的最根本原因是在父类的构造方法中执行了子类实例变量的初始化 操作,这是一个非常不合理的举动。如果子类属性中的东西应该在子类中进行初始化, 而不是在父类中调用初始化方法。

写在最后

代码木有上传上来,如果你想看看这个丑陋的代码是怎么写的,你可以邮件联系我: lovecluo@nightweaver.org

更新代码地址:



友情链接: Hiro's Blog | Junjun's Blog