Классы и объекты в Java

🔥 🚀 Важно для всех, кто работает с Java! 🔥
На JavaRocks ты найдешь уникальные туториалы, практические задачи и редкие книги, которых не найти в свободном доступе. Присоединяйся к нашему Telegram-каналу JavaRocks — стань частью профессионального сообщества!

В этой статье разберемся, что такое классы в Java. По сути, классы — это фундамент программирования на Java. Работа разработчика, в значительной степени, сводится к созданию собственных классов с определённой функциональностью. Давайте разберёмся, что это означает на практике.

Java — объектно-ориентированный язык программирования. Любое приложение на Java состоит из объектов, которые взаимодействуют между собой.

Класс в Java — это шаблон (или описание), на основе которого создаются объекты. Он определяет структуру объекта и его поведение. Каждый объект обязательно принадлежит какому-либо классу.

Вот простой пример:

public class Cat {

    String name;
    int age;

}
Run Code

Допустим, мы разрабатываем приложение, связанное с кошками (например, у нас есть ветклиника с доступом к личному кабинету).

Мы создаём класс Cat и добавляем в него две переменные: String name и int age. Эти переменные называются поля (fields) — они принадлежат объекту.

По сути, это шаблон для всех котов, которых мы будем создавать в будущем. Каждый объект Cat будет содержать два поля: имя (name) и возраст (age).

public class Cat {

    String name;
    int age;

    public static void main(String[] args) {
        Cat smudge = new Cat();
        smudge.age = 3;
        smudge.name = "Smudge";

        System.out.println("We created a cat named " + smudge.name + ". His age is " + smudge.age);
    }

}
Run Code

Вот как это работает! Мы создаём кота, задаём ему имя и возраст — и выводим всё это в консоль. Проще некуда. 🙂

Чаще всего классы описывают реальные вещи и явления.

Кошка, стол, человек, молния, книжная страница, колесо — все эти сущности представляются в программе как отдельные классы.

Теперь рассмотрим подробнее переменные, объявленные в классе Cat. Они называются полями или переменными экземпляра (instance variables). Это означает, что каждый объект, созданный на основе класса Cat, будет иметь собственные копии этих переменных. У каждого кота будет своё значение имени и свой возраст — всё как в жизни. 🙂

Кроме переменных экземпляра, бывают ещё и переменные класса (они же static-переменные). Дополним наш пример:

public class Cat {

    String name;
    int age;

    static int count = 0;

    public static void main(String[] args) {
        Cat smudge = new Cat();
        smudge.age = 3;
        smudge.name = "Smudge";
        count++;

        Cat fluffy = new Cat();
        fluffy.age = 5;
        fluffy.name = "Fluffy";
        count++;

        System.out.println("We created a cat named " + smudge.name + ". His age is " + smudge.age);
        System.out.println("We created a cat named " + fluffy.name + ". His age is " + fluffy.age);

        System.out.println("Total number of cats = " + count);
    }
}
Run Code

Вот что отобразится в консоли:

We created a cat named Smudge. His age is 3 
We created a cat named Fluffy. His age is 5 
Total number of cats = 2

Теперь в нашем классе появилась новая переменная — count. Она отвечает за подсчёт созданных котов. Каждый раз, когда в методе main создаётся новый объект Cat, мы увеличиваем значение этой переменной на единицу.

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

Важно: когда мы выводим значение переменной count, мы не пишем smudge.count или fluffy.count. Потому что она — не часть конкретного кота. Она принадлежит всему классу Cat. Поэтому просто count — и всё.

Можно также использовать Cat.count — это тоже будет правильно.

Однако для нестатических переменных, таких как name, такой подход не работает. Мы не можем обращаться к ним вот так:

public class Cat {

    String name;
    int age;

    static int count = 0;

    public static void main(String[] args) {
        Cat smudge = new Cat();
        smudge.age = 3;
        smudge.name = "Smudge";
        count++;

        System.out.println("We created a cat named " + name + ". His age is " + smudge.age);

        System.out.println("Total number of cats = " + count);
    }
}
Run Code

Это ошибка! У каждого кота — своё имя. Компилятор просто не понимает, что вы от него хотите.

“Вывести имя в консоль? А чьё именно?”

Методы

Помимо переменных, у каждого класса есть методы.

Методы определяют функциональность класса — другими словами, то, что объекты этого класса умеют делать. С одним из таких методов вы уже знакомы — это метод main(). Но main — это статический метод, то есть он принадлежит всему классу, а не конкретному объекту (логика здесь такая же, как и у переменных).

Следует помнить, что нестатические (или обычные) методы можно вызывать только у определенных объектов, которых мы создали.

К примеру, если мы хотим написать класс Cat, нам нужно заранее определить, какие действия должен выполнять кот в нашей программе. Исходя из этого, мы и добавим несколько методов:

public class Cat {

    String name;
    int age;

