В классе можно объявить несколько конструкторов с одинаковым количеством аргументов

Обновлено: 03.05.2024

Java-конструктор – это специальный метод, который вызывается при создании экземпляра объекта. Другими словами, когда вы используете новое ключевое слово. Цель конструктора – инициализировать вновь созданный объект перед его использованием.

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

Здесь создается новый объект MyClass и вызывается конструктор без аргументов MyClass.

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

Объявление конструктора Java

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

Конструктор – это часть:

  • Первая часть объявления конструктора – это модификатор доступа. Он имеет те же значения, что и для методов и полей. Определяют, какие классы могут получить доступ (вызвать) конструктор.
  • Вторая часть – это имя класса, к которому принадлежит конструктор. Использование сигнализирует компилятору, что это конструктор. Также обратите внимание, что конструктор не имеет возвращаемого типа, как другие методы Java.
  • Третья часть – это список параметров, которые может принимать конструктор. Объявляются в круглых скобках () после части имени класса конструктора. В приведенном выше примере параметры не объявляются.
  • Четвертая часть – это тело конструктора. Определяется внутри фигурных скобок <> после списка параметров. В приведенном выше примере нет операций внутри тела конструктора. Говорят, что это «пустой» конструктор.

Перегрузка конструктора

Класс может иметь несколько конструкторов, если их сигнатура (параметры, которые они принимают) не совпадают. Когда класс содержит несколько конструкторов, мы говорим, что конструктор перегружен (поставляется в нескольких версиях):

Класс выше содержит два конструктора. Первый – без аргументов, то есть он не принимает параметров. Второй – принимает параметр int. Внутри тела значение параметра int присваивается полю, то есть значение параметра копируется в поле. Таким образом, поле инициализируется для данного значения параметра.

Ключевое слово this перед именем поля (this.number) необязательно. Оно просто сообщает компилятору, что это поле с именем number, на которое ссылаются.

Конструктор по умолчанию, без аргументов

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

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

Параметры

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

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

Разрывы строки на Java после каждого параметра являются необязательными. Компилятор здесь игнорирует разрывы строк. Вы также можете написать объявление параметра в одну строку, если хотите, например:

Чтобы вызвать этот конструктор, который принимает три параметра, вы должны создать экземпляр объекта Employee следующим образом:

Параметры передаются конструктору в скобках после имени класса справа от знака равенства. Затем объект создается, и конструктор выполняется. После выполнения описанного выше конструктора поля, инициализированные им, будут иметь значения параметров, передаваемых ему.

Параметр конструктора может иметь то же имя, что и поле. В этом случае у компилятора возникают проблемы, зная, на что вы ссылаетесь. По умолчанию, если параметр (или локальная переменная) имеет то же имя, что и поле в том же классе, параметр (или локальная переменная) «затеняет» поле. Посмотрите на этот пример:

Внутри конструктора класса Employee идентификаторы firstName, lastName и birthYear теперь ссылаются на параметры конструктора, а не на поля Employee с одинаковыми именами. Таким образом, конструктор теперь просто устанавливает параметры, равные им самим. Поля Сотрудника никогда не инициализируются.

Чтобы сообщить компилятору, что вы имеете в виду поля класса Employee, а не параметры, поместите ключевое слово this и точку перед именем поля:

Теперь поля Employee правильно инициализируются внутри конструктора.

Вызов

Вы вызываете конструктор, когда создаете новый экземпляр класса, содержащий его:

Этот пример вызывает конструктор без аргументов для MyClass, как определено ранее в этом тексте.

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

Этот пример передает один параметр конструктору MyClass, который принимает int в качестве параметра.

Вызов конструктора из конструктора

В Java можно вызвать конструктор из другого конструктора. Для этого используется ключевое слово this:

Обратите внимание на второе определение конструктора. Внутри тела конструктора вы найдете этот оператор Java:

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

Вызов в суперклассах

В класс может расширять другой. В этом случае говорят, что он «наследует» от класса, который он расширяет. Расширяемый класс называется подклассом, а расширяемый – суперклассом.

Класс, который расширяет другой, не наследует его конструкторы. Однако подкласс должен вызывать конструктор в суперклассе внутри одного из конструкторов подкласса!

