23 сентября 2021

☕ Основы Java: выделяем 4 разных роли классов

Telegram: @fivoronov
Java – объектно-ориентированный язык, и если спросить, какую роль в нем играют классы, первым в голову приходит создание объектов. Выделим ещё три важных роли этой конструкции языка, помимо самой очевидной.
☕ Основы Java: выделяем 4 разных роли классов

1. Класс как схема для объектов

В первую очередь класс нужен для создания объектов. При объявлении класса мы описываем, какие данные (поля) должен содержать и какие команды (методы) должен выполнять создаваемый объект этого типа. К примеру, класс ниже описывает объекты игрушек с типом Toy. Каждый из них будет помнить уровень оставшегося заряда в виде числа, уметь его снижать и играть с пользователем:

Toy.java
        public class Toy {
    private int charge = 100; // поле

    // метод траты заряда
    public boolean spendCharge(int amount) {
        if (charge >= amount) {
            charge -= amount;
            return true;
        } else {
            return false;
        }
    }

    // метод игры с пользователем
    public void play(String user) {
        if (spendCharge(10)) {
            System.out.println(user + " поиграл в игрушку");
        } else {
            System.out.println("Нет заряда!");
        }
    }
}

    

А вот так этот класс используется для создания объектов:

        Toy toy = new Toy(); // создаём объект
toy.play("Petya"); // вызываем команду

    

Здесь класс послужил чертежём, описывающим внутреннее устройство и поведение объектов этого типа (экземпляров класса).

☕ Подтянуть свои знания по Java вы можете на нашем телеграм-канале «Библиотека Java для собеса»

2. Класс как основа схемы для объектов

Представим себе, что мы хотим два вида объектов игрушек с разным поведением в команде play: например, электро-собаки гавкают во время игры, а электро-кошки мяукают. Создадим два класса (ElectricCat и ElectricDog), которые будут отличаться только поведением метода play. Чтобы не дублировать код работы с зарядом (поле charge и метод spendCharge), воспользуемся механизмом наследования, указав у новых классов класс Toy родительским:

ElectricCat.java
        public class ElectricCat extends Toy {

    @Override
    public void play(String user) {
        if (spendCharge(10)) {
            System.out.println("Мяу-мяу для " + user);
        } else {
            System.out.println("Нет заряда");
        }
    }
}
    
ElectricDog.java
        public class ElectricDog extends Toy {
    @Override
    public void play(String user) {
        if (spendCharge(10)) {
            System.out.println("Гав-гав для " + user);
        } else {
            System.out.println("Нет заряда");
        }
    }
}
    

В примере класс Toy послужил основой для других классов, а не сам был схемой для объектов. Чтобы отчётливее увидеть в этом моменте отдельную роль, сделаем класс Toy абстрактным, чтобы напрямую техничеки запретить создавать объекты этого типа, указав тем самым его предназначение как основы новых типов:

Toy.java
        public abstract class Toy {
    private int charge = 100;

    public boolean spendCharge(int amount) {
        if (charge >= amount) {
            charge -= amount;
            return true;
        } else {
            return false;
        }
    }

    // реализацию оставляем на потомках
    public abstract void play(String user);
}

    

3. Класс как список требований к объектам (полиморфизм)

Использование классов не ограничивается созданием объектов, класс указывают и в качестве типа L-value (т.е. ячейки, в которую кладут значения, например, переменная, поле, параметр и тп). При этом в качестве значения в эту ячейку необязательно класть объект того же самого класса: благодаря полиморфизму подойдёт и другой наследник. На примере наших классов это будет выглядеть так:

        public static void main(String[] args) {
    petyaPlayWith(new ElectricCat());
}

public static void petyaPlayWith(Toy toy) {
    toy.play("Petya");
}
    

Типом параметра здесь выступает класс Toy, но спокойно передаётся и объект класса наследника. Здесь класс Toy играет другую роль: будучи типом параметра он разрешает вызвать объявленный в Toy метод у переданного объекта, но запрещает вызов любого отсутствующего в Toy метода (даже если таковой будет в классе переданного объекта). При этом из-за возможности переопределять поведение методов в наследниках, неизвестно какая реализация метода будет вызвана, известно только, что этот метод будет присутствовать у переданного объекта.

Класс Toy в роли типа L-value превратился в список требований к объектам – значению необходимо иметь в наличии необходимые методы и быть наследником этого типа. Чтобы лучше увидеть отличие этой роли от предыдущей, можно сделать класс полностью абстрактным (без полей и только с абстрактными методами), из-за чего у него не будет реализации, которой можно поделиться с наследниками (кроме самой необходимости реализовать абстрактные методы).

Эта роль оказалась настолько важной, что для таких очень абстрактных классов ввели отдельный инструмент в виде интерфейсов с особыми механиками их наследования классами, в терминах джавы – имплементации; но позже в интерфейсы были добавлены и методы с реализацией через модификатор default.

🧩☕ Интересные задачи по Java для практики можно найти на нашем телеграм-канале «Библиотека задач по Java»

4. Класс как пространство имён

Хоть Java и объектно-ориентированный язык, на нем все же возможно и процедурное программирование, когда методы вызываются не у объектов, а данные хранятся в глобальных ячейках вместо полей объектов. Это достигается через использование слова static при объявлении полей и методов:

Utils.java
        public class Utils {
    public static String getTimedLine(String msg) {
        return "[" + LocalDateTime.now() + "] " + msg;
    }

    public static boolean isDebugMode;
}

    

Для обращения к таким полям и методам объекты класса Utils не нужны, используется имя класса напрямую:

        public static void main(String[] args) {
    Utils.isDebugMode = true;
    System.out.println(Utils.getTimedLine("Debug mode is on!"));
}
    

При этом если нестатическое поле присутствует по экземпляру в каждом объекте класса, то статическое поле сущетвует в единственном экземпляре.

Здесь наш класс никак не участвует в создании объектов, а служит пространством имён для статических полей и методов. То есть, например, внутри одного класса не может быть совпадающих по имени двух статических полей, а внутри разных классов – может; для обращения к статическому классу или методу используется название класса, в котором джаве следует его искать

Итог

Классы в джаве используются не только как схемы для будущих объектов одноимённого типа, но и являются основой для других таковых схем, регламентируют взаимодействие с L-value или служат пространством имён для программирования в процедурном стиле. Надеемся, наша статья помогла вам в этом разобраться и оказалась полезной. Удачи в обучении!

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