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

Вариант класса Body , предусматривающий использование различных конструкторов, значительно легче применять, чем простую версию класса, в которой содержатся только поля данных, так как мы всегда уверены в том, что поле idNum получит верное значение без дополнительных воздействий из вне. Но пользователи класса Body могут обратиться к полям построенного объекта напрямую, поскольку тоже поле idNum объявлено нами как public , т.е. полностью открыто для доступа из вне. Исходя из семантики класса Body содержимое переменой idNum позволяется только «читать». Для реализации модели данных, открытых только для чтения, существуют две возможности: либо обозначить поле как final (и тогда оно приобретёт свойство «только для чтения» на весь период жизни объекта), либо каким-то образом скрыть его от постороннего воздействия. Чтобы «скрыть» поле, достаточно снабдить его модификатором private и создать новый метод, который должен выполнять функцию посредника между полем и внешним кодом:

Пример 14.

class Body {

private long idNum; // теперь уже private

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

public Body orbits = null;

private static long nextId = 0;

Body() {

     idNum = nextID++;

 }

public long getID() {

     return idNum ;

}

}

Теперь пользователь, заинтересованный в получении порядкового номера небесного тела, должен обратиться к методу getID , который возвращает требуемое значение. Для прикладной программы больше не существует способов изменения содержимого поля, оно фактически приобретает искомое свойство «только для чтения». Но возможности доступа к полю private со стороны методов класса Body остаются по-прежнему полными. Методы, управляющие доступом к внутренним полям данных класса, иногда так и называют – методами доступа (accessor methods).

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

О методах, позволяющих задавать (set) значения полей объекта и считывать (get) их, иногда говорят, что они определяют свойства (properties) объекта.

Возвращаясь к примеру класса Body , снабдим поля name и orbits модификаторами private и предоставим соответствующие методы set и get :

Пример 15.

class Body {

private long idNum;

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

private Body orbits = null;

private static long nextId = 0;

// объявления конструкторов опущены

public long getId() { return idNum; }

 public String getName() { return name; }

public void setName(String newName) {

     name = newName;

}

public Body getOrbits() { return orbits; }

public void setOrbits(Body orbitsAround) {

     orbits = orbitsAround ;

}

}

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

Теперь, когда все поля класса Body снабжены модификатором private, вернёмся к ранее высказанной мысли о том, что признак доступности данных – это атрибут класса, а не частного объекта. Предположим, что одно небесное тело способно «захватить» другое и заставить последнее изменить свою орбиту. Соответствующий метод класса Body может выглядеть так:

Пример 16.

public void capture ( Body victim ) {

// Захват жертвы

victim.orbits = this;

}

Если бы доступ к полям данных регулировался на уровне объекта, метод capture (захват), будучи вызванным в одном объекте, не смог бы обратиться к приватному полю orbits другого объекта класса Body , victim (жертва). Но поскольку возможности доступа устанавливаются на уровне класса, код метода класса способен обращаться ко всем полям всех объектов этого класса – ему достаточно иметь в своём распоряжении ссылку на нужный объект (такую, как victim в нашем примере).

This

В примере 6 (тема «конструкторы») упоминалось о том, как можно обеспечить явный вызов одного конструктора из тела другого. Для этого применялось выражение this (), размещённое в самом начале тела конструктора-инициализатора. Служебное слово this, выполняющее роль специальной объектной ссылки, можно использовать в теле нестатического метода для указания на текущий объект, которому этот метод «принадлежит». Для методов, объявленных как static, ссылки this не существует, поскольку они вызываются без указания конкретного экземпляра класса. Ссылка this часто используется и в роли носителя информации о текущем объекте, передаваемого в качестве аргумента другим методам. Предположим, что в теле метода требуется добавить текущий объект в список объектов, ожидающих выполнения определённого сервиса. Подобная операция могла бы выглядеть так:

service . add ( this );

В методе capture класса Body ссылка this позволяет задать для поля orbits объекта victim значение, указывающее на текущий объект (пример 16). Ничто не запрещает явно вводить ссылку this перед идентификатором поля или конструкцией вызова метода в коде текущего объекта. Например, строка присваивания

name = bodyName;

в теле конструктора Body с двумя параметрами (пример 6) равноценна следующей:

this . name = bodyName;

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

Пример 17.

Body (String name, Body orbits) {

  this();

  this.name = name;

  this.orbits = orbits;

}

Поля name и orbits класса Body в теле конструктора «перекрываются» одноимёнными параметрами. Чтобы получить доступ к полю name (а не к параметру name !!!) мы обязаны предпослать идентификатору name префикс this ., чтобы явно указать на то, что нас интересует именно поле. Подобное умышленное перекрытие одних идентификаторов другими можно считать вполне приемлемой практикой – правда преимущественно при использовании в контексте конструкторов и методов set.

Перегруженные методы

Объявление каждого метода включает сигнатуру – сочетание наименования метода и списка типов его параметров. Методы класса должны различаться сигнатурами - они могут обладать одним и тем же именем, но количество и/или типы их параметров в таком случае совпадать не могут. Методы класса, имеющие одно и то же имя, называют перегруженными (overloaded) (такое имя получает несколько возможных толкований). При вызове метода компилятор анализирует количество и типы аргументов и находит тот перегруженный метод, сигнатура которого в наибольшей мере отвечает ситуации. В примере 18 приведены тексты двух перегруженных методов orbitsAround класса Body , один из которых возвращает значение true в том случае, когда текущее небесное тело вращается вокруг тела, указанного посредством ссылки на объект, а второй выполняет то же самое, только в качестве параметра принимает номер объекта:

Пример 18.

public boolean orbitsAround(Body other) {

return (orbits ==other);

}

public boolean orbitsAround(long id) {

return (orbits != null && orbits.idNum == id);

}

В объявлении обоих методов указано по одному параметру, но их типы различны. Если при вызове orbitsAround в качестве аргумента вводится выражение ссылки на объект класса Body , управление предаётся первому из методов – в нём содержимое параметра other сравнивается со значением поля orbits текущего объекта. Если же в конструкции вызова задаётся значение тира long , в действие вступает второй одноимённый метод, сопоставляющий значение поля idNum текущего объекта с содержимым параметра id . Если компилятор не в состоянии найти сигнатуры метода, соответствующей конструкции вызова, он генерирует сообщение об ошибке.

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

Метод main

Способы запуска программ на выполнение в большой степени зависят от особенностей той или иной операционной системы, но в любом случае, чтобы активизировать приложение, вы обязаны указать имя некоторого класса. При запуске программы система пытается обнаружить в указанном классе метод main и передать ему управление. В объявлении метода main должны присутствовать модификаторы public и static, а также служебное слово void, а в списке параметров – единственный параметр типа String[] (ссылка на массив ссылок на объекты типа String). Метод main объявляется как public, с тем, чтобы обратиться к нему мог каждый субъект (в данном случае, виртуальная машина Java) и как static – последнее означает, что метод принадлежит самому классу и не «привязан» ни к одному из частных экземпляров этого класса. Метод main объявлен как void - это значит, что он не возвращает каких бы то ни было значений. В примере 19 приведён метод main, который выводит на экран значения переданных ему аргументов:

Пример 19.

class Echo {

public static void main(String[] args) {

      for (int i = 0; i < args.length; i++)

           System.out.print(args[i] + “ ”);

     System . out . println ();

}

}

Строковые аргументы, передаваемые методу main, играют роль параметров командной строки, которые могут быть введены пользователем при старте программы. Например, из командной строки приложение Echo может быть запущено следующем образом:

java Echo Здесь был Вася

В данном случае слово java обозначает наименование программы-интерпретатора байт-кода, Echo соответствует имени класса, подлежащего выполнению, а остальные слова – это аргументы программы. Команда java находит откомпилированный код класса Echo , загружает его в виртуальную машину Java и вызывает метод Echo . main , передавая ему полученные извне параметры командной строки, которые сохраняются в массиве args объектов типа String . Результат работы программы выглядит так:

Здесь был Вася

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

Методы native

Если в процессе реализации Java-проекта возникает необходимость в применении существующего кода, написанного на другом языке программирования или использовании низкоуровневых функций для непосредственного обращения к компьютерной аппаратуре, существует возможность прибегнуть к так называемым методам native, которые могут быть вызваны из среды приложения Java, но создаются на одном из «родных» (native) для платформы языков – как правило, С или С++. native - методы объявляются посредством модификатора native. Тело метода реализуется на другом языке и поэтому в объявлении заменяется символом точки с запятой. Ниже в качестве примера приведено объявление метода, который обращается к операционной системе за информацией об идентификационном номере процессора хост-компьютера:

public native int getCPUID ();

Единственное отличие методов native состоит в том, что они реализуются на другом языке программирования. В остальном, они подобны обычным методам, т.е. могут быть переопределены, перегружены и снабжены любыми модификаторами – final, static, synchronized, public, protected или private, кроме abstract или strictfp.

При обращении к методам native свойства переносимости и безопасности, присущие коду Java, будут утрачены. Практически невозможно использовать методы native в Java - коде, который предназначен для загрузки из Internet или выполнения на удалённых компьютерах сети (примером являются апплеты).

Методы native реализуются с помощью библиотек API, предлагаемых разработчиками виртуальных машин Java для тех или иных платформ. Одна из стандартных библиотек API , предназначенных для программистов, использующих язык С, носит название JNI – от Java Native Interface . Существуют библиотеки и для других языков. Описание подобных библиотек выходит за рамки данного курса.

Лексические основы (самостоятельное изучение)

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

Комментарии

Хотя комментарии никак не влияют на исполняемый код программы, при правильном использовании они оказываются весьма существенной частью исходного текста. Существует три разновидности комментариев: комментарии в одной строке, комментарии в нескольких строках и, наконец, комментарии для документирования. Комментарии, занимающие одну строку, начинаются с символов // и заканчиваются в конце строки. Такой стиль комментирования полезен для размещения кратких пояснений к отдельным строкам кода:

а = 42; // это комментарий

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

/*

это тоже комментарий

*/

Третья, особая форма комментариев, предназначена для сервисной программы javadoc, которая использует компоненты Java-компилятора для автоматической генерации документации по типам. Соглашение, используемое для комментариев этого вида, таково: для того, чтобы разместить перед объявлением класса, метода или переменной документирующий комментарий, нужно начать его с символов /**. Заканчивается такой комментарий точно так же, как и обычный комментарий - символами */. Программа javadoc умеет различать в документирующих комментариях некоторые специальные элементы, имена которых начинаются с символа @. Вот пример такого комментария:

/**

* Этот класс умеет делать замечательные вещи.

* @see java.applet.Applet

* @author Patrick Naughton

* @version 1. 2

*/

class CoolApplet extends Applet { /**

* У этого метода два параметра:

* @param key - это имя параметра.

* @param value - это значение параметра с именем key.

*/ void put (String key, Object value) {

Тэг @see создаёт перекрёстную ссылку, указывающую на другой документ javadoc. Тэг @author позволяет включить в комментарий сведения об авторе кода. Тэг @version служит для обозначения версии программного обеспечения. Тэг @param служит для документирования одного параметра метода. Для обозначения каждого параметра надлежит задавать отдельный тэг @param. Первое слово, следующее за тэгом, трактуется как наименование параметра, а остальная часть строки – как его описание.

Служебные слова

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

Таблица 1 - Зарезервированные слова Java

abstract assert boolean break byte
case catch char class const
continue default do double else
extends final finally float for
goto if implements import instanceof
int interface long native new
package private protected public return
short static strictfp super switch
syncronized this throw throws transient
try void volatile while  

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

Отметим, что слова const и goto зарезервированы в Java, но пока не используются. Кроме этого, в Java есть зарезервированные имена методов (см. таблицу 2). Эти методы наследуются каждым классом.

Таблица 2 - Зарезервированные имена методов Java

clone equals finalize getClass hashCode
notify notifyAll toString wait  

Идентификаторы

Идентификаторы используются для именования классов, методов и переменных. В качестве идентификатора может использоваться любая последовательность строчных и прописных букв, цифр и символов _ (подчеркивание) и $ (доллар). Идентификаторы не должны начинаться с цифры, чтобы транслятор не перепутал их с числовыми литеральными константами, которые будут описаны ниже. Java - язык, чувствительный к регистру букв. Это означает, что, к примеру, Value и VALUE - различные идентификаторы.

Литералы

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

Целые литералы

Целые числа - это тип, используемый в обычных программах наиболее часто. Любое целочисленное значение, например, 1, 2, 3, 42 - это целый литерал. В данном примере приведены десятичные числа, то есть именно те, которые мы повседневно используем вне мира компьютеров. Кроме десятичных, в качестве целых литералов могут использоваться также восьмеричные и шестнадцатеричные числа. Java распознает восьмеричные числа по стоящему впереди нулю. Нормальные десятичные числа не могут начинаться с нуля, так что использование в программе внешне допустимого числа 09 приведет к сообщению об ошибке при трансляции, поскольку 9 не входит в диапазон 0..7, допустимый для знаков восьмеричного числа. Шестнадцатеричная константа различается по стоящим впереди символам нуль-х (0х или 0Х). Диапазон значений шестнадцатеричной цифры - 0.. 15, причем в качестве цифр для значений 10.. 15 используются буквы от А до F (или от а до f). С помощью шестнадцатеричных чисел вы можете в краткой и ясной форме представить значения, ориентированные на использование в компьютере, например, написав 0xffff вместо 65535.

Целые литералы являются значениями типа int, которое в Java хранится в 32-битовом слове. Если вам требуется значение, которое по модулю больше, чем приблизительно 2 миллиарда, необходимо воспользоваться константой типа long. При этом число будет храниться в 64-битовом слове. К числам с любым из названных выше оснований вы можете приписать справа строчную или прописную букву L, указав таким образом, что данное число относится к типу long. Например, 0x7ffffffffffffffL или 9223372036854775807L - это значение, наибольшее для числа типа long.

Литералы с плавающей точкой

Числа с плавающей точкой представляют десятичные значения, у которых есть дробная часть. Их можно записывать либо в обычном, либо в экспоненциальном форматах. В обычном формате число состоит из некоторого количества десятичных цифр, стоящей после них десятичной точки, и следующих за ней десятичных цифр дробной части. Например, 2.0, 3.14159 и .6667 - это допустимые значения чисел с плавающей точкой, записанных в стандартном формате. В экспоненциальном формате после перечисленных элементов дополнительно указывается десятичный порядок. Порядок определяется положительным или отрицательным десятичным числом, следующим за символом Е или е. Примеры чисел в экспоненциальном формате: 6.022е23, 314159Е-05, 2е+100. В Java числа с плавающей точкой по умолчанию рассматриваются, как значения типа double. Если вам требуется константа типа float, справа к литералу надо приписать символ F или f. Если вы любите избыточные определения - можете добавлять к литералам типа double символ D или d. Значения используемого по умолчанию типа double хранятся в 64-битовом слове, менее точные значения типа float - в 32-битовых.

Логические литералы

У логической переменной может быть лишь два значения - true (истина) и false (ложь). Логические значения true и false не преобразуются ни в какое числовое представление. В отличие от C и C++, ключевое слово true в Java не равно 1, a false не равно 0. В Java эти значения могут присваиваться только переменным типа boolean либо использоваться в выражениях с логическими операторами.

Символьные литералы

Символы в Java - это индексы в таблице символов UNICODE. Они представляют собой 16-битовые значения, которые можно преобразовать в целые числа и к которым можно применять операторы целочисленной арифметики, например, операторы сложения и вычитания. Символьные литералы помещаются внутри пары апострофов (' '). Все видимые символы таблицы ASCII можно прямо вставлять внутрь пары апострофов: - 'a', 'z', '@'. Для символов, которые невозможно ввести непосредственно, предусмотрено несколько управляющих последовательностей (см. таблицу 3).

Таблица 3 - Управляющие последовательности символов

Управляющая последовательность Описание
\ddd Восьмеричный символ (ddd)
\uxxxx Шестнадцатиричный символ UNICODE (xxxx)
\' Апостроф
\" Кавычка
\\ Обратная косая черта
\r Возврат каретки (carriage return)
\n Перевод строки (line feed, new line)
\f Перевод страницы (form feed)
\t Горизонтальная табуляция (tab)
\b Возврат на шаг (backspace)

Строчные литералы

Строчные литералы в Java выглядят точно также, как и во многих других языках - это произвольный текст, заключенный в пару двойных кавычек (""). Хотя строчные литералы в Java реализованы весьма своеобразно (Java создает объект для каждой строки), внешне это никак не проявляется. Примеры строчных литералов: "Hello World!"; "две\строки; \ А это в кавычках\"". Все управляющие последовательности и восьмеричные / шестнадцатеричные формы записи, которые определены для символьных литералов, работают точно так же и в строках. Строчные литералы в Java должны начинаться и заканчиваться в одной и той же строке исходного кода. В этом языке, в отличие от многих других, нет управляющей последовательности для продолжения строкового литерала на новой строке.

Операторы

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

Таблица 4 - Операторы языка Java

+ += - -=
* *= / /=
| |= ^ ^=
& &= % %=
> >= < <=
! != ++ --
>> >>= << <<=
>>> >>>= && | |
== = ~ ?:
  instanceof [ ]  

Разделители

Лишь несколько групп символов, которые могут появляться в синтаксически правильной Java-программе, все еще остались неназванными. Это - простые разделители, которые влияют на внешний вид и функциональность программного кода (см. таблицу 5).

Таблица 5 - Разделители

Символы Название Для чего применяются
( ) круглые скобки Выделяют списки параметров в объявлении и вызове метода, также используются для задания приоритета операций в выражениях, выделения выражений в операторах управления выполнением программы, и в операторах приведения типов.
{ } фигурные скобки Содержат значения автоматически инициализируемых массивов, также используются для ограничения блока кода в классах, методах и локальных областях видимости.
[ ] квадратные скобки Используются в объявлениях массивов и при доступе к отдельным элементам массива.
; точка с запятой Разделяет операторы.
, запятая Разделяет идентификаторы в объявлениях переменных, также используется для связи операторов в заголовке цикла for.
. точка Отделяет имена пакетов от имен подпакетов и классов, также используется для отделения имени переменной или метода от имени переменной.

Переменные

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

Простые типы

Простые типы в Java не являются объектно-ориентированными, они аналогичны простым типам большинства традиционных языков программирования. В Java имеется восемь простых типов: - byte, short, int, long, char, float, double и boolean. Их можно разделить на четыре группы:

Целые. К ним относятся типы byte, short, int и long. Эти типы предназначены для целых чисел со знаком.

Типы с плавающей точкой - float и double. Они служат для представления чисел, имеющих дробную часть.

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

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

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

Целые числа

В языке Java понятие беззнаковых чисел отсутствует. Все числовые типы этого языка - знаковые. Отсутствие в Java беззнаковых чисел вдвое сокращает количество целых типов. В языке имеется 4 целых типа, занимающих 1, 2, 4 и 8 байтов в памяти. Для каждого типа - byte, short, int и long, есть свои естественные области применения.

Byte

Тип byte - это знаковый 8-битовый тип. Его диапазон - от -128 до 127. Он лучше всего подходит для хранения произвольного потока байтов, загружаемого из сети или из файла.

byte b;

byte с = 0х55;

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

Short

short - это знаковый 16-битовый тип. Его диапазон - от -32768 до 32767. Это, вероятно, наиболее редко используемый в Java тип, поскольку он определен, как тип, в котором старший байт стоит первым.

short s;

short t = Ox55aa;

Случилось так, что на ЭВМ различных архитектур порядок байтов в слове различается, например, старший байт в двухбайтовом целом short может храниться первым, а может и последним. Первый случай имеет место в архитектурах SPARC и Power PC, второй - для микропроцессоров Intel x86. Переносимость программ Java требует, чтобы целые значения одинаково были представлены на ЭВМ разных архитектур.

Int

Тип int служит для представления 32-битных целых чисел со знаком. Диапазон допустимых для этого типа значений - от -2147483648 до 2147483647. Чаще всего этот тип данных используется для хранения обычных целых чисел со значениями, достигающими двух миллиардов. Этот тип прекрасно подходит для использования при обработке массивов и для счетчиков. Всякий раз, когда в одном выражении фигурируют переменные типов byte, short, int и целые литералы, тип всего выражения перед завершением вычислений приводится к int.

int i;

int j = 0x55aa0000;

Long

Тип long предназначен для представления 64-битовых чисел со знаком.

long m;

long n = Ох 55 аа 000055 аа 0000;

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

Таблица 6 – Разрядность и диапазон значений целых чисел

Имя Разрядность Диапазон
long 64 -9, 223, 372, 036, 854, 775, 808.. 9, 223, 372, 036, 854, 775, 807
int 32 -2, 147, 483, 648.. 2, 147, 483, 647
short 16 -32, 768.. 32, 767
byte 8 -128.. 127

Числа с плавающей точкой

Числа с плавающей точкой, часто называемые в других языках вещественными числами, используются при вычислениях, в которых требуется использование дробной части. В Java реализован стандартный (IEEE-754) набор типов для чисел с плавающей точкой - float и double и операторов для работы с ними. Характеристики этих типов приведены в таблице 7.

Таблица 7 – Разрядность и диапазон значений чисел с плавающей точкой

Имя Разрядность Диапазон
double 64 1.7е-308.. 1. 7е+ 308
float 32 3.4е-038.. 3. 4е+ 038

Float

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

float f;

float f2 = 3. 14F;       // обратите внимание на F,

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

Double

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

double d;

double pi = 3. 14159265358979323846;

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

Приведение типов (type casting) - одно из неприятных свойств C++, тем не менее приведение типов сохранено и в языке Java. Иногда возникают ситуации, когда у вас есть величина какого-то определенного типа, а вам нужно ее присвоить переменной другого типа. Для некоторых типов это можно проделать и без приведения типа, в таких случаях говорят об автоматическом преобразовании типов. В Java автоматическое преобразование возможно только в том случае, когда точности представления чисел переменной-приемника достаточно для хранения исходного значения. Такое преобразование происходит, например, при занесении значения переменной типа byte или short в переменную типа int. Это называется расширением (widening) или повышением (promotion), поскольку тип меньшей разрядности расширяется (повышается) до большего совместимого типа. Размера типа int всегда достаточно для хранения чисел из диапазона, допустимого для типа byte, поэтому в подобных ситуациях оператора явного приведения типа не требуется. Обратное, в большинстве случаев, неверно, поэтому для занесения значения типа int в переменную типа byte необходимо использовать оператор приведения типа. Эту процедуру иногда называют сужением (narrowing), поскольку вы явно сообщаете транслятору, что величину необходимо преобразовать, чтобы она уместилась в переменную нужного вам типа. Для приведения величины к определенному типу перед ней нужно указать этот тип, заключенный в круглые скобки. В приведенном ниже фрагменте кода демонстрируется приведение типа источника (переменной типа int) к типу приемника (переменной типа byte). Если бы при такой операции целое значение выходило за границы допустимого для типа byte диапазона, оно было бы уменьшено путем деления по модулю на допустимый для byte диапазон (результат деления по модулю на число - это остаток от деления на это число).

int a = 100;

byte b = (byte) a;

Автоматическое преобразование типов в выражениях

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

byte a = 40;

byte b = 50;

byte с = 100;

int d = a* b / с;

Результат промежуточного выражения (а* b) вполне может выйти за диапазон допустимых для типа byte значений. Именно поэтому Java автоматически повышает тип каждой части выражения до типа int, так что для промежуточного результата (а* b) хватает места.

Автоматическое преобразование типа иногда может оказаться причиной неожиданных сообщений транслятора об ошибках. Например, показанный ниже код, хотя и выглядит вполне корректным, приводит к сообщению об ошибке на фазе компиляции. В нем мы пытаемся записать значение 50* 2, которое должно прекрасно уместиться в тип byte, в байтовую переменную. Но из-за автоматического преобразования типа результата в int мы получаем сообщение об ошибке от транслятора - ведь при занесении int в byte может произойти потеря точности.

byte b = 50;

b = b* 2;

Исправленный текст :

byte b = 50;

b = (byte) (b* 2);

что приводит к занесению в b правильного значения 100.

Если в выражении используются переменные типов byte, short и int, то во избежание переполнения тип всего выражения автоматически повышается до int. Если же в выражении тип хотя бы одной переменной - long, то и тип всего выражения тоже повышается до long. He забывайте, что все целые литералы, в конце которых не стоит символ L (или 1), имеют тип int.

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

class Promote {

public static void main (String[] args) { byte b = 42;

      char с = 'a';

      short s = 1024;

      int i = 50000;

      float f = 5.67f;

      double d =.1234;

      double result = (f* b) + (i/ c) - (d* s);

      System. out. println

      ((f* b)+ "+ "+ (i / с )+ " - " + (d* s));

      System. out. println ("result = "+ result);

      }

}

Подвыражение f* b - это число типа float, умноженное на число типа byte. Поэтому его тип автоматически повышается до float. Тип следующего подвыражения i / с ( int, деленный на char) повышается до int. Аналогично этому тип подвыражения d* s (double, умноженный на short) повышается до double. На следующем шаге вычислений мы имеем дело с тремя промежуточными результатами типов float, int и double. Сначала при сложении первых двух тип int повышается до float и получается результат типа float. При вычитании из него значения типа double тип результата повышается до double. Окончательный результат всего выражения - значение типа double.

Символы

Поскольку в Java для представления символов в строках используется кодировка Unicode, разрядность типа char в этом языке - 16 бит. В нем можно хранить десятки тысяч символов интернационального набора символов Unicode. Диапазон типа char - 0..65536. Unicode - это объединение десятков кодировок символов, он включает в себя латинский, греческий, арабский алфавиты, кириллицу и многие другие наборы символов.

char c;

char c2 = Oxf132;

char c3 = ' a';

char c4 = '\n';

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

int three = 3;

char one = '1';

char four = (char) (three+ one);

В результате выполнения этого кода в переменную four заносится символьное представление нужной нам цифры - '4'. Обратите внимание - тип переменной one в приведенном выше выражении повышается до типа int, так что перед занесением результата в переменную four приходится использовать оператор явного приведения типа.

Тип boolean

В языке Java имеется простой тип boolean, используемый для хранения логических значений. Переменные этого типа могут принимать всего два значения - true (истина) и false (ложь). Значения типа boolean возвращаются в качестве результата всеми операторами сравнения, например (а < b). Кроме того, boolean - это тип, требуемый всеми условными операторами управления - такими, как if, while, do.

boolean done = false;

Массивы

Массивы (arrays) – это упорядоченные наборы элементов одного типа. Элементами массива могут служить объекты простых и ссылочных типов, в том числе и ссылки на другие массивы. Массивы сами по себе являются объектами и наследуют класс Object. Объявление

int [] ia = new int [3];

создаёт массив с именем ia , который изначально указывает на набор из трёх элементов типа int. В объявлении массива его размерность не указывается. Количество элементов массива задаётся при его создании посредством оператора new. Длина массива фиксируется в момент создания и в дальнейшем изменению не поддаётся. Впрочем, переменной типа массива (в нашем примере - ia) в любой момент может быть поставлен в соответствие новый массив с другой размерностью.

Доступ к элементам массива осуществляется по значениям их номеров-индексов. Первый элемент массива имеет индекс, равный 0, а последний – length-1. Обращение к элементу массива выполняется посредством задания имени массива и значения индекса, заключённого в квадратные скобки. При каждом обращении к элементу массива по индексу исполняющая система Java проверяет, находится ли значение индекса в допустимых пределах, и генерирует исключение типа ArrayIndexOutOfBoudsException, если результат проверки ложен. Выражение индекса должно относиться к типу int – только этим и ограничивается максимальное количество элементов массива.

Длину массива можно определить с помощью поля length объекта массива (которое неявно снабжено признаками public и final). Ниже приведён дополненный код прежнего примера, в котором предусмотрено выполнение цикла, обеспечивающего вывод на экран содержимого каждого элемента массива ia :

for (int i = 0; i < ia.length; i++)

System.out.println(i+ “: ” + ia[i]);

Массив нулевой длины (т.е. такой, в котором нет элементов) принято называть пустым. Обратите внимание, что ссылка на массив, равная значению null, и ссылка на пустой массив – это совершенно разные вещи. Пустой массив – это реальный массив, в котором попросту отсутствуют элементы. Пустой массив представляет собой удобную альтернативу значению null при возврате из метода. Если метод способен возвращать null, прикладной код, в котором выполняется обращение к методу, должен сравнить возвращённое значение с null прежде, чем перейти к выполнению оставшихся операций. Если же метод возвращает массив (возможно, пустой), никакие дополнительные проверки не нужны – разумеется, помимо тех случаев, которые касаются длины массива и должны выполняться в любом случае.

Допускается и иная форма объявления массива, в которой квадратные скобки задаются после идентификатора массива, а не после наименования его типа:

int ia [] = new int [3];

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

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

Многомерные массивы

В Java поддерживается возможность объявления многомерных массивов (multidimensional arrays), т.е. массивов, элементами которых служат другие массивы. Код, предусматривающий объявление двумерной матрицы и вывод на экран содержимого её элементов, может выглядеть, например, так:

float[][] mat = new float [4][4];

for (int y = 0; y < mat.length; y++) {

for (int x = 0; x < mat[y].length; x++)

    System.out.print(mat[y][x] + " ");

System . out . println ();

}

При создании массива должна быть указана, по меньшей мере, его первая, «самая левая», размерность. Другие размерности разрешается не задавать – в этом случае их придётся определить позже. Указание в операторе new единовременно всех размерностей – это самый лаконичный способ создания массива, позволяющий избежать необходимости использования дополнительных операторов new. Выражение объявления и создания массива mat, приведённое выше, равнозначно следующему фрагменту кода:

float[][] mat = new float [4][];

for (int y = 0; y < mat.length; y++)

mat [ y ] = new float [4];

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

Инициализация массивов

При создании массива каждый его элемент получает значение, предусмотренное по умолчанию и зависящее от типа массива: нуль – для числовых типов, ‘\u0000’ – для char, false – для boolean и null – для ссылочных типов. Объявляя массив ссылочного типа, мы на самом деле определяем массив переменных этого типа. Рассмотрим следующий фрагмент кода:

Attr [] attrs = new Attr [12];

for ( int i = 0; i < attrs . length ; i ++)

attrs[i] = new Attr(names[i],values[i]);

После выполнения первого выражения, содержащего оператор new, переменная attrs получит ссылку на массив из 12 переменных, которые инициализированы значением null. Объекты Attr как таковые, будут созданы только в процессе прохождения цикла. Массив может инициализироваться (одновременно с объявлением) посредством конструкции в фигурных скобках, в которой перечислены исходные значения его элементов:

String [] dangers = {“Львы”, “Тигры”, “Медведи”};

Следующий фрагмент кода даст тот же результат:

String[] dangers = new Srting[3];

dangers[0] = “ Львы ”;

dangers [1] = “Тигры”;

dangers [2] = “Медведи”;

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

String[] dangers = new String[] {“ Львы ”, “ Тигры ”, “ Медведи ”};

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

printStrings(new String[] {“ раз ”, “ два ”, “ три ”});

Массив без названия (т.е. без явно сохраненной ссылки), который создаётся таким образом, называют анонимным (anonymous).

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

int [][] pascalsTriangle = {

     {1};

     {1,1};

     {1,2,1};

     {1,3,3,1};

     {1,4,6,4,1};

};

Индексы многомерных массивов следуют в порядке от внешнего к внутренним. Так, например, pascalsTriangle [0] ссылается на массив значений типа int, содержащий один элемент, pascalsTriangle [1] указывает на массив с двумя элементами и т.д.

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

Операторы

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

В Java имеется 46 операторов. Их можно разбить на 4 класса - арифметические, битовые, операторы сравнения и логические.

Арифметические операторы

Арифметические операторы используются для вычислений так же как в алгебре (см. таблицу 8). Допустимые операнды должны иметь числовые типы. Например, использовать эти операторы для работы с логическими типами нельзя, а для работы с типом char можно, поскольку в Java тип char - это подмножество типа int.

Таблица 8 – Арифметические операторы

Оператор Результат Оператор Результат
+ Сложение + = Сложение с присваиванием
- Вычитание (также унарный минус) -= Вычитание с присваиванием
* Умножение *= Умножение с присваиванием
/ Деление /= Деление с присваиванием
% Деление по модулю %= Деление по модулю с присваиванием
++ Инкремент -- Декремент

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