Класс Car расширяет (наследует) класс Vehicle:

Обратите внимание на конструктор в классе Car. Он вызывает конструктор в суперклассе, используя этот оператор:

Использование ключевого слова super относится к суперклассу класса. Когда за ключевым словом следуют круглые скобки, как здесь, это относится к конструктору в суперклассе. В этом случае это относится к конструктору в классе Vehicle. Поскольку Car расширяет Vehicle, все конструкторы Car должен вызывать конструктор в Vehicle.

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

Модификатор доступа конструктора определяет, какие классы в вашем приложении могут вызывать его. Например, если конструктор объявлен защищенным, то он может вызывать только классы в одном пакете или подклассы этого класса.

Класс может иметь несколько конструкторов, а каждый конструктор свой собственный модификатор доступа. Таким образом, некоторые конструкторы могут быть доступны для всех классов в вашем приложении, в то время как другие конструкторы доступны только для классов в том же пакете, подклассах или даже только для самого класса (частные конструкторы).

Исключение

Можно сгенерировать исключение из конструктора:

Обратите внимание на исключительную часть throws объявления конструктора. Она объявляет, что конструктор может выдать исключение. Если это произойдет, созданный экземпляр Car недействителен.

Вот пример вызова конструктора Car:

Если из конструктора Car выдается исключение, переменной car никогда не будет назначена ссылка на объект Car, который вы пытаетесь создать. Переменная car по-прежнему будет указывать на ноль.

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

Зачем нужны конструкторы? Объясните подробно

Предположим, у нас есть класс с именем Student . И у нас есть имя переменной экземпляра и roll_number . Теперь, если мы создадим 1000 объектов, то JVM инициализирует эти значения своим типом по умолчанию Name = null и rollNo = 0 . Идентификация этих отдельных объектов невозможна, а присвоение значений каждому из объектов увеличит объем кода, что считается плохой практикой в программировании. Поэтому, чтобы этого избежать, используются конструкторы. То есть, цель конструктора в Java состоит в том, чтобы инициализировать значение переменных экземпляра класса.

Какие типы конструкторов существуют в Java?

  • Default Constructor (Конструктор по умолчанию)
  • No-Argument Constructor (Конструктор без аргументов)
  • Parameterized Constructor (Параметризованный конструктор)

Что такое Default Constructor в Java?

Конструктор по умолчанию (Default Constructor) — это конструктор, созданный JVM во время выполнения, если конструктор не определен в классе. Основная задача конструктора по умолчанию — инициализировать значения экземпляров в соответствии с их типом по умолчанию. Пример конструктора по умолчанию в Java: Теперь для этого класса, если мы создадим объект, то внутри JVM появится конструктор по умолчанию, которому присвоено значение по умолчанию. Теперь, если мы напечатаем значение, то получим:

Что такое No-Argument Constructor (Конструктор без аргументов)?

Конструктор без аргументов — это конструктор, который можно явно определить для инициализации значения экземпляров. Например:

Что такое Parameterized Constructor (Параметризованный конструктор)?

Параметризованный конструктор — это конструктор, который принимает параметр для инициализации экземпляров. Например:

Каковы правила определения конструктора?

Имя конструктора должно совпадать с именем класса.

В Java не должно быть возвращаемого типа конструктора.

Единственно применяемые модификаторы для конструкторов:

  • public
  • default
  • protected
  • private

Конструкторы могут принимать любое количество параметров.

Модификаторы final, synchronized, static и abstract в конструкторе недопустимы.

Конструктор не поддерживает оператор return внутри своего тела.

В конструкторе могут быть исключения с оператором throw .

Допустимо использовать throws clause с конструктором.

Конструктор не должен формировать рекурсию.

Когда мы можем использовать private (частный) конструктор?

Если мы не хотим создавать объекты определенного класса извне, мы можем использовать закрытые или частные (private) конструкторы. Объявив конструкторы закрытыми, мы можем создавать объекты только внутри класса. Классы Singleton — хороший пример использования частных конструкторов.

Каким будет модификатор доступа конструктора по умолчанию, если мы не определим его явно?

Модификатор доступа конструктора по умолчанию всегда будет таким же, как модификатор класса. Если класс публичный, то конструктор тоже будет публичным. Если класс частный, то конструктор тоже будет частным. Аналогично будет и с другими модификаторами доступа.

