Модификаторы в java

Глубоко задумался

Говоря об ООП мы подразумеваем три базовых понятия (класс, объект, экземпляр класса) и три свойства (инкапсуляция, наследование, полиморфизм).

Как реализуется инкапсуляция в java? Как модификаторы доступа java позволяют управлять наследованием и полиморфизмом? Какие еще модификаторы в java существуют? Зачем они нужны и как их использовать?

Именно этим вопросам (и не только) посвящена данная статья.

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

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

Можно представить класс в java как некий “черный ящик” с выведенной наружу консолью управления и лотками для результатов работы. Как именно этот ящик организован внутри, т.е. какая его часть будет доступна извне (из других блоков программы), какие его блоки можно взять для создания наследников определяет механизм разграничения доступа. Данный механизм в java, как и в других объектно-ориентированных языках, реализуется через модификаторы доступа (access modifiers).

Модификатор доступа в java прописывается непосредственно для каждого метода или поля данных отдельно, в отличие от некоторых других языков (Паскаль, С++), где элементы с различными модификаторами разнесены по соответствующим секциям.

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

  • частный (private)
  • модификатор по умолчанию (default, package visible)
  • защищенный (protected)
  • общий (public)

Модификатор private

Самый высокий уровень закрытости элементов класса в java обеспечивает модификатор java private (частный).

Поля и методы private доступны только внутри класса. Это означает, что получать значения и присваивать значения полям и производить вызовы методов можно только из его методов, либо методов вложенных классов (inner class).

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

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

Модификатор private может быть использован в java для описания внутренних частных классов (private inner class). Такие классы описываются внутри других классов, соответственно создать экземпляр такого класса можно только в методе содержащего его класса.

Конструктор класса может быть частным и объявлен как private. Экземпляр класса с таким конструктором создать извне невозможно – только с помощью методов самого класса.

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

Например:

Создание экземпляра:

Модификатор protected

Следующий по уровню закрытости элементов– модификатор доступа java protected (защищённый). По сути это аналог private с возможностью передачи поля или метода классам-наследникам.

Если класс-наследник в java будет содержать описание поля или метода родителя (помеченного модификатором protected) с тем же типом данных, именем и набором параметров, то компилятор java поймет, что в классе-наследнике переопределен (override) метод родителя. При вызове такого метода для экземпляра класса-наследника будет вызван его метод, а не родительский.

Обычно такие методы в дочерних классах помечаются аннотацией @Override. Данная аннотация указывает компилятору (и/или среде разработки) что мы собираемся переопределить метод родителя. При сборке проекта, если была допущена ошибка в описании переопределяемого метода, компилятор сообщит, что переопределяемый метод не найден в родительском классе.

Пометка @Оverride не обязательна, но очень удобна, т.к. позволяет избежать ошибок при построении иерархии классов если, например, была допущена ошибка в имени метода, количестве или типе параметров, возвращаемом значении.

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

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

Именно благодаря смене модификатора метода getName в наследниках появляется возможность использовать его.

Модификатор protected можно использовать и для классов. Как и в случае с private class, защищенные классы (protected class) являются внутренними (inner class), но в отличие от private передаются в классы-наследники и могут быть использованы в них.

Модификатор default

Если модификатор доступа не указан явно java подразумевает доступ по умолчанию (default или package visible) и такой метод (или поле данных) будут видны любому классу внутри пакета.

Если класс будет являться наследником, но при этом его описание будет находиться в другом пакете, то в отличие от protected, элементы default это приведет к ошибке – в другом пакете такие поля просто не будут видны.

Если говорить о методах описываемых в интерфейсах (interface), то модификатор default играет более важную роль.

В java интерфейс только анонсирует предоставляемые классом методы и не содержит их реализацию. Соответственно все методы должны быть реализованы в классе, который является реализацией данного интерфейса (class implements interface).

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

Модификатор public

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

Данный модификатор доступа помечает элементы класса, с помощью которых он взаимодействует с “внешним миром”. Это те поля и методы, для которых и создается класс, которые реализуют его функционал с точки зрения его использования.

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

