This в конструкторе абстрактного класса

Обновлено: 29.04.2024

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

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

Так вот, все работает хорошо, вот только есть сомнение, что как-то не по фен-шую эти конструкторы сделаны как оптимизировать?

Вполне по фен-шую. Если включать в базовый класс какую-либо логику вы не собираетесь, то можете просто заменить его на интерфейс.

А что вы подразумеваете под оптимизацией? Что именно, по вашему мнению, нуждается в улучшении? Какой параметр?

@andreycha Спасибо за ответ! Выбор сделан в пользу абстрактного класса, а не интерфейса, чтобы не дублировать в наследниках N строк с объявлением полей, их геттерами и сеттерами. Классы полностью идентичны по своей сути, разница только в логике ключевого метода

2 ответа 2

По фэн шую, конструктор абстрактного класса должен быть protected , то есть:

На сколько я понял, у вас примерно следующая ситуация:

Основная причина для создания новых классов - разное поведение метода. Если вы вынесете этот метод в функциональный интерфейс, то все равно вам придется объявлять два класса с транзитными конструкторами (которые вызывают super).

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

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

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

Ключевое слово this

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

Допустим, у нас есть класс Human , для которого определено поле «имя»: - 2" width="462" />
Давайте для переменной name создадим сеттер (setter вполне рабочий и никакого подвоха здесь нет): - 3" width="700" />
Обратите внимание, что в метод (сеттер) setName мы передаем переменную String newName . Мы ввели новую переменную и (в общем-то) могли назвать ее как угодно ведь она будет видна только в пределах метода setName . Обратите внимание, что в сеттере есть одна строка: То есть по факту мы ввели новую переменную newName и присвоили ее уже существующей в классе переменной name . Многим программистом казалось это странным, — вводить переменную с новым именем, если в итоге речь идет об одном и том же. Об имени в классе Human . Поэтому, разработчики языка задумались о том, чтобы удобно сделать использование одного имени переменной. Другими словами, зачем иметь два имени для переменной, обозначающей одно и то же. То есть хотелось бы сделать как-то так: - 4" width="700" />
Но в этом случае возникает проблема. У нас теперь две переменные, которые называются одинаково. Один String name принадлежит классу Human , а другой String name его методу setName . Поэтому Java – машина не знает, какую переменную вы имеете ввиду, когда пишете строку в сеттере: Java берет самую близкую – name из метода setName :

Ключевое слово this <в примерах></p>
<p>- 5

и получается, что вы просто присваиваете значение переменной name из этого метода, ей же. Что конечно не имеет никакого смысла. Поэтому нужен был какой-то способ, чтобы отличить переменную name из класса Human , от переменной name из метода setName .Для решения этой проблемы и было введено ключевое слово this , которое в данном случае укажет, что нужно вызывать переменную не метода, а класса Human :

Ключевое слово this <в примерах></p>
<p>- 6

То есть this сошлется на вызвавший объект, как было сказано в начале статьи. В результате чего имя человека через сеттер setName будет установлено создаваемому объекту. Ниже приведен программный код без использования ключевого слова this . В коде создается объект класса Human и присваивается ему имя:

А ниже программный код с ключевым словом this : Таким образом, здесь this позволяет не вводить новые переменные для обозначения одного и того же, что позволяет сделать код менее «перегруженным» дополнительными переменными.

Пример второй — Применение this для явного вызова конструктора

  • вызови this (этот) конструктор, который имеет два параметра.
  • и добавить недостающую переменную.

Ключевое слово this <в примерах></p>
<p>- 9

1. Могут ли в языке Java у абстрактного класса быть конструкторы?

Да, в абстрактном классе в Java можно объявить и определить конструкторы. Поскольку создавать экземпляры абстрактных классов нельзя, вызвать такой конструктор можно только при формировании цепочки конструкторов, то есть при создании экземпляра конкретного класса-реализации. Но представьте, что интервьюер задаст затем вопрос: а какой смысл в конструкторе, если создать экземпляр абстрактного класса все равно нельзя? Дело в том, что его всё равно можно использовать для задания начальных значений общих переменных, объявленных в абстрактном классе и используемых различными реализациями. Даже если вы не объявили никакого конструктора, компилятор добавит в абстрактный класс конструктор по умолчанию без аргументов. Без него ваш подкласс не скомпилируется, поскольку первый оператор в любом конструкторе представляет собой неявный вызов super() – конструктора суперкласса по умолчанию в языке Java.

2. Могут ли абстрактные классы в языке Java реализовывать интерфейсы? Должны ли они реализовывать все методы?

Да, абстрактные классы могут реализовывать интерфейсы с помощью ключевого слова implements . Поскольку они абстрактные, то не обязаны реализовывать все методы. Наличие абстрактного базового класса и интерфейса для объявления типа является рекомендуемой практикой. Пример — интерфейс java.util.List и соответствующий абстрактный класс java.util.AbstractList . Поскольку AbstractList реализует все общие методы, то конкретные реализации (например, LinkedList и ArrayList ) не должны реализовать все методы, как в случае, если бы они реализовали интерфейс List напрямую. Это решение сочетает преимущество использования интерфейса для объявления типа и гибкость абстрактного класса для реализации всего общего поведения в одном месте. В книге Джошуа Блоха «Java. Эффективное программирование» есть отличная глава на тему использования интерфейсов и абстрактных классов в Java, для лучшего понимания имеет смысл её изучить.

3. Может ли абстрактный класс быть final?

Нет, не может. Ключевое слово final означает, что класс на вершине иерархии, и у него не может быть наследников. А абстрактный класс без наследников — это сферический конь в вакууме, так как нельзя создать экземпляр abstract class . Таким образом, если класс одновременно abstract и final , то у него нет наследников и нельзя создать его экземпляр. Компилятор Java выдаст ошибку, если сделать класс одновременно abstract и final .

4. Могут ли у абстрактного класса в языке Java быть статические методы?

Да, абстрактные классы могут объявлять и определять статические методы. Только необходимо следовать общим принципам создания статических методов в Java, поскольку они нежелательны при объектно-ориентированном проектировании, ведь переопределение статических методов в Java невозможно. Статические методы в абстрактном классе – явление очень редкое, но, если на это есть уважительные причины, вам ничего не помешает их использовать.

5. Можно ли создать экземпляр абстрактного класса?

Нет, этого делать нельзя. Суть абстрактного класса заключается в том, что он не завершён, и его нужно завершить в классах-наследниках. То есть этот класс не готов к использованию. В нём, например, может отсутствовать реализация каких-то методов. Раз класс не готов к использованию, то нельзя создавать его объект. А вот экземпляры наследников абстрактного класса создавать можно. Компилятор Java выдаст ошибку, если программа попытается создать экземпляр абстрактного класса.

6. Обязательно ли в абстрактном классе должны быть абстрактные методы?

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

7. Каковы различия между абстрактным классом и интерфейсом в Java?

  • Интерфейс описывает только поведение (методы) объекта, а вот состояний (полей) у него нет (кроме public static final ), в то время как у абстрактного класса они могут быть.
  • Абстрактный класс наследуется (extends), а интерфейс — реализуется (implements). Мы можем наследовать только один класс, а реализовать интерфейсов — сколько угодно. Интерфейс может наследовать (extends) другой интерфейс/интерфейсы.
  • Абстрактные классы используются, когда есть отношение "is-a", то есть класс-наследник расширяет базовый абстрактный класс, а интерфейсы могут быть реализованы разными классами, вовсе не связанными друг с другом.

8. Когда имеет смысл предпочесть абстрактный класс интерфейсу и наоборот?

  • Вы хотите поделиться кодом между несколькими тесно связанными классами.
  • Вы ожидаете, что классы, которые расширяют ваш абстрактный класс, имеют много общих методов или полей, или требуют других модификаторов доступа, кроме public (например, protected и private ).
  • Вы хотите объявить нестатические или не-final поля. Это позволяет вам определять методы, которые могут получить доступ и изменить состояние объекта, которому они принадлежат.
  • Вы ожидаете, что несвязанные классы будут реализовывать ваш интерфейс. Например, интерфейсы Comparable и Cloneable реализуются многими несвязанными классами.
  • Вы хотите определить поведение конкретного типа данных, но вам не важно, кто его реализует.
  • Вы хотите использовать множественное наследование типа.

9. Что такое абстрактный метод в языке Java?

Абстрактный метод – это метод без тела. Вы просто объявляете метод, не определяя его, с использованием ключевого слова abstract в объявлении метода. Все объявленные внутри интерфейса в языке Java методы – по умолчанию абстрактные. Вот пример абстрактного метода в языке Java: Теперь, для реализации этого метода необходимо расширить абстрактный класс и этот метод переопределить.

I am slightly confused towards why I am able to use the 'this' in abstract classes.

I am making a very very simple object oriented role playing game. I have a base/super class called Items . I then have two types of items, equiptable and non-equiptable.

Theres the interface for equiptable items.

Next, I have an abstract class called Weapons :

This extends the base class of Items (this class only contains a name for the item at the moment, so not worth show) and implements the interface 'IEquiptable'.

Finally I have a class called Swords

This extends Weapons and provides a constructor for the sword.

Here is some of my Player class

To eqipt a weapon I am using this method: (in my player class)

I don't understand why this works. tempWep is being given the value of the first Weapon in the list. But then it is calling the method equiptItem() which is being implemented in by abstract class and then 'this' is being assigned, which I cannot follow how it doesn't throw an error for not being to initialize Weapons

Hopefully you can understand what I mean.

Weapons don't equip themselves. The Person should have the Equip method. Regardless of that, an abstract class, although you can't instantiate it directly, does after all represent an object - hence this .

Maybe this will help. Mentally imagine every method that uses "this" is now a static method. Now add a formal parameter to that method called _this . Mentally replace every this with _this . For properties, imagine they are static methods static int get_Color(Shape _this) < return _this.color; >and similarly for setters. In a world where every method is static and no one uses this , do you have any objection? Because that's basically how it works. this is just a convention that there's a hidden formal parameter.

2 Answers 2

Language constraints mean you cannot create an abstract class directly, however it is totally possible to have an instance of it - you simply create an instance of a derived class and then cast it down to the abstract class. An abstract class can also have implementation in it, so the usage of the keyword this is perfectly normal and acceptable.

Take a look at this Microsoft on Abstract Keyword. It's meant to work like that. Just Because the object itself can not be instantiated does not mean it will never be instantiated. As a matter of fact your guaranteed to have an implementing class before you can possibly reach the this keyword in your code. Thus it's safe. ( When you request a Weapon from your Item list, that Weapon had to have already been instantiated in your code. Presumably to a Swords or the like. Otherwise you could never access it to store it in the list and so on )

Finally I will note that the way you are using the Weapon class can be slightly confusing. So a short analogy in on point. Think about it this way. Just because the thought, swing a weapon, means nothing without a actual weapon in hand, does not mean that if you are holding a weapon in hand; it has can not refer to that weapon.

Hope it's a bit clearer now.

Related

Hot Network Questions

To subscribe to this RSS feed, copy and paste this URL into your RSS reader.

Site design / logo © 2022 Stack Exchange Inc; user contributions licensed under cc by-sa. rev 2022.6.22.42430

Автор материала, перевод которого мы сегодня публикуем, говорит, что когда она работала в сфере бухучёта, там применялись понятные термины, значения которых легко найти в словаре. А вот занявшись программированием, и, в частности, JavaScript, она начала сталкиваться с такими понятиями, определения которых в словарях уже не найти. Например, это касается ключевого слова this . Она вспоминает то время, когда познакомилась с JS-объектами и функциями-конструкторами, в которых использовалось это ключевое слово, но добраться до его точного смысла оказалось не так уж и просто. Она полагает, что подобные проблемы встают и перед другими новичками, особенно перед теми, кто раньше программированием не занимался. Тем, кто хочет изучить JavaScript, в любом случае придётся разобраться с this . Этот материал направлен на то, чтобы всем желающим в этом помочь.




Что такое this?

Предлагаю вашему вниманию моё собственное определение ключевого слова this . This — это ключевое слово, используемое в JavaScript, которое имеет особое значение, зависящее от контекста в котором оно применяется.

Причина, по которой this вызывает столько путаницы у новичков, заключается в том, что контекст this меняется в зависимости от его использования.

This можно считать динамическим ключевым словом. Мне нравится, как понятие «контекст» раскрыто в этой статье Райана Морра. По его словам, контекст всегда является значением ключевого слова this , которое ссылается на объект, «владеющий» кодом, выполняемым в текущий момент. Однако, тот контекст, который имеет отношение к this , это не то же самое, что контекст выполнения.

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

Ситуации, когда this указывает на объект window

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

При использовании функций, которые имеются в глобальном контексте (это отличает их от методов объектов) ключевое слово this в них будет указывать на объект window .

Попробуйте выполнить этот код, например, в консоли браузера:

Использование this внутри объекта

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

This и вложенные объекты

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

Особенности стрелочных функций

Стрелочные функции ведут себя не так, как обычные функции. Вспомните: при обращении к this в методе объекта, этому ключевому слову соответствует объект, которому принадлежит метод. Однако это не относится к стрелочным функциям. Вместо этого, this в таких функциях относится к глобальному контексту (к объекту window ). Рассмотрим следующий код, который можно запустить в консоли браузера.


Если, озадачившись рассматриваемым вопросом, заглянуть на MDN, там можно найти сведения о том, что стрелочные функции имеют более короткую форму записи, чем функциональные выражения и не привязаны к собственным сущностям this , arguments , super или new.target . Стрелочные функции лучше всего подходят для использования их в роли обычных функций, а не методов объектов, их нельзя использовать в роли конструкторов.

Прислушаемся к MDN и не будем использовать стрелочные функции в качестве методов объектов.

Использование this в обычных функциях

Когда обычная функция находится в глобальной области видимости, то ключевое слово this , использованное в ней, будет привязано к объекту window . Ниже приведён пример, в котором функцию test можно рассматривать в виде метода объекта window .


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

Обращение к this из функции, которая была объявлена за пределами объекта, а потом назначена в качестве его метода

Рассмотрим пример с уже известным нам объектом dog . В качестве метода этого объекта можно назначить функцию chase , объявленную за его пределами. Тут в объекте dog никаких методов не было, до тех пор, пока мы не создали метод foo , которому назначена функция chase . Если теперь вызвать метод dog.foo , то будет вызвана функция chase . При этом ключевое слово this , к которому обращаются в этой функции, указывает на объект dog . А функция chase , при попытке её вызова как самостоятельной функции, будет вести себя неправильно, так как при таком подходе this будет указывать на глобальный объект, в котором нет тех свойств, к которым мы, в этой функции, обращаемся через this .

Ключевое слово new и this

Ключевое слово this находит применение в функциях-конструкторах, используемых для создания объектов, так как оно позволяет, универсальным образом, работать со множеством объектов, создаваемых с помощью такой функции. В JavaScript есть и стандартные функции-конструкторы, с помощью которых, например, можно создавать объекты типа Number или String . Подобные функции, определяемые программистом самостоятельно, позволяют ему создавать объекты, состав свойств и методов которых задаётся им самим.

Как вы уже поняли, мне нравятся собаки, поэтому опишем функцию-конструктор для создания объектов типа Dog , содержащих некоторые свойства и методы.


Когда функцию-конструктор вызывают с использованием ключевого слова new , this в ней указывает на новый объект, который, с помощью конструктора, снабжают свойствами и методами.

Вот как можно работать со стандартными конструкторами JavaScript.


Теперь поработаем с только что созданной функцией-конструктором Dog .


Вот ещё один пример использования функций-конструкторов.

О важности ключевого слова new

При вызове функции-конструктора с использованием ключевого слова new ключевое слово this указывает на новый объект, который, после некоторой работы над ним, будет возвращён из этой функции. Ключевое слово this в данной ситуации весьма важно. Почему? Всё дело в том, что с его помощью можно, используя единственную функцию-конструктор, создавать множество однотипных объектов.

Это позволяет нам масштабировать приложение и сокращать дублирование кода. Для того чтобы понять важность этого механизма, подумайте о том, как устроены учётные записи в социальных сетях. Каждая учётная запись может представлять собой экземпляр объекта, создаваемый с помощью функции-конструктора Friend . Каждый такой объект можно заполнять уникальными данными о пользователе. Рассмотрим следующий код.

Итоги

На самом деле, особенности использования ключевого слова this в JavaScript не ограничиваются вышеописанными примерами. Так, в череду этих примеров можно было бы включить использование функций call , apply и bind . Так как материал этот рассчитан на начинающих и ориентирован на разъяснение основ, мы их здесь не касаемся. Однако если сейчас у вас сформировалось начальное понимание this , то и с этими методами вы вполне сможете разобраться. Главное — помните о том, что если что-то с первого раза понять не удаётся, не прекращайте учиться, практикуйтесь, читайте материалы по интересующей вас теме. В одном из них вам обязательно попадётся нечто такое (какая-то удачная фраза, например), что поможет понять то, что раньше понять не удавалось.

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