Напишите вывод по приведенному ниже фрагменту кода и поясните

Мы получим такой вывод, потому что если мы не укажем в конструкторе ключевое слово super() или this() в первой строке, то JVM сама автоматически поместит его во время выполнения. JVM делает это, потому что он наследуется от другого класса, и его функциональность также будет реализована в производном классе. Таким образом, присваивая экземплярам базового класса значения по умолчанию, JVM по умолчанию добавляет ключевое слово super() .

Рассмотрите код и укажите, является ли он действительным (valid) или недействительным (invalid). Поясните причину

Приведенный выше код недействителен, потому что он является одним и тем же конструктором внутри конструктора Scaler Academy . Это создает рекурсию в конструкторе, что недопустимо. Соответственно, мы получим ошибку времени компиляции, связанную с вызовом рекурсивного конструктора.

Можем ли мы использовать два конструктора в одном классе в Java?

  • Параметры конструкторов должны быть разными.
  • В конструкторе не должно быть рекурсии.

Можем ли мы переопределить (override) конструктор в Java?

Может ли конструктор быть окончательным (final) в Java?

Никакой конструктор не может быть окончательным (final). Это связано с тем, что ключевые слова final используются для остановки переопределения метода в производном классе. Но в конструкторе концепция переопределения неприменима, поэтому нет необходимости писать ключевое слово final . Если же мы напишем ключевое слово final в конструкторе, то получим ошибку времени компиляции под названием required return type (требуется возвращаемый тип), потому что компилятор рассматривает это как метод.

Может ли конструктор быть статическим (static) в Java?

Нет, конструктор Java не может быть статическим. Это связано с тем, что ключевые слова static используются, когда мы хотим, чтобы член принадлежал классу, а не объекту. Но конструкторы предназначены для инициализации объектов, поэтому компилятор будет рассматривать его как метод. Мы получим ошибку required return type.

Опишите разницу между super(), super и this(), this

Что такое деструкторы? Существует ли деструктор в Java?

Destructors (Деструкторы) используются для освобождения памяти, полученной программой. Например, если память необходима программе во время ее выполнения, то деструктор освобождает эту память, чтобы ее могли использовать другие программы. В Java нет понятия деструктора, поскольку работа по освобождению памяти в Java обрабатывается сборщиком мусора.

Что такое цепь конструкторов (constructor chaining) в Java?

Конструкторы классов. Java JDK 1.5 - 1

Конструктор имеется в любом классе. Даже если вы его не написали, компилятор Java сам создаст конструктор по умолчанию (default constructor). Этот конструктор пустой и не делает ничего, кроме вызова конструктора суперкласса. Т.е. если написать: то это эквивалентно написанию: В данном случае явно класса предка не указано, а по умолчанию все классы Java наследуют класс Object поэтому вызывается конструктор класса Object . Если в классе определен конструктор с параметрами, а перегруженного конструктора без параметров нет, то вызов конструктора без параметров является ошибкой. Тем не менее, в Java, начиная с версии 1.5, можно использовать конструкторы с аргументами переменной длины. И если есть конструктор, имеющий аргумент переменной длины, то вызов конструктора по умолчанию ошибкой не будет. Не будет потому, что аргумент переменной длины может быть пустым. Например, следующий пример не будет компилироваться, однако если раскомментарить конструктор с аргументом переменной длины, то компиляция и запуск пройдут успешно и в результате работы строки кода DefaultDemo dd = new DefaultDemo() ; вызовется конструктор DefaultDemo(int . v) . Естественно, что в данном случае необходимо пользоваться JSDK 1.5. Файл DefaultDemo.java Результат вывода программы при раскомментаренном конструкторе: Однако, в распространенном случае, когда в классе вообще не определено ни одного конструктора, вызов конструктора по умолчанию (без параметров) будет обязательным явлением, поскольку подстановка конструктора по умолчанию происходит автоматически.

