Создание объектов и конструкторы
Разбираем основные моменты, связанные с написанием собственных конструкторов классов в Java.
Василий Порядин
Программист, преподаватель Skillbox. Пишет про Java.
Процесс создания объектов — один из важных аспектов программирования на Java. Под созданием подразумевают как минимум две основные операции: создание нового объекта класса и инициализацию полей объекта.
В этой статье мы рассмотрим:
- что такое конструктор класса Java;
- как работают параметризованные конструкторы;
- как происходит инициализация полей объекта.
Что такое конструкторы классов
Конструктор — это специальный метод, который имеет имя, совпадающее с именем класса, и вызывается при создании экземпляра объекта совместно с оператором new. Результатом работы этого метода всегда является экземпляр класса.
Следовательно, конструктор класса Cat называется Cat(). В результате работы этого конструктора будет создан новый объект класса Cat. Цель конструктора — правильно инициализировать объект перед его использованием.
Самый простой способ создать объект — это строка вида:
Cat murka = new Cat();
Рассмотрим порядок создания объекта. В этой строке выполняется три действия:
- Во-первых, задаётся переменная класса Cat под именем murka. Эта переменная ещё не определяет объект, она просто даёт возможность ссылаться на него.
- Во-вторых, создаётся физическая копия объекта, а ссылка на него присваивается переменной murka. Это делается с помощью оператора new. Оператор new динамически — во время выполнения программы — выделяет память для объекта и возвращает ссылку на него, которая представляет собой адрес области памяти.
- В-третьих, ссылка на объект сохраняется в переменной. За это отвечает оператор =.
Приведённый выше код можно разбить на две строки:
Cat murka; // объявление ссылки на объект murka = new Cat(); // выделение памяти для объекта типа Cat и присвоение значения ссылке
Выполнять отдельно создание объекта не имеет смысла, так как в таком случае мы не сможем с ним работать — ведь ссылка на него нигде не сохранится.
new Cat(); // такая операция не позволит дальше работать с объектом
В первой строке кода переменная murka объявляется как ссылка на объект типа Cat. Здесь важно понять, что объектная переменная фактически не содержит никакого объекта. Значение любой объектной переменной в Java представляет собой ссылку на объект, размещённый в памяти. В данный момент переменная murka пока ещё не ссылается на объект (содержит пустое значение null).
Во второй строке кода создаётся новый объект типа Cat, а ссылка на него присваивается переменной murka. С этого момента переменная murka оказывается ассоциированной с объектом. Чтобы работать с объектами, нужно сначала создать их и задать их исходное состояние. Затем к этим объектам можно применять методы.
Теперь взглянем на код класса Cat (без методов):
public class Cat < private double originWeight; private double weight; private double minWeight; private double maxWeight; public Cat() < weight = 1500.0 + 3000.0 * Math.random(); originWeight = weight; minWeight = 1000.0; maxWeight = 9000.0; > >
Видно, что все объявленные в классе переменные в результате работы конструктора получили значение — и объект готов к использованию. Мы можем вызывать различные методы класса, просматривать значение переменных — никаких ошибок не появится.
Важно. Конструкторы имеются у всех классов, независимо от того, определите вы их или нет, поскольку Java автоматически предоставляет конструктор, используемый по умолчанию (без параметров) и инициализирующий все переменные экземпляра их значениями, заданными по умолчанию.
Для справки. Для числовых типов данных значением по умолчанию является нулевое, для типа boolean — логическое значение false, а для ссылочных типов — пустое значение null.
Но как только вы определите свой собственный конструктор, конструктор по умолчанию предоставляться не будет. Следовательно, если мы удалим конструктор из класса Cat и попытаемся создать объект через new Cat(), то объект будет создан, но все переменные в классе получат значения по умолчанию.
Сразу отметим, что полагаться на действия по умолчанию не следует. Если поля инициализируются неявно, программа становится менее понятной.
Как работают параметризованные конструкторы
В предыдущем примере использовался конструктор без параметров, который также называется конструктором по умолчанию. В некоторых случаях этого оказывается достаточно, но зачастую конструктор должен иметь один или несколько параметров. Добавление параметров в конструктор происходит точно так же, как и добавление параметров в метод, — для этого достаточно объявить их в скобках после имени конструктора.
Важно. Класс может иметь несколько различных конструкторов. Они (кстати, как и методы) отличаются между собой количеством, типом и порядком следования параметров. Если в классе несколько конструкторов с разным набором параметров, это называется перегрузкой конструктора.
Например, конструкторы ниже являются разными и вполне могут существовать в одном классе и иметь разную логику:
public Cat(String name, int weight)< //код. > public Cat(String name, int weight, double tailLength)< //код. >
Заметьте, что конструкторы ниже для компилятора одинаковы и вызовут ошибку при запуске программы (тип, количество и порядок следования параметров идентичны):
public Cat(String newName, int weight)< //код. > public Cat(String newName, int initialOriginWeight)< //код. >
Разберём пример применения параметризованного конструктора класса Cat с добавленным полем имени:
public class Cat < private double originWeight; private double weight; private double minWeight; private double maxWeight; private String name; public Cat() < name = "Безымянная"; weight = 1500.0 + 3000.0 * Math.random(); originWeight = weight; minWeight = 1000.0; maxWeight = 9000.0; > public Cat(String name) < this.name = name; weight = 1500.0 + 3000.0 * Math.random(); originWeight = weight; minWeight = 1000.0; maxWeight = 9000.0; > >
Как видно, в этих конструкторах очень много кода повторяется, что в целом не очень удобно и повышает вероятность ошибки: ведь если нам потребуется поменять один конструктор, необходимо будет не забыть внести изменения в другой.
Для этого применяется вызов конструктора из конструктора с использованием ключевого слова this, которое означает ссылку на текущий объект. Обратиться к конструктору из другого конструктора можно через вызов this() — так будет выполнен конструктор без параметров. Если же нужен конструктор с параметрами, их указывают в скобках.
Применять ключевое слово this для вызова другого конструктора очень удобно — нужно лишь один раз написать общий код для конструирования объекта.
Важно. Вызов другого конструктора всегда должен стоять первой строкой в конструкторе.
Вот пример оптимизации кода первого из конструкторов:
public Cat() < this("Безымянная"); //конструктор по умолчанию сокращён до одной строки >
Обратите внимание. Несколько слов насчёт ключевого слова this. Синтаксис языка Java не запрещает использовать имена параметров или локальных переменных, совпадающие с именами переменных экземпляра (класса). В таком случае говорят, что локальная переменная или параметр скрывает переменную экземпляра. При этом доступ к скрытой переменной экземпляра обеспечивается с помощью ключевого слова this.
Приведённый ниже пример конструктора класса Cat показывает, каким образом лучше выполнять присваивание переданных в конструктор параметров переменным класса:
public Cat(String name) < this.name = name; //значение параметра присваивается переменной класса // остальной код конструктора. >
При выборе имён параметров рекомендую в первую очередь ориентироваться на читаемость кода. Чтобы, взглянув на этот код спустя некоторое время, вы сразу могли понять, что здесь происходит.
Но в целом читаемость кода — это отдельная и довольно обширная тема.
Инициализация полей объекта
Основная задача конструкторов — подготовка объекта к работе с ним и установка значений для полей (переменных) объекта. Но есть и другие варианты установки значения для полей. Это явная инициализация и так называемые блоки инициализации.
Явная инициализация — это возможность присвоить полю соответствующее значение указанным ниже образом:
public class Cat < . private double minWeight = 1000.0; private double maxWeight = 9000.0;
Здесь присваивание выполняется до вызова конструктора. Такой подход оказывается полезным, когда требуется, чтобы поле имело конкретное значение — независимо от вызова конструктора класса.
Блоки инициализации выглядят так:
public class Cat < private static int count; private int id; .
Такой блок выполняется каждый раз, когда создаётся объект данного класса. В этом примере начальное значение поля id задаётся в блоке инициализации объекта. Причём неважно, какой именно конструктор используется для создания экземпляра класса. Первым выполняется блок инициализации, а вслед за ним — тело конструктора.
Блоки инициализации могут быть и статическими. Перед ними пишется ключевое слово static.
static
В таком случае блок инициализации будет выполнен при первом обращении к этому классу. Попробуйте выполнить вот такой код:
public class Main < static < System.out.println("Static"); > public static void main(String[] args) < System.out.println("Test"); > >
Что в итоге
При таком многообразии способов инициализации полей класса трудно отследить все возможные пути создания объекта. Поэтому рассмотрим подробнее действия, которые происходят при вызове конструктора:
1. Если в первой строке кода одного конструктора вызывается второй конструктор, то второй конструктор выполняется с предоставляемыми аргументами.
- все поля инициализируются значениями, предусмотренными по умолчанию (0, false или null);
- инициализируются все поля и блоки инициализации в порядке их следования в объявлении класса.
3. Выполняется тело конструктора.
Естественно, код, отвечающий за инициализацию полей, нужно организовать так, чтобы в нём можно было легко разобраться. Например, было бы странным, если бы вызов конструкторов класса зависел от порядка объявления полей. Такой подход чреват ошибками.
Инициализировать статическое поле следует, задавая его начальное значение или используя статический блок инициализации — если для инициализации статического поля требуется сложный код.
На заметку. Важный момент, о котором часто забывают при написании собственных конструкторов, — в конструкторах не должно содержаться никакой бизнес-логики. Их задача — корректное создание объектов и подготовка их к дальнейшему использованию. Вся логика должна находиться в соответствующих методах.
Для изучения — пример, который содержит все элементы, описанные в статье. В нём есть:
- применение конструктора без аргументов;
- перегрузка конструкторов;
- вызов одного конструктора из другого;
- инициализация полей явным способом;
- инициализация полей с использованием блока инициализации.
public class Cat < private static int count = 0; static < //статический блок инициализации count = 1; > private double maxWeight = 9000.0; //явная инициализация private double minWeight = 1000.0; private double originWeight; private double weight; private String name; private int id; < //блок инициализации //count к этому моменту получит значение 1 count++; > //перегрузка конструкторов public Cat() < //вызов конструктора с именем, в качестве параметра передаётся имя по умолчанию this("Безымянная"); > public Cat(String newName) < //вызов конструктора с именем и весом, вес генерируется случайным образом this(newName, 1500.0 + 3000.0 * Math.random()); > public Cat(String newName, double initialWeight) < name = newName; weight = initialWeight; >>
Мы рассмотрели все возможные пути создания объектов и инициализации их полей в Java. Теперь вы сможете создавать свои объекты любым из представленных способов, а также осознанно читать в коде порядок создания объектов других классов.
Конструкторы в Java
Конструкторы — это специальные методы, которые вызывается при создании объекта . Они «конструируют» новый объект определенного класса.
Шаг за шагом
Итак, чтобы объяснить нагляднее, представим, как работает программа.
1. Вы создаете основное «тело» программы, прописывая метод main:
public class Test < public static void main ( String [ ] args ) <
2. Допустим, Вам нужен объект класса Cat. Класс Cat у вас уже есть, и выглядит он так:
private String name ;
private String color ;
public String getName ( ) <
return name ;
public void setName ( String a ) <
public String getColor ( ) <
return color ;
public void setColor ( String color ) <
this . color = color ;
Вы пишете строку, которая должна создать объект класса Cat:
public class Test < public static void main ( String [ ] args ) < Cat cat1 = new Cat ( ) ;
3. В тот момент, когда программа приступает к созданию объекта cat1, она идет в class Cat:
Тут-то и появляется необходимость в конструкторах. Ведь в первую очередь Java ищет именно конструкторы, которые укажут, как именно создавать объект.
Но в нашем классе есть только геттеры и сеттеры — никаких конструкторов! Что же делать? Теперь объект не создастся?
Создастся, конечно. А все потому, что по-настоящему конструктор все равно присутствует — просто он явно не указан. Теперь, давайте посмотрим как создавать конструкторы явно, и какими они вообще бывают.
Явные и неявные конструкторы
Существуют два вида конструкторов — явные и неявные. Вы уже знаете, что, даже если ничего не прописать в коде класса, Вы все равно сможете «сконструировать» объект этого класса. Но, если все и так работает, зачем их вообще писать? Какие преимущества это дает?
Преимущество 1. Контроль над вводом данных.
Сначала, дайте посмотрим на изображение. Какие отличия Вы видите?
Если Вы заметили, что у всех трех классов разное количество параметров в конструкторе — Вы были правы:
Явно прописывая конструктор, Вы получаете возможность регулировать, какие параметры и в каком количестве нужно задать для создания объекта определенного класса.
Код№1 — класс Cat — скорее всего, был создан с использованием неявного конструктора. Он не просит никаких параметров.
Код№2 — класс Scanner — уже использует явно описанный конструктор. Он требует один параметр — и без него создать объект невозможно.
Код№3 — класс Dog — тоже использует явно описанный конструктор. Но тут, как мы видим, требуется уже три параметра — имя («Шарик»), порода («мопс») и возраст собаки (2 года).
Преимущество 2. Меньше строчек кода.
Вы заметили, как конструктор уменьшает количество строк в коде? Сравните:
Как работает конструктор в Java
Узнайте, как работают конструкторы в Java, их роль в создании объектов и основы использования перегруженных конструкторов для гибкости!
Алексей Кодов
Автор статьи
9 июня 2023 в 16:31
Конструкторы в Java играют важную роль в процессе создания объектов. Они используются для инициализации состояния объекта при его создании. В этой статье мы рассмотрим, как работают конструкторы в Java, и разберемся с основными принципами их использования.
Что такое конструктор
Конструктор — это специальный метод в классе, который вызывается при создании нового объекта этого класса. Он обычно имеет тот же имя, что и класс, и не возвращает никакого значения (даже void ). Задача конструктора — инициализировать поля объекта значениями, которые передаются в качестве параметров.
Пример конструктора для класса Person :
class Person < String name; int age; // Конструктор Person(String name, int age) < this.name = name; this.age = age; >>
В этом примере конструктор принимает два параметра: name и age , и инициализирует соответствующие поля объекта их значениями.
Создание объектов с использованием конструкторов
Для создания нового объекта с использованием конструктора нужно использовать ключевое слово new и вызвать конструктор с передачей ему необходимых параметров.
Пример создания объекта класса Person :
Person person = new Person("John", 30);
В этом примере мы создаем новый объект person с именем «John» и возрастом 30 лет, используя конструктор класса Person .
Java-разработчик: новая работа через 11 месяцев
Получится, даже если у вас нет опыта в IT
Перегрузка конструкторов
В Java можно создавать несколько конструкторов с разными параметрами для одного класса. Это называется перегрузкой конструкторов (constructor overloading). Перегруженные конструкторы позволяют инициализировать объекты разными способами, в зависимости от переданных параметров.
Пример перегруженных конструкторов для класса Person :
class Person < String name; int age; // Конструктор с двумя параметрами Person(String name, int age) < this.name = name; this.age = age; >// Конструктор с одним параметром Person(String name) < this.name = name; this.age = 0; // значение по умолчанию >>
Теперь мы можем создавать объекты класса Person с использованием разных конструкторов:
Person person1 = new Person("John", 30); Person person2 = new Person("Alice");
В этом примере person1 будет создан с именем «John» и возрастом 30 лет, а person2 — с именем «Alice» и возрастом 0 (значение по умолчанию).
Заключение
Конструкторы в Java являются важной частью процесса создания объектов и инициализации их состояния. Они позволяют контролировать, какие значения присваиваются полям объекта, и обеспечивают гибкость при создании объектов с использованием разных параметров через перегрузку конструкторов. Надеюсь, эта статья помогла вам разобраться с основами работы конструкторов в Java!
Конструкторы в Java: Правила и особенности
Java
Автор Михаил Миронов На чтение 3 мин Обновлено 09.07.2023
Как известно, объект является экземпляром определенного класса. Для его создания используется ключевое слово new, например:
Person student = new Person(“Mike”)
Этим кодом создается новый объект Person и указывается его имя – Mike. Строка «Mike» передается аргументом соответствующему конструктору Person:
Person(String name)
Этот конструктор позволяет указать имя человека при создании объекта.
Конструктор всегда вызывается, когда создается новый экземпляр класса. Другими словами, конструкторы используются для инициализации состояния объекта при создании объекта. Конструкторы выглядят как обычные методы, но это далеко не так:
- Конструкторы имеют то же имя, что и имя класса.
- Конструкторы, как и методы, имеют список принимаемых параметров, но не имеют возвращаемый тип (даже void).
Ниже представлен набор из 7 основных правил работы с конструкторами, позволяющие полностью разобраться в их работе.
1. Конструкторы можно перегружать:
Это означает, что класс может иметь множество различных конструкторов, если их списки параметров различны. Например:
class Rectangle < int width; int height; Rectangle() < width = 1; height = 1; >Rectangle(int width) < this. width = width; this. height = width; >Rectangle(int width, int height) < this. width = width; this. height = height; >>
Имея три различных конструктора Rectangle можно создавать новый объект тремя различными способами:
Rectangle rect1 = new Rectangle(); Rectangle rect2 = new Rectangle(10); Rectangle rect3 = new Rectangle(10,20);
2. Конструктор по умолчанию:
Объявление конструкторов вовсе не обязательно. В случае если не определен ни один из конструкторов, то компилятор Java автоматически генерирует конструктор по умолчанию, который пуст и не имеет параметров.
Например, если мы напишем класс Rectangle следующим образом:
Class Rectangle < int widh, height; int area() < >int perimeter() < >>
То компилятор автоматически вставляет конструктор по умолчанию:
ectangle()
3. Компилятор не будет генерировать конструктор по умолчанию, если в классе уже есть конструктор:
Рассмотрим следующий пример:
class Rectangle < int width; int height; Rectangle(int width) < this. width = width; this. height = width; >Rectangle(int width, int height) < this. width = width; this. height = height; >>
При попытке создать новый объект:
Rectangle rect1 = new Rectangle();
Компилятор выдаст ошибку, потому что не может найти конструктор без аргументов.
4. Конструкторы не наследуются:
В отличие от методов, конструкторы не наследуются. Пример:
class Rectangle < Rectangle(int width, int height) < >> class Square extends Rectangle
Нельзя сделать что-то вроде этого: Square box = new Square(10, 10);
5. Конструкторы могут быть приватными!
Можно сделать конструктор приватным, чтобы не дать внешнему коду создать новый экземпляр класса. В чем же может быть преимущество приватного конструктора?
В шаблоне проектирования, называемом Singleton, приватный конструктор используется, чтобы гарантировать, что всегда существует только один экземпляр класса. Класс Singleton предоставляет статический метод для получения этого уникального экземпляра.
6. Конструктор по умолчанию имеет тот же модификатор доступа, что и класс:
При написании следующего класса: public class Person
Компилятор при вставке конструктора по умолчанию, укажет и необходимый модификатор доступа: public Preson();
7. Первой строкой любого конструктора должен вызываться либо перегруженный конструктор того же класса или конструктор его суперкласса:
Если это не так, то компилятор автоматически допишет вызов конструктора суперкласса без аргументов super(); Это может привести к ошибке, поскольку такого конструктора может не быть в суперклассе. Пример:
class Parent < Parent(int number) < >> class Child extends Parent < Child() < >>
Приведет к ошибке, потому что компилятор вставляет вызов super () в конструкторе класса Child:
Child() < super();// дописал компилятор >
Однако компилятор не будет вставлять конструктор по умолчанию для класса Parent, т.к. уже есть другие конструкторы.