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

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

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

Классы и объекты: определение.

В основе объектно-ориентированного программирования (ООП) лежат такие понятия, как классы и объекты.

Класс Java напоминает чертеж дома, в котором дана инструкция к его постройке. Тем не менее, это не сам дом. Классы определяют:

  • Свойства (атрибуты или поля) – характеристики объекта
  • Методы (функции) – то, что может делать объект
  • Конструкторы – специальные методы, создающие новые объекты

Вот простой пример объявления класса в Java:

public class Dog {
    // Свойства (переменные экземпляра)
    String breed;
    String color;
    int age;

    // Метод
    public void bark() {
        System.out.println("Woof! Woof!");
    }

    // Метод с возвращаемым значением
    public int getAgeInHumanYears() {
        return age * 7;
    }
}
Run Code

Если класс – это чертеж, то объект – это дом, который вы строите. Объекты – это экземпляры классов, хранящиеся в памяти. Можно провести такую аналогию: класс – это формочка для печенья, а объекты – это сами печеньки: формы у них одинаковые, а состав может быть разным 🙂

Вот пример создания объекта в Java:

// Создаём объекты Dog 
Dog myDog = new Dog();  // первый объект
Dog neighborDog = new Dog();  // второй объект

// Определяем свойства для myDog
myDog.breed = "Golden Retriever";
myDog.color = "Golden";
myDog.age = 3;

// Определяем свойства для neighborDog
neighborDog.breed = "Dalmatian";
neighborDog.color = "White with black spots";
neighborDog.age = 5;

// Используем метод
myDog.bark();  // Вывод: Woof! Woof!
System.out.println(myDog.breed + " is " + myDog.getAgeInHumanYears() + " years old in human years.");
// Вывод: Golden Retriever is 21 years old in human years.
Run Code

Основные составляющие класса

Поля (переменные экземпляра)

Это переменные, объявленные внутри класса, которые отражают характеристики объектов:

public class Person {
    // Переменные экземпляра (поля)
    String name;
    int age;
    double height;
    boolean isEmployed;
}
Run Code

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

Методы

Методы определяют поведение объектов:

public class Calculator {
    // Метод без параметров и возврата значения
    public void clear() {
        // Код для сброса настроек калькулятора
    }

    // Метод с параметрами и возвратом значения
    public int add(int a, int b) {
        return a + b;
    }
}
Run Code

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

Конструкторы – это специальные методы, которые инициализируют новые объекты. Они имеют то же имя, что и класс, и не имеют возвращаемого типа:

public class Car {
    String make;
    String model;
    int year;

    // Конструктор по умолчанию
    public Car() {
        make = "Unknown";
        model = "Unknown";
        year = 2023;
    }

    // Параметризованный конструктор
    public Car(String make, String model, int year) {
        this.make = make;
        this.model = model;
        this.year = year;
    }
}
Run Code

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

Модификаторы доступа

Они определяют уровень доступа для классов, методов и полей. Существует 4 разновидности:

  • Публичный (public): предоставляет неограниченный доступ из любого другого класса
  • Частный (private): доступ возможен только в пределах одного класса
  • Защищенный (protected): доступ для классов из того же пакета и всех подклассов
  • По умолчанию (default): доступ возможен только в пределах одного пакета
public class BankAccount {
    private double balance;  // Доступ толкьо в пределах класса BankAccount

    public void deposit(double amount) {  // Неограниченный доступ
        if (amount > 0) {
            balance += amount;
        }
    }

    protected void applyInterest() {  // Доступ для класса из того же пакета и всех подклассов
        balance *= 1.03;
    }
}
Run Code

Создание и инициализация объектов в Java

С помощью слова new мы можем создавать объекты:

Car myCar = new Car("Honda");

Распределение памяти в Java

Когда вы создаете объект:

  1. Ссылка (например, myCar) находится в стеке.
  2. Данные объекта располагаются в куче.
  3. Ссылка указывает на адрес объекта в куче.

Это важно, потому что несколько ссылок могут ссылаться на один и тот же объект:

Car car1 = new Car("Ford");
Car car2 = car1; // Тот же объект
car2.model = "Chevy";
System.out.println(car1.model); // Вывод: Chevy
Run Code

Разница между классами и объектами

Проясним различия между классами и объектами с помощью сопоставительной таблички:

АспектКлассОбъект
Определение“шаблон”Экземпляр класса
СозданиеСоздается единождыИз одного класса можно создать несколько объектов
Распределение памятиЗагружается в память один разПод каждый объект выделена своя память
Формат существованияЛогическая конструкция (существует в коде), своего рода “идея”“Физическая” сущность (существует в памяти во время выполнения программы), то есть материальное воплощение “идеи”
Как объявитьpublic class ClassName { }ClassName objectName = new ClassName();
Когда создаетсяВо время компиляцииВо время выполнения программы
СодержимоеПоля, методы, конструкторыРеальные данные

Вот как в одном классе Car можно создать несколько объектов с разными состояниями:

// Класс ("чертеж")
public class Car {
    String make;
    String model;
    int year;

    public Car(String make, String model, int year) {
        this.make = make;
        this.model = model;
        this.year = year;
    }

    public void displayInfo() {
        System.out.println(year + " " + make + " " + model);
    }
}

// Создание различных объектов одного класса
Car sedan = new Car("Honda", "Accord", 2020);
Car suv = new Car("Toyota", "RAV4", 2021);
Car truck = new Car("Ford", "F-150", 2019);

// У каждого объетка своё состояние
sedan.displayInfo();  // Вывод: 2020 Honda Accord
suv.displayInfo();    // Вывод: 2021 Toyota RAV4
truck.displayInfo();  // Вывод: 2019 Ford F-150
Run Code

Доступ к элементам класса

После создания объектов необходимо знать, как работать с их полями и методами. В Java для этого используется оператор точка (.):

Доступ к полям

Person person = new Person();
person.name = "John";  // Устанавливаем значение поля
System.out.println(person.name);  // Получаем значение поля
Run Code

Вызов методов

Calculator calc = new Calculator();
int result = calc.add(5, 3);  // Вызов метода
System.out.println(result);  // Вывод: 8
Run Code

При работе с приватными полями (как и должно быть при инкапсуляции, то есть при объединении объектов с их свойствами и методами с возможностью ограничения доступа к ним) вам понадобятся методы доступа getter и setter:

public class Student {
    private String name;
    private double gpa;

    // Getter method
    public String getName() {
        return name;
    }

    // Setter method
    public void setName(String name) {
        this.name = name;
    }

    public double getGpa() {
        return gpa;
    }

    public void setGpa(double gpa) {
        if (gpa >= 0 && gpa <= 4.0) {  // Валидация
            this.gpa = gpa;
        } else {
            System.out.println("Invalid GPA value");
        }
    }
}

Student student = new Student();
student.setName("Emma");
student.setGpa(3.8);
System.out.println(student.getName() + ": " + student.getGpa());  // Вывод: Emma: 3.8
Run Code

Конструкторы в классах Java

Конструкторы инициализируют объекты при их создании. Рассмотрим более подробно.

Конструктор по умолчанию

Если вы не определили ни одного конструктора, Java предоставит конструктор по умолчанию без параметров:

public class Book {
    String title;
    String author;

    // Java неявно предоставит:
    // public Book() { }
}

Book book = new Book();  // Использование дефолтного конструктора
Run Code

Однако, как только вы определите какой-либо конструктор, Java больше не будет предоставлять его по умолчанию:

public class Book {
    String title;
    String author;

    public Book(String title) {
        this.title = title;
        this.author = "Unknown";
    }
}

// Book book = new Book();  // Ошибка! Конструктор по умолчанию больше не доступен
Book book = new Book("Java Programming");  // Необходимо использовать определенный пользователем конструктор
Run Code

Constructor overloading

Constructor overloading – это наличие в одном классе нескольких конструкторов с разными параметрами:

public class Book {
    String title;
    String author;
    int pages;