Создание объекта и конструкторы

  • Ищется класс объекта среди уже используемых в программе классов. Если его нет, то он ищется во всех доступных программе каталогах и библиотеках. После обнаружения класса в каталоге или библиотеке выполняется создание, и инициализация статических полей класса. Т.е. для каждого класса статические поля инициализируются только один раз.
  • Выделяется память под объект.
  • Выполняется инициализация полей класса.
  • Отрабатывает конструктор класса.
  • Формируется ссылка на созданный и инициализированный объект. Эта ссылка и является значением выражения, создающего объект. Объект может быть создан и с помощью вызова метода newInstance() класса java.lang.Class . В этом случае используется конструктор без списка параметров.

Перегрузка конструкторов

Конструкторы одного класса могут иметь одинаковое имя и различную сигнатуру. Такое свойство называется совмещением или перегрузкой(overloading). Если класс имеет несколько конструкторов, то присутствует перегрузка конструкторов.

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

Сигнатура конструктора – это количество и типы параметров, а также последовательность их типов в списке параметров конструктора. Тип возвращаемого результата не учитывается. Конструктор не возвращает никаких параметров. Это положение объясняет в некотором смысле, как Java различает перегруженные конструкторы или методы. Java различает перегруженные методы не по возвращаемому типу, а по числу, типам и последовательности типов входных параметров. Конструктор не может возвращать даже тип void , иначе он превратится в обычный метод, даже не смотря на сходство с именем класса. Следующий пример демонстрирует это. Файл VoidDemo.java В результате программа выведет: Это лишний раз доказывает, что конструктором является метод без возвращаемых параметров. Тем не менее, для конструктора можно задать один из трех модификаторов public , private или protected . И пример теперь будет выглядеть следующим образом: Файл VoidDemo2.java В конструкторе разрешается записывать оператор return , но только пустой, без всякого возвращаемого значения. Файл ReturnDemo.java

Конструкторы, параметризированные аргументами переменной длины

В Java SDK 1.5 появился долгожданный инструмент – аргументы переменной длины для конструкторов и методов(variable-length arguments). До этого переменное количество документов обрабатывалось двумя неудобными способами. Первый из них был рассчитан на то, что максимальное число аргументов ограничено небольшим количеством и заранее известно. В таком случае можно было создавать перегружаемые версии метода, по одной на каждый вариант списка передаваемых в метод аргументов. Второй способ рассчитан на неизвестное заранее и большое количество аргументов. В этом случае аргументы помещались в массив, и этот массив передавался методу. Аргументы переменной длины чаще всего задействованы в последующих манипуляциях с инициализациями переменных. Отсутствие некоторых из ожидаемых аргументов конструктора или метода удобно заменять значениями по умолчанию. Аргумент переменной длины есть массив, и обрабатывается как массив. Например, конструктор для класса Checking с переменным числом аргументов будет выглядеть так: Символьная комбинация . сообщает компилятору о том, что будет использоваться переменное число аргументов, и что эти аргументы будут храниться в массиве, значение ссылки на который содержится в переменной n. Конструктор может вызываться с разным числом аргументов, включая их полное отсутствие. Аргументы автоматически помещаются в массив и передаются через n. В случае отсутствия аргументов длина массива равна 0. В список параметров наряду с аргументами переменной длины могут быть включены и обязательные параметры. В этом случае параметр, содержащий переменное число аргументов должен обязательно быть последним в списке параметров. Например: Вполне очевидное ограничение касается количества параметров с переменной длиной. В списке параметров должен быть только один параметр переменной длины. При наличии двух параметров переменной длины компилятору невозможно определить, где заканчивается один параметр и начинается другой. Например: Файл Checking.java Например, есть аппаратура, способная распознавать номера автомобилей и запоминать номера квадратов местности, где побывал каждый из автомобилей за день. Необходимо из общей массы зафиксированных автомобилей отобрать те, которые в течение дня побывали в двух заданных квадратах, скажем 22 и 15, согласно карте местности. Вполне естественно, что автомобиль может в течение дня побывать во многих квадратах, а может только в одном. Очевидно, что количество посещенных квадратов ограничено физической скоростью автомобиля. Составим небольшую программу, где конструктор класса будет принимать в качестве аргументов номер автомобиля как обязательный параметр и номера посещенных квадратов местности, число которых может быть переменным. Конструктор будет проверять, не появился ли автомобиль в двух квадратах, если появился, то вывести его номер на экран.

