Конструкторы
Конструктор — это специальный метод, который вызывается при создании объекта. Он гарантирует, что объект рождается в правильном состоянии. Без конструктора объект — это просто неинициализированный набор полей.
Зачем нужны конструкторы
Представьте: вы создаёте объект класса Dog и забываете задать ему имя. Без конструктора объект существует, но name = null, age = 0, breed = null. Это бесполезный объект — он ничего не знает о себе.
Конструктор решает эту проблему: он автоматически вызывается при new и задаёт начальные значения. Это как «инструкция по первичной настройке» нового устройства. Вы покупаете телефон — он включается, проходит настройку языка, времени, аккаунта. Конструктор делает то же самое для объекта.
Без конструктора пришлось бы после каждого new вручную вызывать десятки сеттеров. Конструктор берёт это на себя.
Конструктор — это единственное место, где вы ГАРАНТИРОВАННО можете настроить объект. Сеттеры могут быть вызваны позже, а могут и нет. Конструктор вызывается ВСЕГДА при new.
Что такое конструктор
Конструктор — это метод, который автоматически вызывается при создании объекта с помощью new. У него три ключевые особенности, отличающие его от обычного метода:
- Нет возвращаемого типа — даже не void. Конструктор не «возвращает» объект — он его настраивает.
- Имя конструктора совпадает с именем класса — если класс называется Book, конструктор тоже Book.
- Вызывается автоматически при
new— вы не можете вызвать конструктор вручную (ну, почти).
public class Book {
String title;
String author;
Book() {
System.out.println("Создаётся новая книга!");
}
}
Book b = new Book(); // Выведет: "Создаётся новая книга!"
Обратите внимание: конструктор вызывается сам, без вашего участия. Написали new Book() — конструктор выполнился. Вы даже не можете написать Book b = new Book(); b.Book(); — это не обычный метод.
Вот ещё пример — класс Person:
public class Person {
String name;
int age;
Person() {
System.out.println("Родился новый человек!");
name = "Неизвестный";
age = 0;
}
}
Person p = new Person(); // "Родился новый человек!"
System.out.println(p.name); // "Неизвестный"
System.out.println(p.age); // 0
Без конструктора name был бы null, age — 0. С конструктором мы задали осмысленное значение по умолчанию.
Конструктор по умолчанию
Если вы НЕ определяете ни одного конструктора в классе, Java автоматически создаёт «конструктор по умолчанию» — пустой, без параметров:
// Вы написали:
public class Dog {
String name;
int age;
}
// Java тайно создала:
public class Dog {
String name;
int age;
Dog() {
// Пустой конструктор — ничего не делает
}
}
// Поэтому это работает:
Dog d = new Dog(); // OK — конструктор по умолчанию
Конструктор по умолчанию ничего не делает — просто создаёт объект с полями, равными значениям по умолчанию (null, 0, false).
ВАЖНО: как только вы определяете ХОТЯ БЫ ОДИН конструктор, конструктор по умолчанию ИСЧЕЗАЕТ. Это самая частая ошибка новичков! Если вам нужен и конструктор по умолчанию, и параметризованный — напишите оба явно.
public class User {
String name;
User(String name) {
this.name = name;
}
}
// User u1 = new User(); // Ошибка! Пустой конструктор удалён
User u2 = new User("Иван"); // Работает — параметризованный конструктор
// Правильно — написать оба явно:
public class User {
String name;
User() {
this.name = "Гость";
}
User(String name) {
this.name = name;
}
}
User u3 = new User(); // OK — "Гость"
User u4 = new User("Иван"); // OK — "Иван"
Параметризованные конструкторы
Самый распространённый тип конструктора — принимает параметры и инициализирует поля. Это позволяет задать все нужные данные сразу, при создании объекта:
public class Book {
String title;
String author;
int year;
int pages;
Book(String title, String author, int year, int pages) {
this.title = title;
this.author = author;
this.year = year;
this.pages = pages;
System.out.println("Создана книга: \"" + title + "\"");
}
}
Book b = new Book("Война и мир", "Толстой", 1869, 1225);
System.out.println(b.title); // "Война и мир"
System.out.println(b.author); // "Толстой"
System.out.println(b.year); // 1869
System.out.println(b.pages); // 1225
Параметризованный конструктор сразу создаёт объект в правильном состоянии. Не нужно потом вызывать сеттеры — всё задано при создании.
Вот ещё пример — класс Точка на координатной плоскости:
public class Point {
double x;
double y;
Point(double x, double y) {
this.x = x;
this.y = y;
}
double distanceTo(Point other) {
double dx = this.x - other.x;
double dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
void display() {
System.out.printf("(%.1f, %.1f)%n", x, y);
}
}
Point p1 = new Point(0, 0);
Point p2 = new Point(3, 4);
p1.display(); // (0.0, 0.0)
p2.display(); // (3.0, 4.0)
System.out.println("Расстояние: " + p1.distanceTo(p2)); // 5.0
Обратите внимание: конструктор Point(x, y) принимает два параметра. Без него мы не смогли бы создать точку — пришлось бы вручную задавать x и y через сеттеры.
Перегрузка конструкторов
Перегрузка (overloading) — это когда класс имеет несколько конструкторов с РАЗНЫМИ списками параметров. Это позволяет создавать объекты разными способами.
Аналогия: в restaurantе можно заказать кофе: «крупно» (без параметров), «крупно с молоком» (один параметр), «крупно, молоко, два сахара» (три параметра). Один и тот же продукт, разные варианты.
public class User {
String name;
String email;
int age;
User() {
this("Гость", "guest@mail.com", 0);
}
User(String name) {
this(name, name.toLowerCase() + "@mail.com", 18);
}
User(String name, String email) {
this(name, email, 18);
}
User(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
void display() {
System.out.printf("Имя: %s, Email: %s, Возраст: %d%n", name, email, age);
}
}
Теперь можно создавать объекты разными способами:
User u1 = new User();
u1.display(); // Имя: Гость, Email: guest@mail.com, Возраст: 0
User u2 = new User("Иван");
u2.display(); // Имя: Иван, Email: ivan@mail.com, Возраст: 18
User u3 = new User("Мария", "maria@mail.com");
u3.display(); // Имя: Мария, Email: maria@mail.com, Возраст: 18
User u4 = new User("Пётр", "petr@mail.com", 25);
u4.display(); // Имя: Пётр, Email: petr@mail.com, Возраст: 25
Java сама выбирает нужный конструктор по количеству и типам параметров. Это называется разрешение перегрузки. Когда вы пишете new User("Иван"), Java ищет конструктор с одним параметром String — и находит.
Перегрузка работает для методов И конструкторов. Главное правило: сигнатуры (имя + список параметров) должны быть РАЗНЫМИ. Два конструктора с одинаковыми параметрами — ошибка компиляции.
Вызов this(): делегирование конструкторов
this() — это вызов другого конструктора того же класса. Он должен быть ПЕРВОЙ строкой в конструкторе. Это позволяет избежать дублирования кода — вся логика инициализации в одном конструкторе, остальные просто перенаправляют.
public class Point {
int x;
int y;
Point() {
this(0, 0);
}
Point(int x) {
this(x, 0);
}
Point(int x, int y) {
this.x = x;
this.y = y;
}
void display() {
System.out.println("Точка: (" + x + ", " + y + ")");
}
}
Point p1 = new Point(); // (0, 0)
Point p2 = new Point(5); // (5, 0)
Point p3 = new Point(3, 4); // (3, 4)
Конструктор Point() делегирует работу конструктору Point(int, int). Конструктор Point(int) тоже делегирует. Вся логика инициализации — в одном конструкторе. Остальные просто перенаправляют.
Почему это важно? Потому что без делегирования пришлось бы копировать один и тот же код в каждый конструктор:
// ПЛОХО — дублирование кода:
public class Point {
int x;
int y;
Point() {
x = 0;
y = 0;
}
Point(int x) {
this.x = x;
y = 0;
}
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
// ХОРОШо — делегирование:
public class Point {
int x;
int y;
Point() {
this(0, 0); // делегирует
}
Point(int x) {
this(x, 0); // делегирует
}
Point(int x, int y) {
this.x = x; // вся логика здесь
this.y = y;
}
}
this() должен быть ПЕРВОЙ строкой конструктора. Нельзя писать ничего до него. Также нельзя использовать this() и this. (обращение к полям) одновременно в одном конструкторе — это ошибка компиляции.
Валидация в конструкторах
Конструкторы — идеальное место для проверки входных данных. Если данные некорректные — объект не создаётся. Это гарантирует, что каждый объект ВСЕГДА в правильном состоянии.
Аналогия: на заводе деталь проходит контроль качества. Бракованная деталь не отправляется на сборку. Конструктор — это контроль качества для объектов.
public class Person {
private String name;
private int age;
public Person(String name, int age) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("Имя не может быть пустым");
}
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Некорректный возраст: " + age);
}
this.name = name.trim();
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
Person p1 = new Person("Иван", 25); // OK
Person p2 = new Person("", 25); // IllegalArgumentException!
Person p3 = new Person("Мария", -5); // IllegalArgumentException!
Ещё пример — валидация email:
public class Email {
private String address;
public Email(String address) {
if (address == null || !address.contains("@") || !address.contains(".")) {
throw new IllegalArgumentException("Некорректный email: " + address);
}
this.address = address.toLowerCase();
}
public String getAddress() {
return address;
}
}
А ещё — валидация возраста сотрудника:
public class Employee {
private String name;
private int age;
private double salary;
public Employee(String name, int age, double salary) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("Имя обязательно");
}
if (age < 18 || age > 65) {
throw new IllegalArgumentException("Возраст сотрудника: 18-65");
}
if (salary < 0) {
throw new IllegalArgumentException("Зарплата не может быть отрицательной");
}
this.name = name.trim();
this.age = age;
this.salary = salary;
}
public String getName() { return name; }
public int getAge() { return age; }
public double getSalary() { return salary; }
}
Employee e1 = new Employee("Иванов", 30, 100000); // OK
Employee e2 = new Employee("Петров", 15, 50000); // Ошибка! Возраст < 18
Employee e3 = new Employee("Сидоров", 40, -10000); // Ошибка! Зарплата < 0
Полный пример: класс BankAccount
Давайте создадим класс Банковский счёт, используя все концепции этого урока:
public class BankAccount {
private String accountNumber;
private String ownerName;
private double balance;
private boolean isActive;
public BankAccount(String accountNumber, String ownerName, double initialBalance) {
if (accountNumber == null || accountNumber.isEmpty()) {
throw new IllegalArgumentException("Номер счёта обязателен");
}
if (ownerName == null || ownerName.trim().isEmpty()) {
throw new IllegalArgumentException("Имя владельца обязательно");
}
if (initialBalance < 0) {
throw new IllegalArgumentException("Начальный баланс не может быть отрицательным");
}
this.accountNumber = accountNumber;
this.ownerName = ownerName.trim();
this.balance = initialBalance;
this.isActive = true;
}
public BankAccount(String accountNumber, String ownerName) {
this(accountNumber, ownerName, 0);
}
public void deposit(double amount) {
if (!isActive) {
System.out.println("Счёт заблокирован!");
return;
}
if (amount <= 0) {
System.out.println("Сумма должна быть положительной");
return;
}
balance += amount;
System.out.printf("Зачислено %.2f руб. Баланс: %.2f руб.%n", amount, balance);
}
public boolean withdraw(double amount) {
if (!isActive) {
System.out.println("Счёт заблокирован!");
return false;
}
if (amount <= 0) {
System.out.println("Сумма должна быть положительной");
return false;
}
if (amount > balance) {
System.out.printf("Недостаточно средств. Баланс: %.2f руб.%n", balance);
return false;
}
balance -= amount;
System.out.printf("Списано %.2f руб. Баланс: %.2f руб.%n", amount, balance);
return true;
}
public void close() {
isActive = false;
System.out.println("Счёт " + accountNumber + " закрыт");
}
public String getAccountNumber() { return accountNumber; }
public String getOwnerName() { return ownerName; }
public double getBalance() { return balance; }
public boolean isActive() { return isActive; }
public void displayInfo() {
System.out.println("Счёт: " + accountNumber);
System.out.println("Владелец: " + ownerName);
System.out.printf("Баланс: %.2f руб.%n", balance);
System.out.println("Статус: " + (isActive ? "активен" : "закрыт"));
}
}
BankAccount acc1 = new BankAccount("123456", "Иванов И.И.", 10000);
acc1.deposit(5000);
acc1.withdraw(3000);
acc1.displayInfo();
System.out.println("---");
BankAccount acc2 = new BankAccount("789012", "Петров П.П.");
acc2.deposit(1000);
acc2.displayInfo();
Конструкторы и инкапсуляция
Конструкторы работают в паре с приватными полями и публичными методами. Это основа инкапсуляции:
public class Temperature {
private double celsius;
public Temperature(double celsius) {
setCelsius(celsius);
}
public double getCelsius() {
return celsius;
}
public void setCelsius(double celsius) {
if (celsius < -273.15) {
throw new IllegalArgumentException("Температура ниже абсолютного нуля");
}
this.celsius = celsius;
}
public double getFahrenheit() {
return celsius * 9.0 / 5.0 + 32;
}
public void setFahrenheit(double fahrenheit) {
setCelsius((fahrenheit - 32) * 5.0 / 9.0);
}
}
Temperature t = new Temperature(37.5);
t.setFahrenheit(212);
System.out.println(t.getCelsius()); // 100.0
Обратите внимание: конструктор вызывает сеттер setCelsius(), который проверяет данные. Это гарантирует, что объект всегда в правильном состоянии — даже если вызвать сеттер позже.
Ещё примеры перегрузки
Перегрузка конструкторов — мощный инструмент. Вот ещё примеры:
public class Rectangle {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public Rectangle(double side) {
this(side, side); // Квадрат
}
public Rectangle() {
this(1.0, 1.0); // Единичный квадрат
}
public double getArea() { return width * height; }
public double getPerimeter() { return 2 * (width + height); }
public void display() {
System.out.printf("Прямоугольник: %.1f x %.1f%n", width, height);
}
}
Rectangle r1 = new Rectangle(5, 3); // 5 x 3
Rectangle r2 = new Rectangle(4); // 4 x 4 (квадрат)
Rectangle r3 = new Rectangle(); // 1 x 1
Ещё пример — класс Circle:
public class Circle {
private double radius;
public Circle(double radius) {
if (radius <= 0) {
throw new IllegalArgumentException("Радиус должен быть положительным");
}
this.radius = radius;
}
public Circle() {
this(1.0);
}
public double getArea() { return Math.PI * radius * radius; }
public double getCircumference() { return 2 * Math.PI * radius; }
public double getDiameter() { return 2 * radius; }
public void display() {
System.out.printf("Круг: r=%.1f, площадь=%.1f, длина окружности=%.1f%n",
radius, getArea(), getCircumference());
}
}
Circle c1 = new Circle(5);
Circle c2 = new Circle();
c1.display(); // r=5.0, площадь=78.5, длина окружности=31.4
c2.display(); // r=1.0, площадь=3.1, длина окружности=6.3
Типичные ошибки
public class Mistakes {
private String name;
// Ошибка 1: объявление возвращаемого типа
void Mistakes(String name) { // Это МЕТОД, не конструктор!
this.name = name;
}
// Правильно:
Mistakes(String name) {
this.name = name;
}
// Ошибка 2: this() не первая строка
Mistakes() {
System.out.println("Привет");
this("Гость"); // Ошибка! this() должен быть первым
}
// Ошибка 3: вызов this() и this. в одном конструкторе
Mistakes(String name, int age) {
this(); // ОК
this.name = name; // Ошибка! Нельзя и this(), и this.
}
// Ошибка 4: забыли вызвать this() или задать поля
Mistakes(int x) {
// name остался null!
}
}
Итоги урока
- Конструктор — специальный метод, нет возвращаемого типа, имя = имя класса.
- Вызывается автоматически при
new. - Конструктор по умолчанию существует только если нет других конструкторов.
- Параметризованные конструкторы инициализируют поля сразу.
- Перегрузка: несколько конструкторов с разными параметрами.
this()вызывает другой конструктор (должен быть первой строкой).- Валидация в конструкторах гарантирует правильное состояние объекта.
- Делегирование через
this()избегает дублирования кода.
Следующий урок: Модификаторы доступа — public, private, protected и инкапсуляция.
Тест: Конструкторы
10 вопросов