    // Конструктор без параметров
    public Book() {
        title = "Untitled";
        author = "Unknown";
        pages = 0;
    }

    // Конструктор только с названием
    public Book(String title) {
        this.title = title;
        author = "Unknown";
        pages = 0;
    }

    // Конструктор со всеми полями
    public Book(String title, String author, int pages) {
        this.title = title;
        this.author = author;
        this.pages = pages;
    }
}
Run Code

Constructor Chaining

Constructor Chaining – это процесс вызова одного конструктора из другого внутри того же класса или между родительским и дочерним классом. В первом случае можно использовать this(), во втором – super():

public class Book {
    String title;
    String author;
    int pages;

    public Book() {
        this("Untitled", "Unknown", 0);
    }

    public Book(String title) {
        this(title, "Unknown", 0);
    }

    public Book(String title, String author) {
        this(title, author, 0);
    }

    public Book(String title, String author, int pages) {
        this.title = title;
        this.author = author;
        this.pages = pages;
    }
}
Run Code

Этот метод позволяет снизить степень дублирования кода и централизует логику инициализации.

Переменные и методы экземпляра

При работе с классами и объектами в Java важно понимать разницу между членами экземпляра (его переменными/методами, привязанными к объектам) и статическими членами (привязанными к классу):

Переменные экземпляра

У каждого объекта есть своя копия переменных экземпляра:

public class Dog {
    String name;  // Переменная экземпляра уникальна для каждого объекта Dog

    public Dog(String name) {
        this.name = name;
    }
}

Dog dog1 = new Dog("Rex");
Dog dog2 = new Dog("Buddy");

System.out.println(dog1.name);  // Вывод: Rex
System.out.println(dog2.name);  // Вывод: Buddy
Run Code

Изменение переменной экземпляра в одном объекте не влияет на другие объекты:

dog1.name = "Max";
System.out.println(dog1.name);  // Вывод: Max
System.out.println(dog2.name);  // Вывод прежний: Buddy
Run Code

Методы экземпляра

Методы экземпляра работают с конкретными объектами и имеют доступ к их переменным:

public class Counter {
    private int count = 0;

    public void increment() {  // Метод экземпляра
        count++;
    }

    public int getCount() {  // Метод экземпляра
        return count;
    }
}

Counter c1 = new Counter();
Counter c2 = new Counter();

c1.increment();
c1.increment();
c2.increment();

System.out.println(c1.getCount());  // Вывод: 2
System.out.println(c2.getCount());  // Вывод: 1
Run Code

Статические члены vs члены экземпляра

Статические члены принадлежат самому классу, а не объектам:

public class MathUtils {
    // Статическая переменная - переменная, общая для всех объектов
    public static final double PI = 3.14159;

    // Переменная экземпляра уникальна для каждого объекта
    private double result;

    // Статический метод вызывается через класс
    public static double square(double num) {
        return num * num;
    }

    // Метод экземпляра требует объект
    public void storeResult(double value) {
        this.result = value;
    }
}

// Статические члены вызываются через имя класса
double area = MathUtils.square(5) * MathUtils.PI;

// Членам экземпляра требуется объект
MathUtils utils = new MathUtils();
utils.storeResult(area);
Run Code

Классы Java и концепции ООП

Классы и объекты Java составляют основу объектно-ориентированного программирования (ООП). Давайте посмотрим, как классы реализуют ключевые принципы ООП:

Инкапсуляция

Объединение и защита данных:

public class Wallet {
    private double money;

    public void addMoney(double amount) {
        if (amount > 0) money += amount;
    }
}
Run Code

Наследование

Классы могут наследовать свойства других классов:

public class Animal {
    String species;

    Animal(String species) { this.species = species; }
    void eat() { System.out.println("Eating..."); }
}

public class Dog extends Animal {
    Dog() { super("Canine"); }
    @Override
    void eat() { System.out.println("Dog eats kibble"); }
}
Run Code

Полиморфизм

Возможность работать с объектами как с экземплярами родительского класса:

Animal myDog = new Dog();
myDog.eat(); // Вывод: Dog eats kibble
Run Code

Продвинутые методы создания объектов

Хотя new – это самый распространенный способ создания объектов, Java предлагает и другие методы:

Использование рефлексии

API рефлексии в Java позволяет создавать объекты динамически:

try {
    // Получаем объект Class для нужного класса
    Class carClass = Class.forName("com.example.Car");

    // Создаем новый экземпляр с помощью конструктора без параметров
    Object carObject = carClass.newInstance();

    // Для новых версий Java:
    // Object carObject = carClass.getDeclaredConstructor().newInstance();

    // Приводим к нужному типу
    Car car = (Car) carObject;
    car.displayInfo();
} catch (Exception e) {
    e.printStackTrace();
}
Run Code

Это особенно полезно, когда во время компиляции неизвестно, какой именно класс нужно создать.

Клонирование объектов

Вы можете создать копию существующего объекта с помощью метода clone():

public class User implements Cloneable {
    private String name;
    private int age;

    // Конструктор и другие методы...

    @Override
    public User clone() throws CloneNotSupportedException {
        return (User) super.clone();
    }
}

User originalUser = new User("John", 30);
try {
    User clonedUser = originalUser.clone();
    System.out.println(clonedUser.getName());  // John
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
Run Code

Важно: стандартный метод clone() создает поверхностную копию (shallow copy). Для сложных объектов может потребоваться реализация глубокого копирования (deep cloning).

Распространенные ошибки

  1. Забыть инициализировать объекты:
Person person;  // Объект объявлен, но не инициализирован
person.speak();  // NullPointerException!
Run Code
  1. Путать статические члены и члены экземпляра:
public class Counter {
    private int count;
    public static void incrementCount() {
        count++;  // Ошибка! Не удается получить доступ к переменной экземпляра из статического метода
    }
}
Run Code
  1. Не понимать, что в Java используется только pass-by-value (передача по значению), но не pass-by-reference (передача по ссылке)
public void changeCar(Car car) {
    car = new Car("New Car");  // Не влияет на оригинальную ссылку
}
Run Code
  1. Прямой доступ к изменяемому полю:
public class Team {
    public ArrayList players;  // Прямой доступ к изменяемому полю - плохая практика
}
Run Code

Примеры хороших практик

  1. Инкапсуляция данных с помощью приватных полей и публичных геттеров/сеттеров:
private List players;

public List getPlayers() {
    return new ArrayList<>(players);  // Возвращает копию для сохранения инкапсуляции
}
Run Code
  1. Инициализация всех переменных экземпляра:
private String name = "";  // Пустая строка вместо null
private List items = new ArrayList<>();  // Пустой список вместо null
Run Code
  1. Использование осмысленных имен классов и переменных:
// Плохо
public class X {
    private int y;

    public void z() { }
}

// Хорошо
public class Customer {
    private int accountNumber;

    public void processPayment() { }
}
Run Code
  1. Следование принципу единственной ответственности (Single Responsibility Principle): каждый класс должен иметь только одну причину для изменения. Разбивайте сложные классы на более мелкие и специализированные.
  1. По возможности отдавайте предпочтение композиции, а не наследованию:
// Вместо наследования:
class ElectricCar extends Car { }

// Лучше использовать композицию:
class Car {
    private Engine engine;  // Автомобиль содержит двигатель
}
Run Code

Часто задаваемые вопросы о классах и объектах Java

  • Класс vs объект: класс – это чертеж; объект – это построенная по нему вещь.
  • Возможен ли класс без объектов? Да, например, служебные классы по типу Math.
  • А если нет конструктора? Java добавляет его по умолчанию.
  • Возможно реализовать несколько конструкторов? Да, с помощью constructor overloading.
  • Что по модификаторам доступа? Используйте private для полей, public для API.
  • Как запретить создание экземпляров? Используйте приватный конструктор.

Заключение

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

Главное – практика, практика и ещё раз практика!

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

Еще полезные материалы по данной теме: «Создание объектов: детальное изучение» 

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

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

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