Передача параметров в конструктор

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

Конструкторы и блоки инициализации, последовательность действий при вызове конструктора

  1. Все поля данных инициализируются своими значениями, предусмотренными по умолчанию (0, false или null).
  2. Инициализаторы всех полей и блоки инициализации выполняются в порядке их перечисления в объявлении класса.
  3. Если в первой строке конструктора вызывается другой конструктор, то выполняется вызванный конструктор.
  4. Выполняется тело конструктора.
  • присвоить значение в объявлении;
  • присвоить значения в блоке инициализации;
  • задать его значение в конструкторе.

Ключевое слово this в конструкторах

Конструкторы используют this чтобы сослаться на другой конструктор в этом же классе, но с другим списком параметров. Если конструктор использует ключевое слово this , то оно должно быть в первой строке, игнорирование этого правила приведет к ошибке компилятора. Например: Файл ThisDemo.java Результат вывода программы: В данном примере имеется два конструктора. Первый получает строку-аргумент. Второй не получает никаких аргументов, он просто вызывает первый конструктор используя имя "John" по-умолчанию. Таким образом, можно с помощью конструкторов инициализировать значения полей явно и по умолчанию, что часто необходимо в программах.

Ключевое слово super в конструкторах

Конструкторы используют super , чтобы вызвать конструктор суперкласса. Если конструктор использует super , то этот вызов должен быть в первой строке, иначе компилятор выдаст ошибку. Ниже приведен пример: Файл SuperClassDemo.java В этом простом примере конструктор Child() содержит вызов super() , который создает экземпляр класса SuperClassDemo , в дополнение к классу Child . Так как super должен быть первым оператором, выполняемым в конструкторе подкласса, этот порядок всегда одинаков и не зависит от того, используется ли super() . Если он не используется, то сначала будет выполнен конструктор по умолчанию (без параметров) каждого суперкласса, начиная с базового класса. Следующая программа демонстрирует, когда выполняются конструкторы. Файл Call.java Вывод этой программы: Конструкторы вызываются в порядке подчиненности классов. В этом есть определенный смысл. Поскольку суперкласс не имеет никакого знания о каком-либо подклассе, то любая инициализация, которую ему нужно выполнить, является отдельной. По возможности она должна предшествовать любой инициализации, выполняемой подклассом. Поэтому-то она и должна выполняться первой.

Настраиваемые конструкторы

Механизм идентификации типа во время выполнения является одним из мощных базовых принципов языка Java, который реализует полиморфизм. Однако такой механизм не страхует разработчика от несовместимого приведения типов в ряде случаев. Самый частый случай – манипулирование группой объектов, различные типы которых заранее неизвестны и определяются во время выполнения. Поскольку ошибки, связанные с несовместимостью типов могут проявиться только на этапе выполнения, то это затрудняет их поиск и ликвидацию. Введение настраиваемых типов в Java 2 5.0 частично отодвигает возникновение подобных ошибок с этапа выполнения на этап компиляции и обеспечивает недостающую типовую безопасность. Отпадает необходимость в явном приведении типов при переходе от типа Object к конкретному типу. Следует иметь ввиду, что средства настройки типов работают только с объектами и не распространяются на примитивные типы данных, которые лежат вне дерева наследования классов. Благодаря настраиваемым типам все приведения выполняются автоматически и скрыто. Это позволяет обезопасить от несоответствия типов и гораздо чаще повторно использовать код. Настраиваемые типы можно использовать в конструкторах. Конструкторы могут быть настраиваемыми, даже если их класс не является настраиваемым типом. Например: Поскольку конструктор GenConstructor задает параметр настраиваемого типа, который должен быть производным классом от класса Number , его можно вызвать с любы

Нужно написать программу, содержащую класс Triangle . В данном классе должно быть три конструктора:

  1. Конструктор, принимающий три стороны.
  2. Конструктор, принимающий две стороны и угол между ними и рассчитывающий третью сторону по теореме косинусов.
  3. Конструктор, принимающий два угла и сторону между ними и рассчитывающий оставшиеся стороны по теореме синусов (памятуя о теореме о сумме углов треугольника).

Каждый Triangle также должен возвращать собственную площадь.

То есть имеем следующее:

Вся проблема в том, что у всех трех конструкторов одинаковая сигнатура, но разная логика.
Вопрос: как решить эту проблему "правильно"?

  1. Замена параметра(ов) double на string и дальнейший парсинг string в double .
  2. Замена параметра(ов) double на float .

Вынесение параметров в массив:

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

4 ответа 4

Вариант 1 - используйте именованные конструкторы.

Именованным конструктором называется статический метод, задача которого - создать объект с заданными параметрами. Иногда их еще не вполне корректно называют фабричными методами.

Вариант 2 - использовать доменные типы данных

Определяем типы данных "длина" и "угол" - и используем их в конструкторах.

Я бы использовал одновременно оба подхода. Одной только строгой типизации может оказаться недостаточно: вдруг вам нужен треугольник по трём сторонам и по трём медианам?

@VladD три медианы не являются стандартным признаком равенства треугольников - а потому построение треугольника по трем медианам уже не будет задачей конструктора :)

Ну, два катета и катет с гипотенузой — более распространённый сюжет. Или там катет и прилежащий угол против катета и противолежащего угла.

@VladD это все способы задания прямоугольных треугольников, в треугольниках общего вида нет никаких катетов и гипотенуз.

Да, конечно, я не о данном частном случае. Я привёл пример проблемы из той же предметной области, которая не решается одной лишь типизацией (в разумном смысле понятия «типизация»). То, что в частном случае можно формально обойтись без именованных конструкторов — случайная подробность конкретной частной задачи, на мой вкус.

Как вариант сгруппировать наборы параметров в структуры:

но мне кажется более правильный вариант с фабриками в ответе Павла

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

Т.к. выбор перегруженной функции (и конструктора в том числе) осуществляется на основании типов, понятно, что создать несколько разных конструкторов с одинаковыми сигнатурами не получится.

Проблему можно решить путем добавления дополнительного параметра.

Заводятся новые "пустые" классы (имена взял из ответа @Argon):

Сигнатуры конструкторов в этом случае будут следующие:

Используется перечисление enum .

В этом случае конструктор будет единственный с ветвлением логики внутри:

При этом для аргумента kind можно задать значение по умолчанию, если планируется, что будет преобладать тот или иной вариант построения треугольника. Например:

Наследование Java дает возможность одному классу наследовать свойства другого класса. Также называется расширением класса .

Когда один класс наследуется от другого класса, эти два класса принимают определенные роли. Подкласс расширяет суперкласс. Или подкласс наследует от суперкласса. Подкласс – это специализация суперкласса, а суперкласс – это обобщение одного или нескольких подклассов.

Как метод повторного использования кода

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

Вот диаграмма, иллюстрирующая класс с именем Vehicle, который имеет два подкласса, называемые Car и Truck.

объяснение наследования

Класс Vehicle является суперклассом легковых и грузовых автомобилей. Автомобиль и Грузовик – подклассы Автомобиля. Класс Vehicle может содержать те поля и методы, которые нужны всем транспортным средствам (например, номерной знак, владелец и т. д.), Тогда как Car и Truck могут содержать поля и методы, специфичные для легковых и грузовых автомобилей.

Примечание. Некоторые люди утверждают, что наследование – это способ классификации ваших классов в зависимости от того, чем они являются. Автомобиль – это Автомобиль. Грузовик – транспортное средство. Однако на практике это не то, как вы определяете, какие суперклассы и подклассы должны иметь ваше приложение. Обычно это определяется тем, как вам нужно работать с ними в приложении.

Например, вам нужно ссылаться на объекты Car и Truck как объекты Vehicle? Вам нужно обрабатывать объекты Car и Truck одинаково? Тогда имеет смысл иметь общий суперкласс Vehicle для двух классов. Если вы никогда не обрабатываете объекты Car и Truck одним и тем же способом, нет смысла иметь для них общий суперкласс, кроме, возможно, совместного использования кода между ними (чтобы избежать написания дублирующего кода).

Классовые иерархии

Суперклассы и подклассы образуют структуру наследования. На вершине иерархии классов суперклассы. В нижней части иерархии – подклассы. Иерархия может иметь несколько уровней, то есть несколько уровней суперклассов и подклассов. Подкласс сам может быть суперклассом других подклассов и т. д.