Модификатор public может быть применен к классу целиком, что делает класс общедоступным (public class). Зачем это необходимо рассмотрим ниже.

Правила контроля доступа и наследования

При многих схожих чертах инкапсуляция в java несколько отличается от классических как например в C++ или Object Pascal.

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

В java классы и интерфейсы объединяются в пакеты (package). Каждый пакет может содержать произвольное количество классов (интерфейсов), и только часть из них экспортировать во “внешний мир”.

Классов в пакете может быть сколько угодно и они могут размещаться в   нескольких файлах “.java”.

В каждом файле может быть описано множество классов и/или интерфейсов, но в каждом отдельном фале может быть не более одного общедоступного класса (public class) или интерфейса (public interface).

Имя public класса (или интерфейса) должно совпадать с именем файла, в котором он находиться (ну или наоборот – вопрос первенства имени определяет разработчик).

Чтобы определить к какому пакету относятся классы, содержащиеся в файле, в начале файла с помощью команды package указывается имя пакета. Как правило при организации java проекта, все файлы одного пакета физически помещают в одну директорию (папку). Имя этой папки должно совпадать с именем пакета.

Схематично структуру проекта с разделением по пакетам и  видимостью классов можно представить так:

package first; package second; package third;
import second.*;
import third.*;
import third.*;
public class FA { class FB { public class SA { class SB { public class TA { class TB {
Доступны FA, FB, SA, TA Доступны SA, SB, TA Доступны TA, TB

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

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

Пакет целиком или отдельные его классы необходимо импортировать (import) для их дальнейшего использования.

Классы private пакета (или без указания т.к.  private – модификатор доступа по умолчанию) являются локальными (package private) и доступны только внутри содержащего их пакета. Импортировать эти классы и использовать во внешних пакетах не получиться.

Схематично доступность элементов класса, описанных с разными модификаторами можно показать так (зеленым показана доступность поля):

Исходный код Другой класс тот же пакет Класс-наследник тот же пакет Класс-наследник другой пакет Другой класс другой пакет
package someguy; package someguy; package someguy; package badguy; package badguy;
class HasForce { class Humans { class Jedi extends HasForce { class Sith extends HasForce { class Hutts {
private int privV;
int defV;
protected int protV;
public int pubV;

Модификаторы, используемые не для доступа

Кроме модификаторов доступа (java access modifiers) в java существуют модификаторы, управляющие

  • размещением объектов памяти
  • созданием объектов
  • формированием иерархии классов
  • выполнением кода перед непосредственно созданием объектов
  • доступом к объектам в процессе выполнения
  • интерпретацией хранимых значений

Модификатор static

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

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

Модификатор static позволяет определять статические объекты которые создаются автоматически при запуске программы.

Классы static

Статическое описание имеет смысл только для внутренних классов (inner class) которые описываются внутри других классов. Статические внутренние классы позволяют создавать свой экземпляр без необходимости в создании экземпляра родителя.

Создание экземпляров:

Здесь при создании экземпляра luke используется статический внутренний класс Jedi, который можно использовать уже на этапе компиляции. Модификатор static указывает, что конструктор класса Jedi и все его статические методы, а также все статические объекты к которым они обращаются, должны быть проинициализированы. Это позволяет порождать его экземпляры без создания экземпляра внешнего класса (outer class) ForceOwner.

В отличие от Jedi класс Sith не статический и для создания его экземпляра сначала необходимо создать экземпляр ForceOwner. Таким образом только конкретный экземпляр ForceOwner может породить экземпляр Sith:

Методы static

Модификатор static указанный перед методом класса позволяет создавать статические методы.

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

Одним из примеров статических методов могут служить функции пакета Math, используемые для математических вычислений. Все его общедоступные функции – статические методы класса Math:

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

Импортировать можно как функции и поля по отдельности, так и все что пакет предлагает оптом:

Еще один важный пример публичного статического метода – точка начала выполнения программы – public static main:

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

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

Переменные static

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

Статическое поле класса еще на этапе компиляции связывается с ячейкой памяти, что позволяет обращаться к таким полям без создания экземпляра.

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

Статическое поле может хранить какую-либо важную для программы константу (например Math.PI). Имена таких констант обычно записываются прописными буквами.

Константы из пакетов, как и статические методы можно импортировать статически (import static).

Блоки static

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

Для этих целей инкапсуляция в java позволяет создавать статические блоки инициализации.

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

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

Статический блок кода работает только один раз – при первом обращении к классу.

Рассмотрим следующий пример.

Класс содержит статический блок кода и статическое поле count. Рассмотрим вариант его использования:

Данный фрагмент кода выведет следующее

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

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

Если описать Startrooper внутри другого класса как внутренний (inner class), необходимо будет указать что он статический (static class Startrooper).

Модификатор final

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

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

Класс final

Применение модификатора final к классу указывает, что он завершает ветку иерархии. У него не может быть наследников.

Обычно классы final самодостаточные и создание наследников для них не имеет смысла. Например, классы обслуживающие типы данных такие как Integer, Double, String.

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

Модификаторы final и abstract являются взаимоисключающими, соответственно они не могут применятся одновременно.

Методы final

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

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

Переменные final

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

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

Пример описания человека:

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

Модификатор abstract

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

Например, в иерархии компонентов JavaFX есть класс TextInputControl который является базовым для многих компонентов текстового ввода. Он является родителем TextField, TextArea и инкапсулирует основные поля и методы для редактирования текстовых данных, однако его экземпляр создать невозможно.

Такой класс называют абстрактным (abstract) а методы которые он анонсирует, но не реализует – класс абстрактными.

Класс abstract

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

Модификатор abstract может быть использован как предохранитель для указания того, что этот класс является общей “заготовкой” для порождения классов наследников и создавать его экземпляры не имеет смысла.

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

Метод abstract

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

Модификатор synchronized

Как и все современные языки программирования java позволяет использовать параллельные вычисления и создавать многопоточные приложения (multithreading).

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

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

Чтобы решить подобные коллизии в java есть механизмы синхронизации потоков.

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

Модификатор transient

Сериализация в java (serialization) –это механизм сохранения экземпляра класса в виде последовательности байт. Под текущим состоянием подразумеваются в том числе и значения полей данных в текущий момент времени.

Процесс восстановления объекта из сохраненного состояния в исходное называется десериализацией (deserialization).

Поля класса помеченные модификатором transient не подвергаются сериализации, т.е. не сохраняются. При десериализации данные в такие поля не загружаются и их значения не изменяются.

Модификатор volatile

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

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

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

Еще один важный момент: использование volatile не блокирует выполнение потока как synchronized.

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

Модификатор strictfp (реализация IEEE 754, floating-point)

При работе с числами с плавающей точкой (floating-point arithmetic) возникает вопрос о точности вычислений при использвании типов float и double.

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

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

Для обеспечения предсказуемости работы одного и того же программного кода на различных платформах был разработан стандарт IEEE 754-2008, IEEE Standard for Binary Floating-Point Arithmetic (IEEE стандарт для двоичной арифметики с плавающей точкой) регламентирует вычисления с плавающей точкой и преобразование чисел.

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

В некоторых областях вычислений это весьма критично. Например, в распределенных финансовых системах.

Модификатор strictfp ограничивает точность floating-point вычислений в соответствии со стандартом IEEE 754.

Если модификатор применен в описании класса или интерфейса, то все операции на числах с плавающей точкой (floating-point) будут соответствовать стандарту IEEE 754.

Модификатор так же может быть применен и к отдельным методам.

Данный модификатор считается устаревшим начиная с Java 17 (14 September 2021).

Шпаргалка для модификаторов доступа переменной

 

 

Понравилась статья? Поделиться с друзьями:
Комментарии : 2
  1. Andrew Dev

    Hello would you mind letting me know which web host you’re utilizing?
    I’ve loaded your blog in 3 completely different internet browsers
    and I must say this blog loads a lot quicker then most. Can you recommend
    a good internet hosting provider at a fair price? Kudos, I
    appreciate it!

  2. Marie Young

    Wow! In the end I got a web site from where I be able to in fact obtain useful data
    concerning my study and knowledge.

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: