What is a NullPointerException?
来源:互联网 发布:微博域名修改 编辑:程序博客网 时间:2024/06/16 18:20
- 什么是NullPointerException?
- 为什么只有引用类型会产生NullPointerExcepiton?
- 如何避免NullPointerExcepiton?
- 基本类型一定不会产生NullPointerException吗?
- null究竟是什么?
- String.valueOf(null)与重载方法的调用
这是在Stack Overflow上看到的一个问题:What is a NullPointerException, and how do I fix it?
什么是NullPointerException?
NullPointerException应该是非常简单、也是初学者最常见的一个异常,然而这个问题有400+个star,它的提问者有8000+的reputation,而被采纳的答案有2000+的useful,答题者有80000+的reputation。也许,这个问题值得我们稍微思考一下下。
NullPointerException,空指针异常,源码是这么解释的:当程序在需要一个对象的情况下试图使用null,则抛出此异常。
/** * Thrown when an application attempts to use {@code null} in a * case where an object is required. These include: * <ul> * <li>Calling the instance method of a {@code null} object. * <li>Accessing or modifying the field of a {@code null} object. * <li>Taking the length of {@code null} as if it were an array. * <li>Accessing or modifying the slots of {@code null} as if it * were an array. * <li>Throwing {@code null} as if it were a {@code Throwable} * value. * </ul> * <p> * Applications should throw instances of this class to indicate * other illegal uses of the {@code null} object. */publicclass NullPointerException extends RuntimeException { public NullPointerException() {super();} public NullPointerException(String s) {super(s);}}
看一段很简单的代码:
static int sInt; static int[] sArr; public static void main(String[] args) { int i; int[] arr; System.out.println(i); // 报错 System.out.println(arr); // 报错 System.out.println("----------"); System.out.println(sInt); // 0 System.out.println(sArr); // null System.out.println("----------"); sInt = 255; sArr = new int[]{255}; System.out.println(sInt); // 255 System.out.println(sArr); // [I@******** }
当声明局部变量时,会在栈中开辟空间存储这个变量。在刚创建的时候,无论基本类型还是引用类型,都没有记录任何的值。当试图使用一个未赋值的局部变量的时候,编译的时候会报错:The local variable ××× may not have been initialized.
当变量被声明为成员变量的时候,情况会有一些区别。基本类型的成员变量会默认初始化为0(0L,0.0,0.0F,\u0000,false),这个值就是变量的字面值,是真实存在、可以直接使用的。而引用类型统一默认初始化为null,表示这个变量没有指向任何引用。
现在,显式地变量赋值。虽然给变量赋的值是一样的,但是基本类型变量存储的值为字面值(255),而引用类型存储的是该变量在堆内存中引用的内存地址(0x********),而真正的值存储在这个地址中。
因为引用类型变量存储的是一个内存地址(Pointer),当我们使用这个变量的时候,实际上是在使用它所指向的这个内存地址中所存储的对象。所以,当使用了一个没有任何指向的引用类型变量的时候,就会抛出NullPointerException。那么,问题来了。
为什么只有引用类型会产生NullPointerExcepiton?
这个问题也可以有另外一种问法:为什么基本类型的值不可能为空?变量为空,一般是如下三种情况:
- 内部声明的成员变量,未显式赋值。基本类型的默认值是有意义的;而引用类型的默认值是无意义,它只表示”我现在还没有值“。
- 内部声明的变量,接受了一个值为空的赋值。当赋值的时候,基本类型是不接受null这个值的,但是引用类型却可以。
- 外部传入的变量,传入值为空。同上,当接受参数的时候,基本类型是不接受null的,但是引用类型却接受。
如何避免NullPointerExcepiton?
避免NullPointerException的最通常的做法是以下两种:
- 当调用.equal()方法时,如果其中一个变量是常量,则应写为final.equal(variable)而不是variable.equal(final);类似的,如果可以基本确定A不为空,而B不确定,则应写成A.equal(B)。
- 如果不确定一个变量是否为空,则先进行if(null == variable)的判断。
基本类型一定不会产生NullPointerException吗?
是的,一定不会,因为基本类型一定会有一个字面值。但是,有些情况我觉得还是挺有意思的。
public static void test(int a){ System.out.println(a + 1); } public static void test(Integer a){ int b = a + 1; System.out.println(b); }
上面的代码有什么不同?想一下,如果传入的是一个空的Integer对象会有怎样的结果?
- test(int a),空值传不进来,调用时直接抛出NullPointerException。
- test(Integer a),接受传值,在计算int b = a + 1时抛出NullPointerException。
也就是说,基本类型的包装类在自动拆箱时必须有一个真实有意义的值。这也提示我们,虽然包装类在很多情况下可以和基本类型混用,但永远不能忘记他们是引用类型这个事实。
null究竟是什么?
public static void main(String[] args) { Object object = null; System.out.println(null instanceof Object); // false System.out.println(object instanceof Object); // false System.out.println(String.valueOf(object)); // null System.out.println(String.valueOf(null)); // throw NullPointerException }
- null instanceof Object结果为fasle,也就是说null不是Object的对象,而我们知道Java中所有对象都是Object的对象,所以null不是一个对象。
- object instanceof Object结果为false,很容易理解,因为此时object实际上为null。这提示我们,obj instanceof TypeName可以用来代替if(null == obj)同时做类型检查和非空检查。
- String.valueOf(object)输出null,显然,接受传参时只检查变量的类型,而不会检查引用的真实性。至于方法内部也没有报错,是因为valueOf()对null值做了例外处理。
public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); }
- Java的参数传递,实质上都是值传递(地址值也是一个值),null可以被JVM传递,而它又不是一个地址值,所以我认为null应该是一个特殊的字面值,表示这是一个引用,但没有指向任何内存地址。
- 既然如此,那String.valueOf(null)直接抛出NullPointerException又是为什么呢?再看:
String.valueOf(null)与重载方法的调用
public class Demo1 { public static void main(String[] args) { test1(null); //报错 } public static void test(Child c){ if(c == null){ System.out.println("Null Child"); }else{ System.out.println("Not Null Child"); } } public static void test(Super s){ if(s == null){ System.out.println("Null Super"); }else{ System.out.println("Not Null Super"); } }}class Child{}class Super{}
public class Demo2 { public static void main(String[] args) { test1(null); // Null Child } public static void test(Child c){ if(c == null){ System.out.println("Null Child"); }else{ System.out.println("Not Null Child"); } } public static void test(Super s){ if(s == null){ System.out.println("Null Super"); }else{ System.out.println("Not Null Super"); } }}class Child extends Super{}class Super{}
public class Demo3 { public static void main(String[] args) { Super s = null; test1(s); // Null Super } public static void test(Child c){ if(c == null){ System.out.println("Null Child"); }else{ System.out.println("Not Null Child"); } } public static void test(Super s){ if(s == null){ System.out.println("Null Super"); }else{ System.out.println("Not Null Super"); } }}class Child extends Super{}class Super{}
public class Demo4 { public static void main(String[] args) { System.out.println(((Super)new Child()).getClass()); // class Child test((Super)new Child()); //Not Null Super } public static void test(Child c){ if(c == null){ System.out.println("Null Child"); }else{ System.out.println("Not Null Child"); } } public static void test(Super s){ if(s == null){ System.out.println("Null Super"); }else{ System.out.println("Not Null Super"); } }}class Child extends Super{}class Super{}
上面的四段代码:
- Demo1编译时直接报错”The method test(Child) is ambiguous for the type Demo1.”Demo1这类中有两个重载的方法符合test(null)的调用条件,JVM无法确定究竟调用哪一个。
- Demo2输出Null Child,这里Child定义为Super的子类,Child的范围更小,所以test(Child)比test(Super)更符合调用条件,于是JVM调用了test(Child)。
- Demo3输出Null Super,虽然实际传入的值是null,但存储它的变量被声明为Super,JVM在此认为这是一个Super类型值。
- Demo4输出Not Null Super,虽然实际创建的是Child对象,但已将其类型强制声明为Super。
于是我们得出以下结论:
- null在作为参数传递时可以匹配任何类型,与此同时通过instanceof运算符它不能匹配任何类型。
- 当同时有多个方法匹配调用条件时,JVM会调用最匹配的方法,如果有多个方法同等匹配,则编译报错。
- JVM在匹配参数时,根据对象的声明类型,而不是运行时类型做匹配。
再回到String.valueOf(null),我们发现String有两个重载的valueOf():
public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); } public static String valueOf(char data[]) { return new String(data); }
String.valueOf(object)调用的是valueOf(Object obj),这个方法内部对null做了特殊处理,所以传入任何值都是可以的。而String.valueOf(null)调用的是valueOf(char data[]),遗憾的是它并没有处理null,于是导致报错。
- What is a NullPointerException?
- What is a NullPointerException, and how do I fix it?
- What is a UUID?
- what is a lawyer
- What Is a Desision?
- What Is A Framework?
- what is a TFT?
- What is a THINKER?
- What is a disagreement
- What is a Closure?
- What is a Region?
- what is a map
- What is a FOURCC?
- What is a Program?
- what is a process?
- What is a FOURCC?
- What is a Project?
- What Is a Portlet
- linux服务器查看防火墙状态
- laravel资源路由
- mysql使用临时量排序
- 2017多校1 1006Function
- python os.path 骚操作
- What is a NullPointerException?
- x的n次幂
- 猜数游戏
- E
- CodeForces B. Karen and Coffee
- 操作EEPROM时触发ECC内部故障导致通信失败
- codeforces731F (思维)
- 循环结构
- (转)为了快 0.00007 秒,有家交易公司花 1400 万美元买了块地