Основы

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

Что унаследовано?

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

Поля и методы с модификаторами доступа по умолчанию (пакет) могут быть доступны для подклассов, только если он находится в том же пакете, что и суперкласс. На частные поля и методы суперкласса никогда нельзя ссылаться непосредственно, только через методы, достижимые из подкласса (например, методы по умолчанию (пакет), защищенные и публичные).

Конструкторы не наследуются подклассами, но конструктор подкласса должен вызывать конструктор в суперклассе.

Единичное наследование

Механизм наследования позволяет наследовать класс только от одного суперкласса (единичное наследование). В некоторых языках программирования, таких как C ++, подкласс может наследоваться от нескольких суперклассов (множественное).

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

Объявление

Объявляется с использованием ключевого слова extends:

Класс Car в этом примере расширяет класс Vehicle, то есть Car наследуется от Vehicle. Поскольку Car расширяет Vehicle, защищенное поле licensePlate из Vehicle наследуется Car. Когда licensePlate наследуется, оно становится доступным внутри экземпляра Car.

В поле licensePlate на самом деле не ссылаются из класса Car в приведенном выше коде, но можно, если мы захотим:

Ссылка происходит внутри метода getLicensePlate(). Во многих случаях имело бы смысл поместить этот метод в класс Vehicle, где находится поле licensePlate.

Приведение типов

Можно ссылаться на подкласс как на экземпляр одного из его суперклассов. Например, используя определения класса из примера в предыдущем разделе, можно ссылаться на экземпляр класса Car как на экземпляр класса Vehicle. Так как Car расширяет (наследует) Vehicle, он также называется Vehicle.

Вот пример кода Java:

  1. Сначала создается экземпляр автомобиля.
  2. Экземпляр Car присваивается переменной типа Vehicle.
  3. Теперь переменная Vehicle (ссылка) указывает на экземпляр Car. Это возможно, потому что Car наследуется от Vehicle.

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

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

Upcasting и Downcasting

Вы всегда можете привести объект подкласса к одному из его суперклассов, либо из типа суперкласса к типу подкласса, но только если объект действительно является экземпляром этого подкласса (или экземпляром подкласса этого подкласса). Таким образом, этот пример downcasting действителен:

Однако следующий приведенный ниже пример недопустим. Компилятор примет его, но во время выполнения, выдаст исключение ClassCastException.

Объект «Грузовик» может быть передан объекту «Автомобиль», но позже он не может быть передан объекту «Автомобиль». Это приведет к исключению ClassCastException.

Переопределяющие методы

В подклассе вы можете переопределить методы, определенные в суперклассе:

Обратите внимание, как и класс Vehicle, и класс Car определяют метод setLicensePlate(). Теперь каждый раз, когда setLicensePlate() вызывается для объекта Car, вызывается метод, определенный в классе Car. Метод в суперклассе игнорируется.

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

Невозможно переопределить закрытые методы из суперкласса. Если суперкласс вызывает внутренний метод из другого, он будет продолжать вызывать его из суперкласса, даже при создании частного метода в подклассе с той же сигнатурой.

Аннотация @override

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

Для этого и нужна аннотация @ override. Вы размещаете ее над методом, который переопределяет метод в суперклассе:

Вызов методов суперкласса

Если вы переопределяете метод в подклассе, но по-прежнему должны вызывать метод, определенный в суперклассе, используйте ссылку super, например:

В приведенном выше примере кода метод setLicensePlate() в классе Car вызывает метод setLicensePlate() в классе Vehicle.

Вы можете вызывать реализации суперкласса из любого метода в подклассе, как описано выше. Он не должен быть из самого переопределенного метода. Например, вы могли бы также вызвать super.setLicensePlate() из метода в классе Car с именем updateLicensePlate(), который не переопределяет метод setLicensePlate().

Пример инструкции

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

После выполнения этого кода переменная isCar будет содержать значение true.

Инструкция instanceof также может использоваться для определения того, является ли объект экземпляром суперкласса своего класса. Вот пример, который проверяет, является ли объект Car экземпляром Vehicle:

