$ sudo teach IT
МОДУЛЬ 7 · УРОК 1

Классы и объекты: введение

Объектно-ориентированное программирование — это способ мышления, а не просто набор инструментов. Сегодня мы делаем самый важный шаг: понимаем, что такое классы и объекты, и зачем они нужны в Java.

~30 минут Для новичков Java ООП

Программирование до и после ООП

Чтобы понять, зачем нужны классы и объекты, представьте мир без них. Программы писались в процедурном стиле: функции, глобальные переменные, данные хранились отдельно от логики. Это работало для 100–200 строк кода, но когда проекты вырастали до тысяч строк, начинался хаос.

Представьте, что вы готовите复杂ное блюдо. Процедурный рецепт: «возьмите 200 г муки, добавьте 2 яйца, перемешайте, запекайте при 180°C». Для нескольких рецептов — отлично. Но когда рецептов становится 100, и каждый раз приходится заново писать инструкции по замешиванию теста — вы сходит с ума.

Объектно-ориентированное программирование (ООП) предлагает другой подход: мы объединяем данные и методы в одну сущность — объект. Вместо «сначала данные, потом функции» мы думаем: «у меня есть объект Печенье, он знает свои ингредиенты и умеет сам себя запекать».

Java построена целиком на принципах ООП. Вы уже сталкивались с объектами: String, массивы, Scanner. Каждый String — это объект. Каждый массив — это объект. Теперь вы научитесь создавать собственные типы.

ООП — это парадигма программирования, в которой программа состоит из взаимодействующих объектов, каждый из которых имеет состояние (поля) и поведение (методы).

Класс — это чертёж

Класс — это фундаментальное понятие в Java. Насколько хорошо вы его освоите — так и будете программировать.

Аналогия: класс = рецепт торта, объект = торт. Рецепт описывает ингредиенты и шаги приготовления. Вы не можете съесть рецепт. Но вы можете испечь 10 тортов по одному рецепту. Каждый торт будет разным — ванильный, шоколадный, с ягодами. В Java: класс = описание, объект = конкретный экземпляр.

Ещё одна аналогия: класс — это форма для печенья. Форма одна, а печенье можно напечь сколько угодно. Каждое печенье будет иметь форму формы, но может отличаться по цвету, начинке, размеру. Форма описывает СТРУКТУРУ, а каждое печенье — это КОНКРЕТНЫЙ ВАРИАНТ.

Класс определяет СОСТОЯНИЕ (поля/данные) и ПОВЕДЕНИЕ (методы). Поля — это то, что объект ЗНАЕТ. Методы — это то, что объект УМЕЕТ ДЕЛАТЬ.

public class Dog {
    String name;
    int age;
    String breed;

    void bark() {
        System.out.println("Гав! Меня зовут " + name);
    }

    void eat() {
        System.out.println(name + " ест");
    }

    void displayInfo() {
        System.out.println("Кличка: " + name);
        System.out.println("Возраст: " + age);
        System.out.println("Порода: " + breed);
    }
}

Сам по себе класс — это просто описание. Как рецепт в книге. Чтобы получить живую собаку, которая гавкает и ест, нужно создать ОБЪЕКТ этого класса.

Обратите внимание на структуру класса. Сначала идут поля (переменные внутри класса), потом методы (функции внутри класса). Поля описывают данные: кличка, возраст, порода. Методы описывают действия: гавкать, есть, показывать информацию.

Объект — это экземпляр

Если класс — это чертёж, то объект — это конкретная вещь, построенная по этому чертежу. Если класс — это рецепт торта, то объект — сам торт, который можно съесть.

Ключевое слово new создаёт объекты в Java:

public class Main {
    public static void main(String[] args) {
        Dog myDog = new Dog();
        myDog.name = "Бобик";
        myDog.age = 3;
        myDog.breed = "Лабрадор";
        myDog.bark();
        myDog.eat();
        myDog.displayInfo();
    }
}

Разберём каждую строку:

  • Dog myDog — объявляем переменную типа Dog. Пока она равна null (никакого объекта нет).
  • = new Dog() — создаём новый объект через оператор new и присваиваем ссылку на него переменной myDog.
  • myDog.name = "Бобик" — обращаемся к полю объекта через точку и задаём значение.
  • myDog.bark() — вызываем метод объекта через точку. Метод печатает «Гав! Меня зовут Бобик».

Из одного класса можно создать сколько угодно объектов. У каждого будут СВОИ значения полей:

Dog d1 = new Dog();
d1.name = "Бобик";

Dog d2 = new Dog();
d2.name = "Рекс";

Dog d3 = new Dog();
d3.name = "Мухтар";

d1.bark(); // Гав! Меня зовут Бобик
d2.bark(); // Гав! Меня зовут Рекс
d3.bark(); // Гав! Меня зовут Мухтар

Каждый объект — это отдельная сущность со своими данными. Бобик не знает, что Рекс существует, и наоборот. Они независимы.

Оператор new: что происходит «под капотом»

Когда вы пишете new Dog(), Java выполняет четыре шага:

  1. Выделяет память в куче (heap) — специальной области памяти для объектов.
  2. Инициализирует поля значениями по умолчанию — числа становятся 0, boolean — false, ссылочные типы — null.
  3. Вызывает конструктор — специальный метод, который настраивает объект (об этом подробно в уроке 3).
  4. Возвращает ссылку (адрес в памяти) — переменная хранит не сам объект, а указатель на него.

Переменная хранит ССЫЛКУ, а не сам объект. Это как пульт от телевизора: пульт — это не телевизор, но вы управляете телевизором через него. Пульт — это ссылка, телевизор — это объект в куче.

Если ссылочная переменная равна null, попытка вызвать через неё метод приведёт к NullPointerException — самой частой ошибке в Java. Всегда проверяйте ссылки на null перед вызовом методов!

Dog myDog = null;
// myDog.bark(); // NullPointerException — краш!
myDog = new Dog(); // Теперь myDog ссылается на объект
myDog.bark(); // Работает!

Ссылочные типы vs примитивы

В Java два мира: примитивы (int, double, boolean, char) и ссылочные типы (все объекты). Понимание их различий критически важно.

Примитивы хранят значение напрямую в переменной. Когда вы копируете примитив, создаётся полная копия значения. Изменение копии НЕ влияет на оригинал.

int a = 5;
int b = a;
b = 10;
System.out.println(a); // 5 — a не изменилось!

Представьте два стакана с водой. Вы налили воду в стакан A (a = 5), потом перелили в стакан B (b = a). Теперь в обоих по 5. Вы налили в B ещё воды (b = 10). В A всё ещё 5 — это отдельные стаканы.

Ссылочные типы хранят адрес объекта. Когда вы копируете ссылку, вы копируете АДРЕС, а не сам объект. Обе переменные указывают на ОДИН И ТОТ ЖЕ объект.

Dog d1 = new Dog();
d1.name = "Бобик";

Dog d2 = d1;
d2.name = "Рекс";

System.out.println(d1.name); // Рекс! Объект один!

Здесь d1 и d2 — два пульта от одного телевизора. Нажали кнопку через d2 — изображение изменилось. Нажали через d1 — увидели то же самое. Пультов два, телевизор один.

Это одна из самых частых ошибок новичков: думать, что Dog d2 = d1 создаёт нового собаку. Нет! Это создаёт вторую ссылку на ту же собаку.

Жизненный цикл объекта

У каждого объекта есть жизнь:

  1. Рождение: new Dog() создаёт объект в куче. Выделяется память, вызывается конструктор.
  2. Жизнь: На объект существует хотя бы одна ссылка. Через неё можно вызывать методы и менять поля.
  3. Смерть: На объект НЕТ ни одной ссылки. Сборщик мусора (Garbage Collector) удаляет его и освобождает память.
Dog dog = new Dog();  // Рождение — объект появился в куче
dog.bark();           // Жизнь — объект используется
dog = null;           // Объект готов к сборке мусора
// dog.bark();       // Нельзя — ссылки нет!

Сборщик мусора работает автоматически. Вам не нужно вручную освобождать память (в отличие от C++). Java сама решает, когда удалить неиспользуемые объекты.

Можно иметь несколько ссылок на один объект. Объект удалится только когда ВСЕ ссылки на него станут равны null или выйдут из области видимости:

Dog d1 = new Dog();
Dog d2 = d1;        // Две ссылки на один объект
d1 = null;          // Объект всё ещё жив — d2 ссылается
d2 = null;          // Теперь объект можно удалять

Практический пример: класс Книга

Давайте создадим класс Book — модель книги в библиотеке. Это классический пример, который показывает, как класс группирует данные и поведение:

public class Book {
    String title;
    String author;
    int year;
    int pages;
    boolean isOpen;

    void open() {
        isOpen = true;
        System.out.println("Книга \"" + title + "\" открыта");
    }

    void close() {
        isOpen = false;
        System.out.println("Книга \"" + title + "\" закрыта");
    }

    void read(int minutes) {
        if (isOpen) {
            System.out.println("Читаем \"" + title + "\" " + minutes + " минут");
        } else {
            System.out.println("Сначала откройте книгу!");
        }
    }

    void displayInfo() {
        System.out.println("Название: " + title);
        System.out.println("Автор: " + author);
        System.out.println("Год: " + year);
        System.out.println("Страниц: " + pages);
        System.out.println("Открыта: " + (isOpen ? "да" : "нет"));
    }

    String getDescription() {
        return "\"" + title + "\" автора " + author + " (" + year + ")";
    }
}

Теперь создадим объекты:

public class Library {
    public static void main(String[] args) {
        Book b1 = new Book();
        b1.title = "Война и мир";
        b1.author = "Толстой";
        b1.year = 1869;
        b1.pages = 1225;

        Book b2 = new Book();
        b2.title = "Преступление и наказание";
        b2.author = "Достоевский";
        b2.year = 1866;
        b2.pages = 671;

        b1.open();
        b1.read(30);
        b1.close();
        System.out.println(b1.getDescription());

        System.out.println("---");

        b2.open();
        b2.displayInfo();
        b2.close();
    }
}

Обратите внимание: каждая книга — отдельный объект со своими данными. «Война и мир» не знает о «Преступлении и наказании». Они независимы.

Стек и куча: где живут данные

Память Java разделена на две основные области:

Стек (Stack) — хранит примитивы и ссылки на объекты. Быстрая память, ограниченного размера. Когда метод завершается, его локальные переменные удаляются из стека.

Куча (Heap) — хранит сами объекты. Общая память, управляемая сборщиком мусора. Может быть размером в гигабайты.

int x = 5;
String name = "Привет";
Dog dog = new Dog();

// x (примитив) — в стеке, значение 5
// name (ссылка) — в стеке, указывает на строку в куче
// dog (ссылка) — в стеке, указывает на Dog в куче
// dog.name, dog.age — внутри объекта Dog в куче

Представьте стек как рюкзак, который носите с собой (быстро, удобно, мало места). А кучу — как склад (большой, но ходить до него дальше). Примитивы и ссылки — в рюкзаке. Самые объекты — на складе.

Почему классы — это здорово

Классы дают нам:

  • Группировку данных: вместо 5 отдельных переменных — один объект Dog с полями name, age, breed.
  • Группировку поведения: dog.bark() понятнее, чем bark(dog). Объект сам знает, как гавкать.
  • Переиспользование: создаёте столько объектов, сколько нужно. Класс один, объектов — тысячи.
  • Защиту данных: инкапсуляция (следующий урок) — контроль доступа к полям.
  • Читаемость: код с объектами проще читать и поддерживать.

Без ООП код превращается в «кашу» из переменных и функций. С ООП — это структурированная, понятная система.

Ещё пример: класс Автомобиль

Давайте закрепим понимание на другом примере:

public class Car {
    String brand;
    String model;
    int year;
    double fuelLevel;
    boolean isRunning;

    void start() {
        if (!isRunning && fuelLevel > 0) {
            isRunning = true;
            System.out.println(brand + " " + "заведён");
        } else if (fuelLevel <= 0) {
            System.out.println("Нет топлива!");
        } else {
            System.out.println("Автомобиль уже работает");
        }
    }

