toString и null
Напечатаем точку целиком: System.out.println(p);. Хочется увидеть координаты, но программа выведет что-то вроде этого:
Point@1dbd16a6Это имя класса, знак @ и шестнадцатеричное число, которое JVM выдала объекту. У вас число может оказаться другим: оно зависит от JVM и от способа запуска. Похоже, будто вы попросили описать дом, а вам назвали чертёж и номер на табличке.
Когда println получает объект, он просит объект описать себя строкой — вызывает его метод toString. Такой метод есть у каждого объекта, но стандартный возвращает как раз строку Point@…. Свой класс может описать себя сам.
Свой метод toString
Добавим в класс Point метод, который возвращает координаты:
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 + ")";
}
}Давайте разберём программу по шагам:
- В классе
Pointпоявился методtoStringс заголовкомpublic String toString(). - Метод собирает строку из координат:
"(" + x + ", " + y + ")". System.out.println(p)сам вызываетp.toString()и печатает результат:(0, 0).
Что важно знать о toString:
- заголовок пишут ровно так: имя
toString, без параметров, результатString; publicобязателен: без него программа не скомпилируется;- такой метод заменяет стандартный
toString, а как устроена эта замена и зачем над ним пишут@Override, разберём в теме о наследовании.
Сложение строки с объектом через + тоже вызывает его toString:
p.x = 3;
System.out.println("p = " + p); // p = (3, 0)Ссылка null
Переменная типа Point может хранить особую ссылку null — бумажку, на которой нет адреса. Она не ведёт ни к какому объекту. null лежит в поле типа String или Point, которому ничего не присвоили, и его можно записать в переменную явно:
Point r = p;
p = null;
System.out.println(r); // (3, 0)
System.out.println(p); // null
System.out.println(p == null); // trueРазберём по строкам:
Point r = p;— вторая бумажка с адресом той же точки.p = null;стирает адрес только на бумажкеp. Сама точка осталась на месте.rпо-прежнему ведёт к ней, иprintlnпечатает(3, 0).println(p)печатает словоnull: объекта нет, описывать нечего.p == nullдаётtrue. Так проверяют, есть ли объект, прежде чем с ним работать.
println и + справляются с null сами и пишут слово null. Но поле или метод через такую переменную взять нельзя: p.toString() при p, равном null, остановит программу.
Здесь картинка с бумажкой неточна. С пустой бумажкой человек никуда не пойдёт и вернётся к своим делам, а Java на попытку пойти по пустому адресу останавливает программу.
Частая ошибка
Прочитали поле через переменную, в которой лежит null:
Point p = null;
System.out.println(p.x); // ✗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>.