    public void sayMeow() {
        System.out.println("Meow!");
    }

    public void jump() {
        System.out.println("Pounce!");
    }

    public static void main(String[] args) {
        Cat smudge = new Cat();
        smudge.age = 3;
        smudge.name = "Smudge";

        smudge.sayMeow();
        smudge.jump();

    }
}
Run Code

Готово! Теперь наш класс Cat стал ближе к реальному объекту. Помимо имени (Smudge) и возраста (3), у кота появились базовые поведенческие методы: sayMeow() и jump(). Какой бы это был кот без этой «функциональности»? 🙂

Мы берём объект smudge и вызываем у него методы sayMeow() и jump(). Посмотрим, что появится в консоли:

Meow!
Pounce!

Настоящий кот! 🙂

Создание собственных классов. Абстракция

В будущем вам предстоит создавать свои собственные классы. На что стоит обратить внимание при их написании?

Если мы говорим о переменных, то тут вам пригодится такой принцип, как абстракция.

Абстракция — это один из четырёх основных принципов объектно-ориентированного программирования. Она заключается в выделении ключевых и значимых характеристик объекта, исключая те, которые не имеют существенного значения.

Например, давайте создадим картотеку для сотрудников компании. Чтобы представлять сотрудников как объекты, мы написали класс Employee. Какие характеристики важны для картотеки сотрудников нашей компании? Имя, дата рождения, номер социального страхования и ID сотрудника. Но вряд ли нам понадобятся такие данные, как рост сотрудника, цвет глаз или цвет волос для учёта в компании. Эта информация для компании не имеет значения.

Итак, в классе Employee мы объявляем такие переменные, как String name, int age, int socialSecurityNumber и int employeeId, а всё лишнее (например, цвет глаз) мы опускаем. То есть мы создаём абстракцию.

Если же речь идет о картотеке для модельных агентств, то ситуация меняется. Для модели критичными характеристиками будут её рост, цвет глаз и волосы, тогда как SSN нам совершенно не нужен.

Поэтому в классе Model мы создадим переменные: String height, String hair, String eyes.

Вот и вся суть абстракции — ничего сложного! 🙂

Конструкторы

Давайте вернёмся к нашему примеру с котом.

public class Cat {

    String name;
    int age;

    public static void main(String[] args) {
        Cat smudge = new Cat();

        System.out.println("Here the program does something for 2 hours...");

        smudge.age = 3;
        smudge.name = "Smudge";

    }
}
Run Code

Посмотрите на этот код и попробуйте понять, что не так с нашей программой.

У нас в программе два часа существовал кот без имени и возраста!

Это очевидное нарушение логики. База данных ветеринарной клиники не должна содержать неполные записи.

Сейчас наш кот полностью зависит от программиста. Мы просто надеемся, что он не забудет указать имя и возраст, и всё будет нормально. Но если он забудет — в базе появится ошибка: кот без имени.

Как устранить данную проблему? Необходимо запретить создание котов без указания имени и возраста. И здесь нам на помощь приходят конструкторы.

Вот пример:

public class Cat {

    String name;
    int age;

    // Constructor for the Cat class
    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {

        Cat smudge = new Cat("Smudge", 5);
    }
}
Run Code

По сути, конструктор — это шаблон для создания объектов класса.

В нашем примере конструктор требует два параметра: String и int. Это означает, что при создании объекта Cat необходимо передать имя и возраст.

Если теперь попробовать создать безымянного кота — ничего не получится.

public class Cat {

    String name;
    int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {

        Cat smudge = new Cat(); // Error!
    }
}
Run Code

Теперь, когда в классе есть конструктор, компилятор Java точно знает, как должны выглядеть объекты, и не позволит создать объект без указания аргументов.

Теперь рассмотрим ключевое слово this, которое используется внутри конструктора. Оно указывает на конкретный объект, с которым мы работаем.

Код конструктора

public Cat(String name, int age) {
    this.name = name;
    this.age = age;
}
Run Code

можно почти дословно перевести так:

“Имя этого кота (того, которого мы сейчас создаём) = то, что мы передали в name.
Возраст этого кота = то, что мы передали в age.”

После того как конструктор сработает, можно смело проверять — все нужные значения присвоены нашему коту.

public class Cat {

    String name;
    int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {

        Cat smudge = new Cat("Smudge", 5);
        System.out.println(smudge.name);
        System.out.println(smudge.age);
    }
}
Run Code

Вывод:

Smudge 
5

При вызове конструктора:

Cat smudge = new Cat("Smudge", 5);

Конструктор класса сработал и присвоил значения полям экземпляра:

this.name = "Smudge";
this.age = 5;
Run Code

Аргументы, которые мы передали в конструктор, были присвоены объекту smudge — именно на него ссылается this.

На самом деле, даже если вы не объявите в классе ни одного конструктора, он всё равно будет вызываться!

Но как такое возможно? О_О

Всё просто: у любого класса есть “конструктор по умолчанию”. Он не требует параметров и автоматически срабатывает при создании объекта.

public class Cat {

    public static void main(String[] args) {

        Cat smudge = new Cat(); // The default constructor is invoked here
    }
}
Run Code

Сначала может показаться, что ничего особенного не происходит. Ну подумаешь, объект создали — а где же тут вообще конструктор?

Чтобы это проверить, добавим в класс Cat явный конструктор без параметров и в его теле выведем строку в консоль. Если при создании объекта сообщение появится — значит, вызов конструктора состоялся.

public class Cat {

    public Cat() {
        System.out.println("A cat has been created!");
    }

    public static void main(String[] args) {

        Cat smudge = new Cat(); // The default constructor is invoked here
    }
}
Run Code

Вывод:

A cat has been created!

Вот и подтверждение: конструктор по умолчанию действительно существует в каждом классе, просто его не видно.

Но есть тонкость. Если в класс добавлен хотя бы один конструктор с параметрами, Java удаляет конструктор по умолчанию.

На практике мы уже столкнулись с этим в примере выше:

public class Cat {

    String name;
    int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {

        Cat smudge = new Cat(); // Error!
    }
}
Run Code

Создать кота без имени и возраста не получилось, потому что мы объявили конструктор с аргументами (String и int).

Это автоматически удалило конструктор по умолчанию из класса.

Важно помнить: если вам нужен и конструктор с параметрами, и без, — нужно все варианты описывать вручную.

Наша клиника хочет делать добрые дела и помогать бездомным котятам, чьи имя и возраст неизвестны.

Тогда наш код должен выглядеть так:

public class Cat {

    String name;
    int age;

    // For cats with owners
    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // For street cats
    public Cat() {
    }

    public static void main(String[] args) {

        Cat smudge = new Cat("Smudge", 5);
        Cat streetCat = new Cat();
    }
}
Run Code

С явным конструктором по умолчанию мы можем создавать и обычных, и бездомных котов. В конструкторе можно присваивать значения напрямую — необязательно всегда передавать их через аргументы. Например, называть всех уличных котов по шаблону: "Street cat No. <count>" :

public class Cat {

    String name;
    int age;

    static int count = 0;

    public Cat() {
        count++;
        this.name = "Street cat No. " + count;
    }

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {

        Cat streetCat1 = new Cat();
        Cat streetCat2 = new Cat();
        System.out.println(streetCat1.name);
        System.out.println(streetCat2.name);
    }
}
Run Code

У нас есть переменная count, которая считает количество уличных котов. Каждый раз при вызове конструктора по умолчанию мы увеличиваем count на 1 и добавляем это число к имени кота.

Очень важно соблюдать порядок аргументов в конструкторах. Попробуем поменять местами аргументы name и age в нашем конструкторе — и посмотрим, что получится.

public class Cat {

    String name;
    int age;

    public Cat(int age, String name) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {

        Cat smudge = new Cat("Smudge", 10); // Error!
    }
}
Run Code

Ошибка! Конструктор четко указывает, что при создании объекта Cat ему должны быть переданы число и строка — именно в таком порядке. Поэтому наш код не работает.

Запомните: при написании собственных классов порядок аргументов в конструкторе имеет значение и должен строго соблюдаться.

public Cat(String name, int age) {
    this.name = name;
    this.age = age;
}

public Cat(int age, String name) {
    this.age = age;
    this.name = name;
}
Run Code

Это два совершенно разных конструктора!

А теперь — немного практики, чтобы закрепить материал. 🙂

1. Музей древностей.

Ваша задача — разработать класс Artifact.
В музее хранятся три типа артефактов:
1. О первом мы знаем только номер, который присвоил музей (например: 212121).
2. О втором — порядковый номер и культуру, создавшую артефакт (например: 212121, "Aztecs").
3. О третьем — порядковый номер, культуру и век, в котором артефакт был создан (например: 212121, "Aztecs", 12).

Создайте класс Artifact, описывающий эти древности, и напишите необходимый набор конструкторов.
Затем в методе main() создайте по одному артефакту каждого типа.

public class Artifact {

    // Напишите свой код здесь

    public static void main(String[] args) {
        // Напишите свой код здесь
    }
}
Run Code

2. Сайт знакомств

Нужно создать базу пользователей для сайта знакомств.
Но возникла проблема: вы забыли правильный порядок аргументов, а техническая документация отсутствует.
Создайте класс User с полями: name (тип String), age (тип short) и height (тип int).
Добавьте необходимое количество конструкторов, чтобы можно было задавать name, age и height в любом порядке.

public class User {

    String name;
    short age;
    int height;

    // Напишите свой код здесь

    public static void main(String[] args) {

    }
}
Run Code

Чтобы закрепить полученные знания, предлагаем вам посмотреть видеоурок (на английском языке):

Перевод статьи «Java Classes and Objects».

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх