Перейти к содержимому
Теория · Классы и объекты · урок 9 из 10

toString и null

Напечатаем точку целиком: System.out.println(p);. Хочется увидеть координаты, но программа выведет что-то вроде этого:

TEXT
Point@1dbd16a6

Это имя класса, знак @ и шестнадцатеричное число, которое JVM выдала объекту. У вас число может оказаться другим: оно зависит от JVM и от способа запуска. Похоже, будто вы попросили описать дом, а вам назвали чертёж и номер на табличке.

Когда println получает объект, он просит объект описать себя строкой — вызывает его метод toString. Такой метод есть у каждого объекта, но стандартный возвращает как раз строку Point@…. Свой класс может описать себя сам.

Свой метод toString

Добавим в класс Point метод, который возвращает координаты:

JAVA
public class Main {
    public static void main(String[] args) {
        Point p = new Point();
        System.out.println(p); // (0, 0)
    }
}

class Point {
    int x;
    int y;
    public String toString() {
        return "(" + x + ", " + y + ")";
    }
}

Давайте разберём программу по шагам:

  1. В классе Point появился метод toString с заголовком public String toString().
  2. Метод собирает строку из координат: "(" + x + ", " + y + ")".
  3. System.out.println(p) сам вызывает p.toString() и печатает результат: (0, 0).

Что важно знать о toString:

  • заголовок пишут ровно так: имя toString, без параметров, результат String;
  • public обязателен: без него программа не скомпилируется;
  • такой метод заменяет стандартный toString, а как устроена эта замена и зачем над ним пишут @Override, разберём в теме о наследовании.

Сложение строки с объектом через + тоже вызывает его toString:

JAVA
p.x = 3;
System.out.println("p = " + p); // p = (3, 0)

Ссылка null

Переменная типа Point может хранить особую ссылку null — бумажку, на которой нет адреса. Она не ведёт ни к какому объекту. null лежит в поле типа String или Point, которому ничего не присвоили, и его можно записать в переменную явно:

JAVA
Point r = p;
p = null;
System.out.println(r);         // (3, 0)
System.out.println(p);         // null
System.out.println(p == null); // true

Разберём по строкам:

  1. Point r = p; — вторая бумажка с адресом той же точки.
  2. p = null; стирает адрес только на бумажке p. Сама точка осталась на месте.
  3. r по-прежнему ведёт к ней, и println печатает (3, 0).
  4. println(p) печатает слово null: объекта нет, описывать нечего.
  5. p == null даёт true. Так проверяют, есть ли объект, прежде чем с ним работать.

println и + справляются с null сами и пишут слово null. Но поле или метод через такую переменную взять нельзя: p.toString() при p, равном null, остановит программу.

Здесь картинка с бумажкой неточна. С пустой бумажкой человек никуда не пойдёт и вернётся к своим делам, а Java на попытку пойти по пустому адресу останавливает программу.

Частая ошибка

Прочитали поле через переменную, в которой лежит null:

JAVA
Point p = null;
System.out.println(p.x); // ✗
TEXT
Exception in thread "main" java.lang.NullPointerException: Cannot read field "x" because "<local1>" is null

Программа компилируется: компилятор не следит, лежит ли в переменной null. Ошибку находит JVM, когда доходит до p.x: объекта нет, и поле x взять неоткуда. JVM бросает исключение NullPointerException и останавливает программу. На собеседовании о нём спрашивают постоянно: это одно из самых частых исключений в Java.

Сообщение подсказывает, что случилось:

  • Cannot read field "x" — не удалось прочитать поле x;
  • because "<local1>" is null — потому что в переменной null;
  • следующая строка вывода, at Main.main(…), называет метод и номер строки.

Почему <local1>, а не p? java Main.java не сохраняет имён переменных, и JVM называет p по номеру: это первая локальная переменная после args. Если скомпилировать программу командой javac -g Main.java и запустить java Main, сообщение назовёт имя: because "p" is null.

Чтобы ошибки не было, создайте объект до чтения — Point p = new Point(); — или проверьте ссылку: if (p != null).

Главное

  • println(p) и "p = " + p вызывают p.toString(); свой public String toString() описывает объект строкой, а стандартный печатает Point@….
  • null — ссылка, которая не ведёт ни к какому объекту; p = null стирает ссылку только в p.
  • Поле или метод через null — это исключение NullPointerException при запуске, а не ошибка компиляции.
  • Сообщение исключения называет, что не удалось и какая переменная была null; без javac -g вместо имени стоит номер, например <local1>.