Предполагая, что класс Car расширяет (наследует от) класс Vehicle, переменная isVehicle будет содержать значение true после выполнения этого кода. Объект Car также является объектом Vehicle, поскольку Car является подклассом Vehicle.

Как видите, инструкция instanceof может использоваться для изучения иерархии наследования. Тип переменной, используемый с ней, не влияет на ее результат. Посмотрите на этот пример:

Несмотря на то, что переменная транспортного средства имеет тип Vehicle, объект, на который она в конечном итоге указывает в этом примере, является объектом Car. Поэтому экземпляр транспортного средства автомобиля будет оценен как истинный.

Вот тот же пример, но с использованием объекта Truck вместо объекта Car:

После выполнения этого кода isCar будет содержать значение false. Объект Truck не является объектом Car.

Как наследуются

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

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

Вот пример, который иллюстрирует, как поля в подклассах скрывают поля в суперклассах:

Обратите внимание, как для обоих классов определено поле licensePlate.

И класс Vehicle, и класс Car имеют методы setLicensePlate() и getLicensePlate(). Методы в классе Car вызывают соответствующие методы в классе Vehicle. В результате оба набора методов получают доступ к полю licensePlate в классе Vehicle.

Однако метод updateLicensePlate() в классе Car напрямую обращается к полю licensePlate. Таким образом, он получает доступ к полю licensePlate класса Car. Следовательно, вы не получите тот же результат, если вызовете setLicensePlate(), как при вызове метода updateLicense().

Посмотрите на следующие строки кода:

Этот код распечатает текст 123.

Метод updateLicensePlate() устанавливает значение номерного знака в поле licensePlate в классе Car. Однако метод getLicensePlate() возвращает значение поля licensePlate в классе Vehicle. Следовательно, значение 123, которое устанавливается как значение для поля licensePlate в классе Vehicle с помощью метода setLicensePlate(), является тем, что выводится на печать.

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

Механизм наследования не включает конструкторы. Другими словами, конструкторы суперкласса не наследуются подклассами. Подклассы могут по-прежнему вызывать конструкторы в суперклассе, используя конструкцию super().

Фактически, конструктор подкласса должен вызывать один из конструкторов в суперклассе как самое первое действие внутри своего тела. Вот как это выглядит:

Обратите внимание на вызов super() внутри конструктора Car. Этот вызов super() выполняет конструктор в классе Vehicle.

Возможно, вы видели классы, где конструкторы подкласса, похоже, не вызывали конструкторы в суперклассе. Возможно, у суперкласса даже не было конструктора. Тем не менее, конструкторы подкласса все еще называют конструкторы суперкласса в этом случае. Вы просто не могли этого увидеть.

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

Поэтому следующая версия транспортного средства эквивалентна версии, показанной чуть выше:

Если конструктор явно не вызывает конструктор в суперклассе, компилятор вставляет неявный вызов конструктора no-arg в суперклассе. Это означает, что следующая версия класса Car фактически эквивалентна версии, показанной ранее:

Фактически, поскольку конструктор теперь пуст, мы могли бы опустить его, и компилятор вставил бы его и неявный вызов конструктора no-arg в суперклассе. Вот как тогда будут выглядеть два класса:

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

Если бы класс Vehicle не имел конструктора без аргументов, но имел другой, который принимает параметры, компилятор жаловался бы. Класс Car затем должен был бы объявить конструктор, а внутри него вызвать конструктор в классе Vehicle.

Вложенные классы

Те же правила наследования применяются к вложенным классам. Если объявлены закрытыми, не наследуются. Вложенные классы с модификатором доступа по умолчанию (пакет) доступны только для подклассов, если подкласс находится в том же пакете, что и суперкласс. С модификатором защищенного или открытого доступа всегда наследуются подклассами.

Обратите внимание, как можно создать экземпляр вложенного класса MyNestedClass, который определен в суперклассе(MyClass) посредством ссылки на подкласс(MySubclass).

Финальные классы

Класс может быть объявлен окончательным(final):

Последний класс не может быть продлен. Другими словами, вы не можете наследовать от финального класса.

Абстрактные классы

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

Они предназначены для расширения / создания полной реализации. Таким образом, вполне возможно расширить абстрактный класс. Правила наследования такие же для них, как и для неабстрактных классов.

Читайте также: