Почему нулевая ссылка печатается как "null"

В println здесь o.toString() выбрасывает NPE, но o1, нет. Почему?

public class RefTest {
    public static void main(String[] args) {
        Object o = null;
        Object o1 = null;
        System.out.println(o.toString()); //throws NPE
        System.out.print(o1); // does not throw NPE
    }
}

Ответ 1

Это может помочь показать вам байт-код. Взгляните на следующий javap вывод вашего класса:

> javap -classpath target\test-classes -c RefTest

Compiled from "RefTest.java"
public class RefTest extends java.lang.Object{
public RefTest();
  Code:
   0:   aload_0
   1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   aconst_null
   1:   astore_1
   2:   aconst_null
   3:   astore_2
   4:   getstatic       #17; //Field java/lang/System.out:Ljava/io/PrintStream;
   7:   aload_1
   8:   invokevirtual   #23; //Method java/lang/Object.toString:()Ljava/lang/String;
   11:  invokevirtual   #27; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  getstatic       #17; //Field java/lang/System.out:Ljava/io/PrintStream;
   17:  aload_2
   18:  invokevirtual   #33; //Method java/io/PrintStream.print:(Ljava/lang/Object;)V
   21:  return

}

Просто взглянув на основной метод, вы можете увидеть интересующие нас строки, где Code равно 8 и 33.

В коде 8 показан байт-код для вызова o.toString(). Здесь o есть null, и поэтому любая попытка вызова метода на null приводит к NullPointerException.

Код 18 показывает ваш объект null, передаваемый как параметр методу PrintStream.print(). Если посмотреть на исходный код этого метода, вы увидите, почему это не приводит к NPE:

public void print(Object obj) {
    write(String.valueOf(obj));
}

и String.valueOf() сделают это с помощью null s:

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

Итак, вы можете видеть, что там есть тест, который имеет дело с null и предотвращает NPE.

Ответ 2

Это потому, что print(Object) использует String.valueOf(Object) для преобразования (в сторону: после преобразования println(Object) будет вести себя как print(String), print(Object) эффективно использует write(int)). String.valueOf(Object) не выбрасывает NPE, как o.toString(), и вместо этого определяется return "null" для нулевого параметра.

Ответ 3

System.out.println(o.toString())

o.toString() пытается разыменовать нулевой объект, чтобы преобразовать его в строку, прежде чем передать его в println.

System.out.print(o1);

Вызываемый print - это вариант print(Object), который сам проверяет, что объект не является нулевым, прежде чем продолжить.