在实践中,这种例外经常发生,足以保证其拥有自己的文章。 这是一个非常愚蠢的异常,因为它是编程中要避免的最简单的异常之一。 然而,我们早已掌握了一切,为爱因斯坦的说法提供了证明:“只有两件事是无限的,宇宙和人类的愚蠢……”。
主要原因
取消引用null。
到目前为止,这是导致异常的更常见原因。
Java和.NET中的引用类型均具有默认值null。 值为null的引用类型的变量表示该变量没有指向任何对象。 没有。 你什么也做不了。 您一定无法取消引用它,而不会召唤NullPointer异常来并给您一个愚蠢的标签。 所以如果我们有
Type var = null;
我们创建了一个Type类型的变量,将其命名为var,并使其指向没有对象。 如果我们点(应用解引用运算符(。))var,则将获得NullPointer异常,因为我们将没有对任何对象进行解引用。 我们什么也没要求做。 确实很愚蠢。 var需要指向某个非null对象,以便我们能够取消引用它。 可能的解决方案是
var = new Type();
要么
var = anotherVar;
其中anotherVar是对先前初始化的对象的引用。
这听起来很简单,不是吗? 这里有一些避免方法的提示。
避免它
1.)始终声明最接近其使用位置的变量。
在顶部声明变量的旧C方法为在以后的代码中使用几行之前忘记初始化它们留下了空间。 如果您忘记了,则不会在C中获得NullPointer异常。 你只是垃圾。
2.)小心复合材料。
诸如
Type[] array = new Type[5];
创建Type []类型的变量,将其命名为array并将其初始化为包含5个元素的array对象。 不幸的是,此数组中的五个元素array [0],array [1],…,array [4]具有Type类型的默认值。 如果Type是引用类型,则这些值均为空。 他们每个人都指向没有对象。 取消引用任何数组索引将导致引发NullPointer异常。 数组的五个索引中的每个索引都必须指向非null值,然后才能将其取消引用。 显然,其他集合和复合类型也是如此。
3.)当心方法/构造函数变量的隐藏。
class Person {
String name; public Person() {
String name = "Something Stupid"; } public String getFirstName() {
return name.split(" ")[0]; } public static void main(String[] args) {
Person stupid = new Person(); System.out.println(stupid.getFirstName()); } }
在上面的代码段中,Person类具有构造器尝试初始化的name属性。 尝试是没有希望的。 而是在构造函数内部创建一个新的名称变量。 该变量的声明在该构造函数中隐藏了该类的name属性。 为了进一步加重侮辱,在构造函数内部声明的name变量仅在该构造函数内部可用。 它不在该构造函数之外的任何地方。
当毫不怀疑的main方法调用getFirstName方法时,将在未初始化的属性上调用拆分。 猜猜什么都没有奖品。
通常,访问声明时未初始化的实例变量时需要特别注意,并且局部变量最好使用与实例变量名称不冲突的名称。
4)阅读相关文档。
允许程序员以其认为适当的任何理由在其代码中明确抛出NullPointer异常。 这意味着您可以通过执行代码的执行流通过带有NullPointer异常的throws子句的语句来获取NullPointer异常,而无需显式地取消引用null变量。
一个简单的Java示例是java.util.Hashtable的contains方法。 该方法的代码是
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException(); } Entry tab[] = table; for (int i = tab.length ; i-- > 0 ;) {
for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true; } } } return false; } /* * @(#)Hashtable.java 1.116 06/05/26 * * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */
如您所见,如果传递的参数为null,则显式抛出NullPointerException。 幸运的是,我们不必总是猜测在什么条件下哪些方法会引发nullpointer异常。 方法的文档应明确告诉我们什么条件导致方法抛出该异常。 在文档齐全的代码中总是如此,如果在某些条件下显式抛出任何异常,则同样应该正确地编写代码。
因此,如果我们想使用参数p调用Hashtable类的contains方法,阅读该方法的文档将告诉我们,在调用该方法之前,应检查p是否为null。
JSP参数和属性也是如此。 如果request.getParameter方法请求了一个参数,但请求中不存在具有该名称的参数,则返回null。 如果未检查返回值是否为非null,则对返回参数的任何操作都可能导致NullPointer异常。
5)在开发过程中尽可能记录
通常,该异常是由错误地假定某些数据值和某些执行路径引起的。 在关键位置撒一些打印输出将有助于免除一些错误的假设,并揭露nullPointer最有可能尝试通过的漏洞。 的确,我的意思是说,建立日志记录框架应该是系统实施期间应该执行的第一步。
6.)提防超类构造函数初始化与派生类实例初始化程序
还有另一种不太明显的方法来在整个位置创建空值。 实际上,以下讨论可能并不适合胆小者:
在下面的代码段中(特别为
乔斯 )
abstract class BaseClass {
abstract protected void initialize(String name); public BaseClass(String memberName) {
initialize(memberName); System.out.println("in base: " + this); } } class NullMember extends BaseClass {
String name = null; public NullMember(String memberName) {
super(memberName); } @Override protected void initialize(String name) {
this.name = name; } @Override public String toString() {
return name; } public static void main(String[] args) {
NullMember nm = new NullMember("foo"); System.out.println("NullMember: " + nm); } }
输出是
in base: foo NullMember: null
在主方法中,即使打印输出确实显示变量名确实是“ foo”,变量名也从未被初始化。 这种特殊性是由于对象构造过程中语句的执行顺序。 JLS(8.8.7)将其解释为
令C为要实例化的类,令S为C的直接超类,令i为正在创建的实例。 显式构造函数调用的评估过程如下:
•首先,如果构造函数调用语句是超类构造函数调用,则必须确定i相对于S的立即封闭实例(如果有)。 我是否具有关于S的立即封闭实例由超类构造函数调用确定,如下所示:
<在这里,JLS用毫无生气的口吻含糊地解释了如何确定i的立即封闭实例。 值得庆幸的是,我们在此讨论中不关心任何一个>
•其次,从左到右评估构造函数的参数,如
普通方法调用。
•接下来,调用构造函数。
•最后,如果构造函数调用语句是超类构造函数
调用,构造函数调用语句正常完成,然后
C的所有实例变量初始化器和C的所有实例初始化器均被执行。
最后一点就是我们感兴趣的内容。如果我们正在调用超类构造函数,那么在超类构造函数之后不久,我们将在调用(扩展)类中实例初始化器的调用将覆盖在该构造函数中进行的任何初始化叫做。 如果继承类中的初始化程序将任何变量设置为null(如上例所示),则我们将在某个字段中踩入一些null指针已被设置的情况。
就是说,通过将实例字段显式初始化为null确实没有太多收获。 如果我们不提供它,那么我们将没有任何显式的初始化程序可用于覆盖我们的超类构造函数将完成的所有出色工作。
幸运的是,.NET程序员不存在这种特殊性。
等效的C#示例
abstract class BaseClass {
public abstract void initName(String s); public BaseClass(String s) {
initName(s); Console.WriteLine("in base "+this); } } class NullMember : BaseClass {
String name = null; public NullMember(String s):base(s) {
} static void Main(string[] args) {
NullMember p = new NullMember("foo"); Console.WriteLine("NullNumber "+p); Console.ReadLine(); } public override String ToString() {
return name; } public override void initName(String s) {
this.name = s; } }
行为良好,在超类构造函数和main方法中都打印“ foo”。
这种行为得到了C#语言规范(10.11.2)的祝福,
当实例构造函数没有构造函数初始值设定项,或者它具有base(…)形式的构造函数初始值设定项时,该构造函数将隐式执行由在其类中声明的实例字段的变量初始值设定项指定的初始化。 这对应于分配的序列,这些分配的序列在进入构造函数后立即执行,并且在隐式调用直接基类构造函数之前执行。 变量初始化器按照它们在类声明中出现的文本顺序执行。
变量初始值设定项在调用基类构造函数之前执行,这意味着name = null部分是被构造函数调用覆盖的部分,而不是相反的方法。
调试它。
如果您收到此异常,则可能有一个堆栈跟踪,列出了被调用的方法和或构造函数,并包括抛出该异常的确切行。 与该跟踪有关的第一件事是识别堆栈跟踪中来自代码的第一行。 该行将包含对调用null变量的显式取消引用,该调用会导致某些代码块引起nullPointer异常。
第一种情况很容易。 只需测试该行上每个已取消引用的变量,以确保它们不为空。 第二种情况涉及检查已调用代码的文档,以了解哪些条件将导致该异常引发该异常,然后将这些条件与调用它的条件进行比较。
通过Java示例进行演示,
static Object key; static Hashtable<String, String> t = new Hashtable<String, String>() {
{
put("one", "entry"); } }; public static void main(String[] args) {
System.out.println(t.contains(key)); }
你会得到
Exception in thread "main" java.lang.NullPointerException at java.util.Hashtable.contains(Hashtable.java:265) at Person.main(Person.java:20)
属于我的代码的第一行是Person.main(Person.java:20)。
在那条线上,我有
System.out.println(t.contains(key));
被取消引用的变量为t,并且两者都不为空。 我正在调用的代码块是java.util.Hashtable的contains方法,导致其引发NullPointer异常的条件是如果传递的参数为null。 我的参数key永远不会初始化为非null值,这就是问题所在。
当然,调试器的逐步调试将得出相同的结论。
结论
阅读本文后,您可能会以为您永远不会再遇到NullPointer或NullReference异常。 当然,鼓励这种想法。 再次证明爱因斯坦是错的。
From: https://bytes.com/topic/java/insights/866107-nullpointerexception-k-nullreferenceexception
今天的文章NullPointerException又名NullReferenceException分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/12958.html