bannerbannerbanner
Объектно-ориентированное программирование на Java. Платформа Java SE

Тимур Машнин
Объектно-ориентированное программирование на Java. Платформа Java SE

Полная версия

Инкапсуляция. Объекты и классы


Давайте посмотрим на вычислительные возможности калькулятора.

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



Запомнить значения можно с помощью переменных.

И затем мы можем вычислять новые значения с помощью методов.

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

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

Когда мы пишем программу для моделирования этого калькулятора, и мы определяем для него переменные и методы, мы поместим, с одной стороны, все переменные вместе, а с другой стороны – все методы вместе.

Значения всех переменных в конкретный момент времени будут составлять состояние калькулятора.

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

Наша модель будет меняться от одного состояния в другое со временем.



При этом состояние будет определяться значениями переменных.

А методы будут отвечать за изменение состояния.

На самом деле, определение переменных и методов – это общий способ моделирования объектов.

Эти объекты могут соответствовать физическим объектам, например, калькулятору.

Или эти объекты могут быть концептуальными, когда ваш код должен моделировать что-то новое.

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

Представьте себе автомобиль, который моделируется в программе, которую вы пишете для игры.

Состоянием этого объекта может быть местоположение, цвет, включены ли фары или нет.

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

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

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

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

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

И поведение, то, что может делать наш объект, представлено методами.

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

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

Поэтому мы можем сказать, что такие объекты принадлежат одному и тому же классу.

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

Рассмотрим пример с различными автомобилями, которые представлены различными объектами.

Все эти объекты принадлежат классу автомобилей Car, который имеет ряд атрибутов или переменных, или полей и ряд методов.

Давайте посмотрим на возможное определение, как мы можем записать этот класс на Java.



Здесь вы можете увидеть определение класса Car.

Вы можете увидеть зарезервированное ключевое слово class.

Затем имя, которое мы хотим дать классу.

Обратите внимание, что мы пишем его с заглавной буквы.

Затем мы указываем переменные с соответствующим типом.

Наконец, мы определяем методы, которые мы хотим дать всем объектам этого класса.

Но как только мы определили класс, как сконструировать объект для этого класса?

Для этого у нас есть конструкторы.

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



И они имеют имя класса.

Обратите внимание, что здесь не указан тип возвращаемого результата.

Используя конструкторы, мы можем создавать разные объекты класса.



Заметьте, что может быть не один, а несколько конструкторов.

Эти конструкторы отличаются списком параметров.

Здесь вы можете увидеть несколько возможных конструкторов для класса Car.

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

Здесь мы видим несколько вызовов конструкторов, определенных ранее.

Посмотрите на объявление.

Сначала мы определяем объект с именем и обратите внимание, что классы работают как типы.

Сначала мы указываем Car, чтобы указать, что объект имеет тип или класс Car.

Затем знак равенства, зарезервированное ключевое слово new и вызов конструктора.

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

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

Мы можем создать много объектов одного класса, каждый из которых будет со своим собственным состоянием.

Классы и типы


Классы – это шаблоны, из которых мы строим объекты.

И все объекты имеют одинаковую структуру, определенную классом.

Давайте сравним класс, который мы определили, со встроенным Java типом.

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



И, с другой стороны, у нас есть целые числа типа «int».

И для этих целых чисел у нас есть ряд определенных операций или методов, таких как «сложение» или «умножение».

Давайте сосредоточимся на методах.

В обоих случаях методы связаны с объектами в классе или значениями данного типа.

Таким образом, классы похожи на типы, и объекты похожи на сложные значения.

Фактически, вы можете рассматривать классы как типы.

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

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

Действительно, мы использовали строки так же как целые числа, для определения методов и переменных.



И String- это класс, а «int» – это примитивный тип данных.

Здесь мы видим объявление переменной целого числа и переменной строки.

Иногда мы говорим о «ссылках», в случае объектов.

В нижней части мы видим объявление метода со String и «int» в качестве параметров.

Таким образом, вы можете рассматривать классы как типы – типы, определенных вами в соответствии с вашими потребностями.

На самом деле для каждого примитивного типа существует соответствующий класс, называемый «классом-оболочкой».

Например, у нас есть тип «int» и класс «Integer».



И этот класс Integer является классом-оболочкой.

Объект класса «Integer» содержит поле с числом «int» и метод, который возвращает число «int», сохраненное в этом объекте.

Кроме того, там есть другие поля и методы, которые используются для разных целей.

Как вы можете видеть, для преобразования числа «int» в объект «Integer», мы можем использовать конструктор «Integer».

И для преобразования объекта Integer в значение «int», мы используем метод «intValue» класса «Integer».

Представление просто «int» в компьютере намного эффективнее, чем соответствующего объекта, так как существует много вещей, которые нужно хранить в объекте.

Класс – это не просто тип.

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

Это можно понимать, как составное значение – значение с несколькими компонентами – например, тремя целыми числами.

Поэтому, классы – это хороший способ собрать несколько значений вместе в полях объекта.

И эти компоненты могут быть нескольких типов или классов.

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

Представьте, что вы определили класс «Двигатель» с набором полей, которые определяют состояние двигателя и набором методов, которые определяют то, что вы можете делать с двигателем.



У нас может быть объект класса «Двигатель» как атрибут или поле класса «Автомобиль».

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

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

Но концепция объекта класса еще богаче.

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

 

И у нас есть переменные, которые могут хранить эти значения.

Но значение само по себе не имеет состояния, и сам по себе тип данных не имеет состояния.

Напротив, объект имеет состояние.

Так как он имеет внутри переменные, и он может запоминать значения.

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

Но, кроме того, объекты имеют состояние.

Когда мы создаем новый объект – и мы делаем это с помощью ключевого слова «new» и конструктором – строки здесь являются исключением – и мы резервируем пространство в памяти для хранения значений полей.

После создания мы можем ссылаться на этот объект, создавая ссылку с именем.

Переменная и именем объекта будет хранить ссылку на объект.

Но в этих объяснениях мы не будем акцентировать внимание на идеи ссылки или указателя, поскольку Java как-то не поддерживает эту идею.



Другие языки программирования делают это.

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

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

Когда ссылка указывает на отсутствие объекта, мы говорим, что его содержимое равно нулю.

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

Ответ заключается в использовании точечной нотации.

Вы хотите ссылаться на поле «n1» объекта «t».



Вы пишете «t.n1.»

И для методов мы делаем что-то подобное.

Чтобы вызвать метод «get1 ()» для объекта «t», мы пишем «t. get1 ()».

Область видимости


Классы имеют двойную цель.

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

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

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

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

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



Представьте себе, что в нашей модели автомобиля у нас есть поле gas, которое служит индикатором оставшегося топлива в машине.

Представьте, что эта переменная должна содержать значение от 0 до 100, 0 означает пустой бак, а 100 – полный.

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

Мы также можем заполнить бак на АЗС, и в этом случае переменная gas увеличивается.

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

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

Кто-то может даже изменить переменную на отрицательное число, что не имеет смысла.

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



Давайте посмотрим на эти два модификатора доступа, public и private.

На данный момент мы будем использовать их только для переменных и методов класса.

Здесь мы пишем private до объявления переменной gas.

Это означает, что мы можем получить доступ к ней только в классе, а не вне класса.

Два метода, move и fill, определяются как public, и поэтому могут быть вызваны вне класса.

Это типичная ситуация, чтобы иметь приватные переменные и публичные методы.

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



Здесь мы видим метод check, который вызывается из move и fill, но нам не нужно вызывать этот метод вне класса.

Наконец, мы также ставим ключевое слово public перед классом.

Его смысл станет понятным позже.

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

Доступ к переменным имеют только методы.

Здесь мы разделили понятия инкапсуляции и сокрытие информации.

Хотя для некоторых эти две концепции идут вместе, то есть инкапсуляция всегда подразумевает сокрытие информации.

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

Но мы должны запрограммировать это явно с помощью ключевых слов «private» и «public».

Всегда рекомендуется делать переменные приватными.

А затем определять публичные методы для установки значений переменных и получения значений переменных.



Как правило, название этих двух типов методов соответствует одному и тому же шаблону:

Как правило, имена этих методов начинаются со слова «set» и начинаются со слова «get».

Поэтому эти методы иногда называют сеттеры и геттеры.

Заметим, что в методе setGas мы имеем параметр g, который присваивается полю gas.

Иногда, мы хотим назвать параметр setGas тем же именем, что и переменную экземпляра.

И с этим не возникает никаких проблем.



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

Это означает, что это имя относится к полю класса.

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



Эти переменные называются переменными класса, а не переменными экземпляра класса, и они объявляются с помощью ключевого слова «static».

Эти переменные не создаются для каждого созданного объекта класса.

Они создаются только один раз для всех объектов класса.

И если мы изменим это значение, оно будет изменено для всех объектов.

Если мы не хотим, чтобы эта переменная менялась,

Мы можем сделать ее константой, добавив ключевое слово «final».



Мы можем также сделать это и для переменных экземпляра.

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

Как показано здесь.

Значения финальных переменных могут быть установлены только один раз.

Таким образом, теперь у нас есть разные виды переменных.

С одной стороны, у нас есть локальные переменные.

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

Каждый объект может иметь свое значение, хранящееся в этой переменной.

Мы можем использовать ключевое слово «this» для обозначения этих переменных.

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

Они объявляются с ключевым словом «static».

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

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

И если вы хотите сделать переменную экземпляра или переменную класса неизменной, вы добавляете ключевое слово «final».

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


Рассмотрим две машины, принадлежащие к одному классу.



У них есть общие методы и поля, но в тоже время есть отличающиеся особенности.

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

Давайте посмотрим, как мы это делаем на Java.

Представьте, что у нас есть класс Car с этими полями и методами.



В частности, есть приватное поле количество пассажиров, noPass, которое содержит количество пассажиров в данный момент времени.

enter и exit- это методы, которые изменяют это число пассажиров.

Другой класс грузовиков имеет переменную загрузки, которая может быть изменена с помощью методов load и unload.

Имейте в виду, что не стоит называть переменную и метод одним и тем же именем.

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

и назад.

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

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



Затем мы можем определить классы, car и truck, которые наследуют поля и методы от этого общего для них класса.

Vehicle будет называться суперклассом классов car и truck, и классы car и truck являются подклассами класса Vehicle.

Теперь мы можем определить класс car, расширив класс Vehicle, и добавить дополнительные поля и методы, которые может иметь легковой автомобиль.



А для грузовых автомобилей мы делаем то же самое: расширяем класс Vehicle такими полями и методами, которые необходимы.

Все остальные поля и методы унаследованы от класса Vehicle.

Обратите внимание, что мы не раскрыли тело конструктора.

Это требует дальнейшего объяснения и новых концепций.

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

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

Таким образом, мы знаем, что один класс может расширить другой класс.

Например, если класс B расширяет класс A, это означает, что он наследует его поля и методы.

И это можно сделать многократно.

То есть класс B может быть расширен, например, классом C.

Теперь мы хотим проанализировать вопрос о том, как определить конструктор класса A, который расширяет другой класс.

В нашем определении класса vehicle и класса car, где класс car расширяет класс vehicle, мы определяем конструктор для класса vehicle, который инициализирует приватное поле color.



И с этим не никаких проблем.

Но как мы можем определить тело конструктора car, с учетом двух аргументов, целого числа для количества пассажиров и строки для цвета?

Класс car наследует все методы от класса vehicle – перемещение вперед и назад, и все его поля, в данном случае, только color.

Но поле color является приватным полем и не может быть доступно извне класса vehicle.

Это относится также и к подклассам, и это очень важно.

Поэтому неправильно присваивать значение «с» полю color в классе car.

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

 

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

Теперь, если мы хотим вызвать конструктор суперкласса, мы используем ключевое слово super.



Здесь вы это видите.

super (c) – вызов конструктора vehicle (c).

Таким образом, мы сможем инициализировать поле color из подкласса.

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

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

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



Здесь мы видим другой пример.



У нас есть класс A с подклассом B, а класс B с подклассом C.

Диаграмма справа от вас показывает отношения наследования.

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

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

В классе C конструктор без аргументов сначала вызывает конструктор его суперкласса B, а затем печатает строку C точка.

Теперь, что происходит, когда мы создаем новый объект класса C?

Конструктор C вызывает конструктор B, который в свою очередь, вызывает конструктор А.

Таким образом, печатается: A, пробел, B, пробел, C точка.

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

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


Давайте посмотрим снова на эту иерархию классов.



Легковой автомобиль и грузовик являются подклассами или производными классами класса vehicle.

Вопрос в том, если ли у нас есть объект класса car, мы можем использовать его там, где должны быть объекты класса vehicle?

Например, в переменной vehicle?



И наоборот, можем ли мы поместить объекты суперкласса там, где должны быть объекты подкласса?

И если да, то при каких обстоятельствах?

Мы говорим о кастинге или приведении при преобразовании объекта из одного класса к другому связанному классу.

Представьте себе, что у нас есть переменная vehicle, которая хранит объект vehicle, и переменная car, с сохраненным в нем объектом car.



Можем ли мы присвоить объект car переменной vehicle и наоборот?

Мы говорим о приведение к базовому типу при преобразовании объекта из класса в суперкласс.

И переход от подкласса к суперклассу всегда возможен.

Объекты подкласса наследуют все от суперкласса.

Поэтому все, что вы хотите сделать с переменной суперкласса, применимо к объекту подкласса.

Чтобы привести к базовому типу объект, вы можете указать суперкласс в круглых скобках, как вы здесь видите.

Но вы также можете не делать это, как вы видите в последней строке.

Мы говорим о понижающем приведении при конвертации объекта от класса к его подклассу.

Теперь мы хотим заставить vehicle стать car.

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

В этом примере мы объявляем переменную типа vehicle, но храним в ней car.

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

Вы должны быть очень осторожны при кастинге вверх и вниз.

Мы объявляем переменную v, и мы храним в ней car.



Мы можем это сделать, поскольку car является vehicle.

Однако вы не можете привести v в переменную truck.

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

Вы не можете превратить car в truck или truck в car.



У них разные поля и методы.

Преобразование применимо не только для классов.

Это также возможно с примитивными типами и между примитивными типами.



Мы видели несколько примеров со строками и целыми числами.

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

При кастинге вверх мы не теряем информацию о числовом значении.

Поэтому мы можем делать это преобразование неявно.

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

При преобразовании double в int мы получаем усеченное целочисленное значение, поэтому это преобразование нужно указывать явно.

1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27 
Рейтинг@Mail.ru