    void stop() {
        if (isRunning) {
            isRunning = false;
            System.out.println(brand + " заглушен");
        }
    }

    void drive(double km) {
        if (!isRunning) {
            System.out.println("Сначала заведите двигатель!");
            return;
        }
        double fuelUsed = km * 0.08;
        if (fuelUsed > fuelLevel) {
            System.out.println("Недостаточно топлива для поездки на " + km + " км");
        } else {
            fuelLevel -= fuelUsed;
            System.out.println("Проехали " + km + " км. Осталось топлива: " + fuelLevel + " л");
        }
    }

    void refuel(double liters) {
        if (liters > 0) {
            fuelLevel += liters;
            if (fuelLevel > 60) fuelLevel = 60;
            System.out.println("Заправлено. Уровень топлива: " + fuelLevel + " л");
        }
    }

    void displayStatus() {
        System.out.println(brand + " " + model + " (" + year + ")");
        System.out.println("  Двигатель: " + (isRunning ? "работает" : "выключен"));
        System.out.println("  Топливо: " + fuelLevel + " л");
    }
}
public class Garage {
    public static void main(String[] args) {
        Car car1 = new Car();
        car1.brand = "Toyota";
        car1.model = "Camry";
        car1.year = 2023;
        car1.fuelLevel = 50;
        car1.isRunning = false;

        Car car2 = new Car();
        car2.brand = "BMW";
        car2.model = "X5";
        car2.year = 2022;
        car2.fuelLevel = 30;
        car2.isRunning = false;

        car1.start();
        car1.drive(100);
        car1.displayStatus();

        System.out.println("---");

        car2.start();
        car2.drive(50);
        car2.displayStatus();
    }
}

Toyota и BMW — два разных объекта одного класса Car. У каждого свои данные и своё поведение. Изменение одного не влияет на другой.

Типичные ошибки новичков

Вот самые частые ошибки при работе с классами и объектами:

// Ошибка 1: Попытка использовать объект до создания
Dog myDog;
// myDog.bark(); // Ошибка! myDog равен null

// Ошибка 2: Забыли создать объект
// Dog dog; dog.name = "Рекс"; // Ошибка!

// Ошибка 3: Путаница примитивов и ссылок
String s1 = "Привет";
String s2 = s1;
s2 = "Пока";
System.out.println(s1); // "Привет" — строки неизменяемы!

// Ошибка 4: NullPointerException
Dog dog = null;
// dog.bark(); // Краш! Ссылка null

// Правильно:
Dog dog2 = new Dog();
dog2.name = "Рекс";
dog2.bark(); // Работает!

Краткое повторение

Давайте повторим ключевые概念ы этого урока:

  • Класс — это чертёж/шаблон. Описывает поля (данные) и методы (действия).
  • Объект — это экземпляр класса. Конкретная вещь, созданная по чертежу.
  • new — оператор создания объекта. Выделяет память и возвращает ссылку.
  • Ссылка — адрес объекта в памяти. Хранится в переменной.
  • Примитивы — хранят значение напрямую. Копирование = копия значения.
  • Ссылочные типы — хранят адрес. Копирование = копия адреса (объект один).
  • null — означает «нет объекта». Вызов метода через null = NullPointerException.
  • Сборщик мусора — автоматически удаляет объекты без ссылок.
  • Стек — примитивы и ссылки. Куча — объекты.

Итоги урока

  • Класс — это шаблон/чертёж; объект — это экземпляр, созданный из него.
  • Оператор new выделяет память в куче и возвращает ссылку.
  • Переменные хранят ссылки, а не сами объекты.
  • Примитивы хранят значения; объекты доступны по ссылке.
  • null означает «нет ссылки»; NullPointerException — самая частая ошибка.
  • Сборщик мусора удаляет объекты без ссылок.
  • Классы группируют данные (поля) и поведение (методы).

Следующий урок: Поля и методы класса — как их объявлять, использовать this, создавать геттеры и сеттеры.

Тест: Классы и объекты — введение

10 вопросов

Класс Dog

Premium