为什么String.valueOf(null)会抛出NullPointerException异常?

141
根据文档,方法 String.valueOf(Object obj) 返回:

如果参数是 null,则返回一个等于 "null" 的字符串;否则,返回 obj.toString() 的值。

但是当我尝试这样做时,为什么会出现问题:
System.out.println("String.valueOf(null) = " + String.valueOf(null));

它为什么会抛出NPE?(如果你不相信,请自行尝试!)

    Exception in thread "main" java.lang.NullPointerException
    at java.lang.String.(Unknown Source)
    at java.lang.String.valueOf(Unknown Source)

为什么会发生这种情况?是文档在欺骗我吗?这是Java中的一个重大错误吗?

4个回答

216
问题在于 String.valueOf 方法有重载的几种形式: Java 规范要求在这种情况下选择最具体的重载方法

JLS 15.12.2.5 选择最具体的方法

如果一个方法调用可应用多个成员方法且它们都可访问,则需要选择其中一个作为运行时方法分派的描述符。Java 编程语言使用规则,选择最具体的方法。

一个 char[] 是一个 Object,但不是所有的 Object 都是一个 char[]。 因此,char[]Object 更具体,按照 Java 语言的规定,在这种情况下会选择 String.valueOf(char[]) 重载方法。 String.valueOf(char[]) 要求数组不能为 null,而在这种情况下给出了 null,所以它会抛出 NullPointerException 异常。
一个简单的“修复”方法是将 null 显式地强制转换为 Object,如下所示:
System.out.println(String.valueOf((Object) null));
// prints "null"

相关问题


故事的寓意

有几个重要的寓意:

  • 《Effective Java》第二版,Item 41: 谨慎地使用重载
    • 仅仅因为能够重载,不代表每次都应该这么做
    • 它们可以引起混淆(特别是当方法执行完全不同的操作时)
  • 使用好的IDE,你可以在编译时检查选择了哪个重载函数
    • 在Eclipse中,你可以将鼠标悬停在上述表达式上,就可以看到确实选择了valueOf(char[])重载!
  • 有时候你需要明确地将null进行强制类型转换(下面会给出例子)

另请参阅


关于将null进行转换的问题

至少有两种情况下需要明确地将null强制类型转换为特定的引用类型:

  • 选择重载函数(如上述示例中所示)
  • null作为可变参数的单个参数传递

后者的一个简单例子如下:

static void vararg(Object... os) {
    System.out.println(os.length);
}

那么,我们可以得到以下内容:

vararg(null, null, null); // prints "3"
vararg(null, null);       // prints "2"
vararg(null);             // throws NullPointerException!

vararg((Object) null);    // prints "1"

参见

相关问题


5
另一个建议:当你重载一个方法时,如果某个参数可以在两个重载中调用(比如在这种情况下的 null),请确保两个重载对该值的处理方式相同! - Joachim Sauer
3
@Joachim:我刚读了这篇内容,很惊喜地发现这两种方法被明确讨论了!Bloch更进一步指出,由于String.valueOf(Object)valueOf(char[])无论是否为null,它们所执行的操作完全不同,或许它们一开始就不应该是重载方法。 - polygenelubricants
如果你有一个返回对象的方法,那么语句 return null;return (Object) null; 是完全相同的吗? - user972276

19
问题在于你调用了 String.valueOf(char[]),而不是 String.valueOf(Object)
这是因为 Java 总会选择使用提供参数的最具体版本的重载方法。 null 是一个有效的 Object 参数值,但也是 char[] 参数值的有效值。
要让 Java 使用 Object 版本,可以通过变量传递 null 或指定显式的 Object 强制转换。
Object o = null;
System.out.println("String.valueOf(null) = " + String.valueOf(o));
// or
System.out.println("String.valueOf(null) = " + String.valueOf((Object) null));

5

关于这个问题,编号为4867608的错误报告早在2003年就已经提交了,但被解决为“不予修复”,并附有以下解释。

由于兼容性限制,我们无法进行更改。请注意,最终被调用的是公共静态String valueOf(char data[])方法,并且它没有提及将"null"替换为null参数。

@###.### 2003-05-23


-1
在Scala中使用String.valueOf(null: Any),否则将推断为String.valueOf(null: Array[Char])。同样,在Java中,如果您不满意隐式转换,则需要显式转换。在IntelliJ中,对valueOf的使用按Ctrl+B可以让你找到你使用的版本,因为有很多版本可供选择。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接