MENU

String.valueOf(null)引出的两个问题

September 4, 2017 • Read: 1398 • Java

昨天有人在群里问了这么个问题:

System.out.println(String.valueOf(null));

会报空指针异常;而下面这样就可以打印null

Object obj = null;    
System.out.println(String.valueOf(obj));

debug代码会发现,两种方式执行了不同的重载方法,第一种执行了

public static String valueOf(char data[]) {
    return new String(data);
}

第二种执行了

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

如果对重载不熟悉,很难解释其中原因;当然null是另一个让人头疼的问。

重载

Java的重载解析过程是以两阶段运行的:

  • 第一阶段:选取所有可获得并且可应用的方法或构造器。
  • 第二阶段:在第一阶段选取的方法或构造器中选取最精确的一个,如果一个方法或构造器可以接受传递给另一个方法或构造器的任何参数,那么我们就说第一个方法比第二个方法缺乏精确性。

详细说明参考:https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5

java.lang.String中valueOf()所有重载方法

static String valueOf(boolean b)
static String valueOf(char c)
static String valueOf(char[] data)
static String valueOf(char[] data, int offset, int count)
static String valueOf(double d)
static String valueOf(float f)
static String valueOf(int i)
static String valueOf(long l)
static String valueOf(Object obj)

因为基本类型不能赋值null,String.valueOf(null)只能匹配valueOf(char[] data)valueOf(Object obj);而valueOf(char data[])更精确,所以选择执行valueOf(char data[])

再看一个例子:

public static void hah(Integer i) {
    System.out.println(i);
}

public static void hah(Long l) {
    System.out.println(l);
}

private static void hah(Object o){
    System.out.println(o);
}

尝试调用hah(null);提示编译失败(对hah引用不明确);因为此时不能匹配到一个最精确的重载方法。

null

null有类型吗?null是一个值还是一个对象?

我们知道若instanceof左边为null,那么不论右边是什么类,直接返回false;至少可以知道null不是对象;其实执行valueOf(char[] data)方法也证明了这点。

还是引用JLS中的描述,看看null到底是怎样的存在(也很纠结),具体地址:https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.1

4.1. The Kinds of Types and Values

There are two kinds of types in the Java programming language: primitive types (§4.2) and reference types (§4.3). There are, correspondingly, two kinds of data values that can be stored in variables, passed as arguments, returned by methods, and operated on: primitive values (§4.2) and reference values (§4.3).

There is also a special null type, the type of the expression null (§3.10.7, §15.8.1), which has no name.

Because the null type has no name, it is impossible to declare a variable of the null type or to cast to the null type.

The null reference is the only possible value of an expression of null type.

The null reference can always be assigned or cast to any reference type (§5.2, §5.3, §5.5).

In practice, the programmer can ignore the null type and just pretend that null is merely a special literal that can be of any reference type.

4.1中是说,Java编程语言中有两种类型:基本类型引用类型;但是,还有一个特殊的null类型,表达式null的类型,而且没有名字;因为null类型没有名字,所以不可能声明null类型的变量,也不能转换为null类型。null引用是null类型表达式唯一可能的值;null引用可以分配或转换为任意引用类型。
实际上,程序员可以忽略null类型,认为null只是一个可以是任何引用类型的特殊文字。

尝试将之前String.valueOf(null)中的null强制转换为Integer类型,再次运行程序,会输出null;因为Integer为Object的子类,而且没有更精确的匹配类型;所以执行了valueOf(Object obj)重载方法。

System.out.println(String.valueOf((Integer)null));

null造成的NullPointerException大概是最常见的异常,不论是JDK还是第三方类库都做了很多工作尽可能的避免空指针异常;比如Apache Commons的collections、lang判空,Guava的Optional等;甚至Optional类已经成为Java 8类库的一部分。

或许,大家认为这两段代码都会抛出空指针异常;其实,第二段代码会正常执行。

hah()方法是上面例子中定义的方法,它用static修饰是静态方法;对于静态方法和静态变量,使用了静态绑定,并不会抛出空指针异常;但是像这种对象访问类成员的写法最好不要使用,很容易造成误解。

String s = null;
System.out.println(s.length());
Demo demo = null;
demo.hah("hello");

静态绑定就是在程序执行前方法已经被绑定(在编译期中已经确定);比如demo.hah("hello")这个代码,反编译class文件得到:

Demo demo = null;
hah((Object)"hello");

你会发现编译后方法调用已经和demo没有关系,必然不会抛出空指针异常。

Last Modified: September 27, 2017