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

Класс и объект

Чему вы научитесь

Чему вы научитесь:

  • описывать свой тип классом, создавать объекты через new и объяснять, почему две переменные могут вести к одному объекту;
  • задавать поля объекта конструктором и различать внутри него this.x и x;
  • закрывать поля словом private и отличать поле объекта от static-поля;
  • печатать объект через toString и читать сообщение NullPointerException.

Представьте чертёж дома. На нём написано, что будет у каждого дома: сколько этажей, какого цвета крыша. Жить в чертеже нельзя, это только описание. Зато по одному чертежу можно построить целую улицу. Дома выйдут похожими, но у каждого своя крыша, и если перекрасить один дом, соседний останется прежним.

Java устроена так же. Чертёж — это класс: он описывает, какие данные будут у каждого объекта. Дом — это объект, который программа строит по чертежу, пока работает.

Зачем это нужно? Возьмём точку на плоскости. Это два числа, x и y, и они всегда идут вместе. Для сотни точек понадобилось бы двести переменных, и x одной точки оказался бы рядом с y другой. Класс позволяет сказать Java: «точка — это пара чисел», и дальше работать с точкой как с одной вещью:

JAVA
public class Main {
    public static void main(String[] args) {
        Point p = new Point();
        p.x = 3;
        p.y = 4;
        System.out.println(p.x + " " + p.y); // 3 4
        Point q = new Point();
        System.out.println(q.x + " " + q.y); // 0 0
    }
}

class Point {
    int x;
    int y;
}

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

  1. Внизу файла объявлен класс Point с двумя полями: x и y. Это чертёж.
  2. new Point() строит по чертежу объект — одну точку, а Point p заводит для неё переменную.
  3. p.x = 3; и p.y = 4; кладут числа в поля этой точки. Знак . между именем переменной и именем поля читается как «поле x той точки, к которой ведёт p».
  4. Первый println печатает 3 4.
  5. new Point() строит вторую точку. В её поля никто ничего не клал, поэтому печатается 0 0.

Что описывает класс

Класс — описание нового типа: какие поля будут у каждого его объекта. После объявления class Point в программе есть тип Point, и переменную этого типа заводят так же, как переменную int или String.

Объект — одна конкретная вещь, построенная по классу словом new. Объект ещё называют экземпляром класса. Что важно знать об объектах:

  • у каждого объекта свои поля: запись в p.x не меняет q.x;
  • поля нового объекта получают значения по умолчанию, как поля static: 0 у чисел, false у boolean, null у String;
  • объектов одного класса можно создать сколько угодно, и каждый new строит новый.

Класс Point объявлен в том же файле, после Main, и без слова public. В одном файле может быть только один public-класс, и файл называется по его имени — Main.java.

Переменная хранит адрес

Вернёмся к улице. Переменная p хранит не сам дом, а бумажку с его адресом. Как и с массивом, это ссылка: new Point() строит объект в куче и возвращает ссылку на него. Посмотрим, что получится, если скопировать такую бумажку:

JAVA
Point a = new Point();
Point b = a;
b.x = 7;
System.out.println(a.x); // 7

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

  1. Point a = new Point(); строит дом и кладёт его адрес в a.
  2. Point b = a; копирует бумажку с адресом, а не дом. Теперь a и b ведут к одному объекту.
  3. b.x = 7; идёт по адресу из b и меняет поле этого объекта.
  4. a.x печатает 7: по адресу из a стоит тот же дом.

Если теперь написать b = new Point();, на бумажке b окажется адрес нового дома. Старый дом от этого не пострадает: a по-прежнему ведёт к нему, и a.x останется 7.

Здесь картинка с домами неточна. В настоящий дом можно прийти и без бумажки, если знать улицу. А к объекту программа попадает только по ссылке: если ни одна переменная не ведёт к объекту, работать с ним уже нельзя.

Объект в параметре метода

Этот вопрос часто задают на собеседовании: может ли метод изменить объект, который ему передали? Давайте посмотрим:

JAVA
public class Main {
    static void move(Point p) {
        p.x = 10;
        p = new Point();
        p.x = 20;
    }

    public static void main(String[] args) {
        Point a = new Point();
        move(a);
        System.out.println(a.x); // 10
    }
}

class Point { int x; int y; }

Класс Point здесь тот же, только записан в одну строку. Разберём вызов move(a) по шагам:

  1. Параметр p получает копию значения a, то есть копию адреса. Теперь a и p ведут к одной точке.
  2. p.x = 10; меняет поле этой точки, и изменение увидит main.
  3. p = new Point(); пишет на бумажку p адрес новой точки. Бумажка a в main осталась прежней.
  4. p.x = 20; меняет уже новую точку, о которой main не знает.
  5. main печатает 10.

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

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

Объявили переменную, но забыли построить объект:

JAVA
Point p;
p.x = 3; // ✗
TEXT
error: variable p might not have been initialized

Программа не компилируется. Point p; заводит только бумажку для адреса, а дом никто не строил, и записать 3 некуда. Объект создаёт только new: Point p = new Point();.

Главное

  • Класс описывает поля нового типа, а new строит по нему объект; у каждого объекта свои поля.
  • Переменная типа Point хранит ссылку на объект, поэтому Point b = a копирует ссылку, и a и b ведут к одному объекту.
  • Метод получает копию ссылки: он может изменить поля объекта, но присваивание параметру не меняет переменную вызывающего кода.
  • Без new объекта нет: Point p; не создаёт точку.