Модификаторы объявления класса
Поможем в ✍️ написании учебной работы
Поможем с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой

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

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

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

· final. Класс, определенный как final, не допускает наследования.

· strict floating point. Присутствие в объявлении класса модификатора stictfp означает, что операции с плавающей запятой, предусмотренные методами-членами класса, должны выполняться точно и единообразно всеми виртуальными машинами Java.

Очевидно, что в объявлении класса не могут использоваться одновременно модификаторы final и abstract. Объявление способно содержать несколько модификаторов, порядок их указания несущественен. Большинством инструментальных средств разработки Java выдвигается требование, чтобы объявление класса с модификатором public сохранялось в файле с тем же именем, которое присвоено классу, отсюда следует, что файл не может содержать более одного объявления класса, помеченного как public (количество не публичных классов может быть произвольным).

Пакеты

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

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

· Способствуют созданию пространств имён, позволяющих избежать конфликтов идентификаторов, относящихся к разным типам. (В интерфейсах и классах, принадлежащих одному пакету, для обозначения public -членов могут быть использованные некоторые популярные идентификаторы – list , component – которые обладают строго определённым смыслом в контексте текущего пакета, но способны вступать в конфликт с одноимёнными членами других пакетов.)

· Обеспечивают дополнительные средства защиты элементов кода. (Фрагменты кода внутри пакета могут взаимодействовать, используя права доступа, которыми не обладает любой внешний код.)

Каждый исходный файл, в котором размещены объявления классов и интерфейсов, относящихся к пакету attr, должен содержать специальное объявление:

package attr ;

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

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

Об имени типа, в которое включён префикс названия пакета, отделённый символом точки, говорят как о полном имени типа (например, полным именем класса String является java.lang.String). При написании кода, которому необходимо обращаться к членам определённого пакета, возможно использование двух подходов. Один из них состоит в употреблении полного имени нужного типа. Он удобен, если вам нужно только несколько элементов пакета, но если учесть, что имена пакетов бывают достаточно длинными, неоднократное задание полного имени типа может оказаться утомительным занятием. Другой подход связан с импортом пакета или отдельной его части. Если вам необходимы средства пакета attr , нужно поместить в верхней части текста с исходным кодом (после объявления package , если таковое имеется, но перед любыми другими строками) следующую инструкцию импорта:

import attr .*;

Теперь для ссылки на типы, принадлежащие пакету attr, можно использовать простые имена, например, Attributed. Команду импорта, в которой используется символ *, называют объявлением импорта по требованию ( import on demand ). Можно воспользоваться также инструкцией импорта единственного типа ( single type import ):

import attr . Attributed ;

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

Понятие имени.

Имена задаются посредством идентификаторов и указывают на компоненты программы (типы, переменные, поля, методы и т.д.). Задача управления именами компонентов программы решается посредством двух механизмов. Во-первых, единое пространство имён (namespace) делится на категории, отвечающие различным разновидностям компонентов. Во-вторых, имена, видимые в одной части программы, «скрываются» от других посредством соответствующих контекстов. Так, например, механизм деления пространства имён позволяет использовать один и тот же идентификатор для поля и метода класса, а средства контекстного сокрытия дают возможность пользоваться одним идентификатором для обозначения переменных-счётчиков всех циклов for. Существует шесть различных пространств имён:

· Пакеты

· Типы

· Поля

· Методы

· Локальные переменные и параметры

· Метки

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

Пример 2 (душераздирающий, но синтаксически правильный код)

package Reuse;

class Reuse {

Reuse Reuse (Reuse Reuse) {

       Reuse:

       for(;;) {

           if (Reuse.Reuse(Reuse)== Reuse)

                break Reuse ;

       }

       return Reuse ;

}

}

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

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

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

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

{

int a = 0;

{

     int a = 2; // неверно, переменная уже объявлена

     // …

}

}

Впрочем, ничто не запрещает создавать в пределах блока различные (не вложенные) циклы for или применять не вложенные блоки с объявлениями одноимённых переменных.

Примеры именования

· Пакеты (java.lang, com.magic.attr)

· Типы (Student, ArrayIndexOutOfBoundException, Runnable)

· Поля (value, enabled, distanceFromShop)

· Методы (getValue, setValue, toString, length)

· Поля - константы (PI, SIZE_MIN, SIZE_MAX)

Понятие модуля компиляции

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

· Объявления пакета (package MyPackage;)

· import-выражений (import java.lang.Double;

import java.lang.*;)

· Объявлений верхнего уровня – классов, интерфейсов.

Поля

Переменные, объявленные в классе, называют полями (fields). Примером поля может служить переменная name класса Body , рассмотренного выше. Объявление поля состоит из наименования типа, за которым следует идентификатор переменной и необязательная конструкция инициализации, позволяющая присвоить переменной некое исходное значение. Каждый объект класса Body обладает собственными копиями трех полей: типа long для хранения уникального номера, позволяющего различить объект среди ему подобных, типа String , содержащего ссылку на строку имени объекта, и типа Body , ссылающегося на другой объект того же типа, который представляет небесное светило, «центральное» по отношению к текущему. Объект (экземпляр) класса обладает «личными» копиями полей, т.е. собственным – в общем случае, уникальным – состоянием. Поля объекта принято называть переменными экземпляра. Например, изменение содержимого поля orbits , принадлежащего одному из объектов Body, не воздействует на одноименные поля других объектов того же типа.

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

· Модификаторы доступа - будут рассмотрены далее.

· static

· final

· transient – относится к проблеме сериализации (представления объекта в виде последовательности байтов данных) и будет рассмотрен далее.

· volatile – связан с вопросами синхронизации потоков вычислений и управления памятью, будет рассмотрен далее.

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

Поле может быть инициализировано в конструкции объявления с помощью оператора присваивания и значения соответствующего типа. В примере класса Body поле nextID инициализируется нулем. В качестве выражения инициализации допускается применять не только константы, но и имена других полей, конструкции вызова методов или более сложные выражения, состоящие из всех названных элементов вместе. Единственное требование состоит в том, что тип выражения инициализации должен совпадать с типом поля; кроме того, если в выражении используется вызов метода, тот не должен генерировать объявляемые исключения, поскольку в данном случае их «некому» будет отловить и обработать. Ниже приведены примеры допустимых выражений инициализации.

double zero = 0.0;

double zeroCopy = zero;

double rootTwo = Math.sqrt(2);

Если поле явно не инициализировано, ему присваивается значение соответствующего типа, принятое по умолчанию:

Тип                                     Значение по умолчанию

boolean                               false

char                                     ‘\u0000’

byte,short,int,long               0

float                                     +0.0f

double                                 +0.0

ссылка на объект                null

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

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

В примере 1 класса Body объявлено одно статическое поле, nextID , предназначенное для хранения очередного доступного для использования порядкового номера объекта. Далее мы увидим, что в каждом вновь создаваемом объекте класса Body переменной idNum будет присваиваться текущее содержимое nextID с последующим увеличением значения nextID на единицу. Для создания объектов Body и ведения учета их порядковых номеров вполне достаточно одного экземпляра переменной nextID .

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

System.out.println(Body.nextID);

Собственно говоря, для иллюстрации возможностей доступа к статическим полям вполне подходит и поле out класса System .

Для обращения к статическому члену класса разрешено также пользоваться ссылкой на объект этого класса:

System.out.println(mercury.nextID);

Такой возможностью, однако, не следует злоупотреблять, поскольку в подобном случае создается ложное впечатление, что переменная nextID есть частный атрибут объекта mercury , а не член класса Body в целом. При обращении к статическому члену посредством ссылки на объект компилятор определяет имя соответствующего класса, а значением ссылки как таковой может быть даже null.

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

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

Если поле, помеченное как final, лишено инициализатора, его принято называть blank final. Наличие возможности подобного объявления полезно в тех случаях, когда для инициализации поля простого выражения недостаточно. Поле final должно быть инициализировано только единожды в ходе инициализации класса (в случае, если поле static) либо в процессе конструирования объекта класса (если поле не статическое). Компилятор проверяет, выполнена ли такая операция, и выдает сообщение об ошибке, если выявляет, что поле final не получило соответствующего исходного значения.

Необходимость придания полю свойства неизменности определяется его смысловым назначением в контексте всего приложения. Если известно, что свойство нельзя назвать подлинно стабильным, хотя частота его изменения и невелика, модификатор final применять не следует. Если к моменту создания объекта значение поля не определено – пусть по своему смыслу оно и абсолютно устойчиво, - поле и в этом случае не может быть помечено признаком final . Наконец, если процесс инициализации поля связан с издержками (например, ощутимыми затратами процессорного времени) и значение поля используется нечасто, рекомендуется отложить операцию инициализации до тех пор, пока содержимое поля не будет реально востребовано. Для обозначения подобного подхода обычно употребляют термин отложенная (пассивная) инициализация; разумеется, в этом случае признак final использовать запрещено.

Управление доступом

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

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

· private. Элементы класса, помеченные как private, доступны только в контексте этого класса.

· package (default). Элементы, объявленные без указания модификатора доступа, открыты для самого класса и классов, размещенных в том же пакете.

· protected. Элементы, обозначенные с помощью служебного слова protected, доступны в пределах «родного» класса, классов того же пакета и классов-наследников.

· public. Элементы, объявленные как public, открыты во всех случаях, когда доступен сам класс.

Модификаторы доступа private и protected применимы исключительно по отношению к членам классов, но не к самим классам или интерфейсам (если только последние не являются вложенными). Чтобы обеспечить возможность доступа к члену класса из некоторого блока кода, первым делом следует открыть для доступа класс, которому принадлежит член.

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

Создание объектов

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

Пример 3.

Body sun = new Body();

sun.idNum = Body.nextID++;

sun.name = “ Солнце ”;

sun.orbits = null;

Body earth = new Body();

earth.idNum = Body.nextID++;

earth.name = “ Земля ”;

earth . orbits = sun ;

Сначала объявляется переменная sun , предназначенная для хранения ссылки на объект типа Body . Подобное объявление не предполагает немедленного создания объекта – оно всего лишь определяет переменную конкретного типа. Объект, на который ссылается переменная sun , создается посредством оператора new . Создание ссылки и создание объекта – различные операции!!! Конструкция, предполагающая использование new – это наиболее употребительный способ создания объектов. Намереваясь создать объект с помощью оператора new, мы задаем имя соответствующего класса и перечисляем требуемые аргументы, если таковые предусмотрены. Исполняющая система выделяет фрагмент памяти, необходимый для размещения содержимого полей объекта, и инициализирует их значениями, принятыми по умолчанию. После этого отрабатывают блоки инициализации и конструкторы. По завершению процесса система возвращает ссылку на созданный объект.

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

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

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

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

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

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

Пример 4.

class Body {

public long idNum;

public String name = “< Без имени >”;

public Body orbits = null;

private static long nextID = 0;

Body () {

     idNum = nextID ++;

}

}

Объявление конструктора состоит из имени класса, за которым следует список (возможно, пустой) параметров, ограниченный круглыми скобками, и тело конструктора – блок выражений, заключенный в фигурные скобки.

Конструктор класса Body объявлен без параметров. Его назначение – обеспечивать уникальность значения поля idNum вновь создаваемого объекта. В исходном варианте класса небольшая ошибка, связанная, например, с неаккуратно выполненной операцией присваивания значения полю idNum либо с отсутствием инструкций приращения содержимого поля nextID , могла бы привести к тому, что несколько объектов Body получили бы один и тот же порядковый номер. Это было бы ошибкой, так как значения idNum различных объектов класса должны быть уникальными. Передав ответственность за выбор верных значений idNum самому классу, мы раз и навсегда избавляемся от подобных ошибок. Теперь конструктор класса Body – это единственный субъект, который изменяет содержимое поля nextID и нуждается в доступе к нему. Поэтому мы должны обозначить переменную nextID модификатором private , чтобы предотвратить возможность обращения к ней за пределами класса. Сделав это, мы исключим один из потенциальных источников ошибок, грозящих будущим пользователям нашего класса.

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

Пример 5.

Body sun = new Body(); // idNum = 0

sun.name = “ Солнце ”;

Body earth = new Body(); / / idNum = 1

earth.name = “ Земля ”;

earth.orbits = sun;

В процессе создания объекта с помощью оператора new конструктор класса Body вызывается после присваивания полям name и orbits предусмотренных нами выражений инициализации.

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

Пример 6.

Body (String bodyName, Body orbitsAround) {

  this();

  name = bodyName;

  orbits = orbitsAround;

}

В данном примере один конструктор класса обращается к другому посредством выражения this ()первой исполняемой инструкции в теле конструктора-инициатора. Подобное предложение называют явным вызовом конструктора. Если конструктор, к которому вы намереваетесь обратиться явно, предполагает задание аргументов, при вызове они должны быть переданы. Какой из конструкторов будет вызван – это обусловливается количеством аргументов и набором их типов. В данном случае выражение this () означает вызов конструктора без параметров, позволяющего установить значение idNum объекта и увеличить текущее содержимое статического поля nextID на единицу. Обращение к this () дает возможность избежать повторения кода инициализации idNum и изменения nextID . Теперь код, предусматривающий создание объектов, становится существенно более простым:

Пример 7.

Body sun = new Body(“ Солнце ”, null);

Body earth = new Body(“ Земля ”, sun);

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

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

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

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

· При использовании дополнительных конструкторов задача определения начальных свойств объектов упрощается (наглядный пример – конструктор класса Body с двумя параметрами).

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

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

Весьма широкое применение находят и конструкторы без параметров. Если, объявляя класс, вы не создали ни одного конструктора какой бы то ни было разновидности, компилятор автоматически включит в состав класса пустой конструктор без параметров. Подобный конструктор (его называют конструктором по умолчанию) создается только в том случае, если других конструкторов не существует, поскольку можно привести примеры классов, в которых применение конструктора без параметров нецелесообразно или вовсе невозможно. Если необходимы и конструктор без параметров, и один или несколько дополнительных конструкторов, первый должен быть создан явно. Конструктор по умолчанию получает тот же признак доступа, что и класс, для которого он создается: если объявление класса снабжено модификатором public, то и конструктор будет помечен как public.

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

Блоки инициализации

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

Пример 8.

class Body {

public long idNum;

public String name = “< Без имени >”;

public Body orbits = null;

private static long nextID = 0;

{

     idNum = nextID++;

}

Body (String bodyName, Body orbitsAround) {

     name = bodyName ;

   orbits = orbitsAround ;

 }

}

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

Статическая инициализация

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

Пример 9.

class Primes {

static int[] knownPrimes = new int[4];

static {

     knownPrimes[0] = 2;

     for (int i = 1; i< knownPrimes.length; i++)

          knownPrimes[i] = nextPrime(i);

}

// объявление nextPrime…

}

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

Методы

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

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

Пример 10.

class BodyPrint {

public static void main (String[] args) {

       Body sun = new Body(“ Солнце ”, null);

       Body earth = new Body(“ Земля ”, sun);

       System.out.println(“ Тело ” + earth.name +

                                          “ вращается вокруг тела “ +

                                           earth . orbits . name +

                                           “ и обладает номером ”+

                                            earth . idnum );

}

}

Конструкция объявления метода состоит из двух частей: заголовка метода и его тела. Заголовок в общем случае включает набор модификаторов, наименование типа возвращаемого значения, сигнатуру и предложение throws, перечисляющие классы исключений, которые могут генерироваться методом. Сигнатура метода состоит из наименования – идентификатора метода и списка (возможно, пустого) типов параметров, заключенного в круглые скобки. В объявлении любого метода должны быть указаны, по меньшей мере, тип возвращаемого значения и сигнатура – модификаторы и список throws необязательны. Тело метода представляет собой набор выражений, ограниченный фигурными скобками.

В объявлении метода применяются модификаторы следующих категорий.

· Модификаторы доступа (уже были рассмотрены ранее).

· abstract. Модификатором abstract помечаются методы, которые точно не определены в контексте текущего класса. В объявлении методов abstract отсутствует тело – оно заменятся символом точки с запятой. Предполагается, что абстрактные методы должны быть реализованы в некотором производном классе.

· static. Обсудим далее.

· final. Методы, обозначенные как final, не допускают переопределения в производных классах.

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

· native. Обсудим далее.

· strict floating point. Метод, объявленный как strictfp, гарантирует, что все предусмотренные им операции с плавающей запятой, будут выполняться точно и единообразно всеми виртуальными машинами Java. Признак strictfp, содержащийся в объявлении класса, неявно распространяется на все методы класса, манипулирующими числами с плавающей запятой.

Метод abstract не может быть одновременно помечен любым из модификаторов – static, final, synchronized, native или strictfp.

Статические методы

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

Вызов метода

Методы вызываются посредством задания ссылки на объект класса (скажем, reference ) и наименования метода со списком аргументов ( method ( arguments )). Ссылка на объект и наименование метода объекта разделяются оператором точки (.):

reference.method(arguments);

В примере класса BodyPrint (пример 10) мы обращались к методу println , используя статическую ссылку System . out и передавая в качестве аргумента объект типа String , сформированный с помощью оператора сцепления (+) из нескольких строковых операндов.

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

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

Пример BodyPrint (пример 10) иллюстрирует типичную ситуацию, связанную с проверкой состояния объекта класса. Однако более предпочтительным следует считать решение, в котором вместо прямого обращения к полям объекта в классе предусмотрен некоторый метод, который способен возвратить строковое представление состояния. Ниже представлен метод в составе класса Body , возвращающий ссылку на объект типа String , который описывает совокупность требуемых полей конкретного экземпляра Body .

Пример 11.

public String toString() {

String desc = idNum + “ (“ + name + “) “;

if (orbits != null)

     desc += “ вращается вокруг “ + orbits.toString();

return desc ;

}

Операторы + и +=, используемые в тексте метода, выполняют функции сцепления (конкатенации) строк. Сначала создаётся строка desc , содержащая номер небесного тела и его наименование. Если тело является спутником другого тела, в desc добавляется текстовое описание тела более высокого уровня, получаемое с помощью того же метода toString . Подобный рекурсивный процесс повторяется несколько раз, если иерархия небесных тел сложна (например, Луна-Земля-Солнце) – где каждое предыдущее тело служит спутником следующего,- и завершается по достижении «самого» центрального тела системы, когда поле orbits соответствующего объекта класса Body содержит значение null . Наконец, с помощью команды return строка desc возвращается в то место кода, откуда был осуществлён вызов метода.

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

System . out . println (“Тело ” + sun );

System . out . println (“Тело ” + earth );

Методы toSring объектов sun и earth будут вызваны неявно и обеспечат вывод на экран следующих результатов:

Тело 0 (Солнце)

Тело 1 (Земля) вращается вокруг 0 (Солнце)

Метод toSring заведомо присутствует в составе любого объекта, независимо от того, был ли он (метод) объявлен в соответствующем классе или нет, поскольку все классы Java наследуют класс Object , а в нём метод toSring предусмотрен заранее. Класс Object и вопросы наследования классов будут рассмотрены далее.

Дата: 2019-02-19, просмотров: 288.