Будь умным!


У вас вопросы?
У нас ответы:) SamZan.net

вопросы программирования на языке Java

Работа добавлена на сайт samzan.net:


yjГЛАВА

1

ГЛАВА

1

         ГЛАВА 1

          

    Краткий обзор

                                   Спешите увидеть Европу!

                                   Десять стран за семнадцать  дней!

                                                 Из рекламного плаката,

                                              выставленного в витрине

                                        одной туристической фирмы

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

1.1. На старт ...

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

Класс содержит члены, которые относят к двум основным категориям-полям (fields) и методам (rnethods). Поля - это элементы данных, принадлежащие либо непосредственно самому классу, либо его объектам; значения полей в совокупности определяют состояние объекта или класса, Методы - это наборы выражений (expressions), предназначенные для выполнения операций с полями объекта/класса и изменения его состояния. Выражениями задаются характеристики поведения класса: с помощью методов можно изменять содержимое полей класса и иных переменных программы, выполнять арифметические операции, вызывать другие методы и управлять потоком вычислений.

Давняя традиция гласит: первым делом при изучении любого языка программирования следует рассмотреть код, который должен вывести на экран приветствие "Здравствуй, мир!". Что ж, мы так и поступим:

class HelloWorld {

 Public static void main(String[] args) {

  System.out.println (“Здравствуй, мир!”);

 }

}

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

компилятору, чтобы преобразовать набранный исходный текст (soиrce code) в байт-код (bytecodes) - совокупность инструкций "машинного языка", который способна воспринимать так называемая виртуальная машина Java (Java virtual machine) (позже мы остановимся на этом подробнее). Приемы набора исходных программных текстов и их компиляции существенно зависят от того, с какой системой вы работаете. За конкретной информацией по подобным вопросам обращайтесь к соответствующей документации. В системе, которой мы, например, пользуемся особенно часто - Java 2 Software Development Kit (Java 2 SDK), бесплатно предлагаемой компанией Sиn Microsystems, - вам достаточно сохранить текст программы He110Wor1d в одноименном файле, He110wor1d.java. Чтобы осуществить компиляцию, введите команду

javac HelloWorld.java

Следующая команда позволяет выполнить программу: java HelloWorld

Команда вызывает метод main класса HelloWorld. Во время своей работы программа отображает на экране сообщение

Здравствуй, мир!

Теперь у вас есть крошечное приложение, способное делать нечто полезное, но как все-таки оно работает?

Про грамма состоит из объявления класса под названием HelloWorld, содержащего единственный член - метод main. Описания членов класса заключают между фигурными скобками, { и }, которые следуют за наименованием класса.

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

Метод main объявляется как public, с тем чтобы обратиться к нему мог каждый субъект (в данном случае, виртуальная машина Java), и как statiс последнее означает, что метод принадлежит самому классу и не "привязан" ни к одному из частных экземпляров этого класса.

Непосредственно перед наименованием метода задается тип возвращаемого им значения. Метод main объявлен как void - это значит, что он не возвращает каких бы то ни было значений и поэтому говорить об их типе не имеет смысла.

За наименованием метода следует список его параметров (parameters) - последовательность (возможно, пустая) пар, каждая из которых состоит из обозначения типа и имени-идентификатора. Параметры разделяются символом запятой и заключаются в круглые скобки, ( и ). Единственный параметр метода main это массив (array) объектов типа string, снабженный именем args. Массивы объектов обозначаются парой квадратных скобок, [], которая располагается после наименования типа. В данном случае args предназначается для хранения параметров командной строки программы, передаваемых при ее запуске. О массивах и строковых объектах класса String мы расскажем в этой главе чуть ниже, а смысл параметра метода main поясним в разделе 2.9 на странице 83.

Имя-идентификатор метода совместно со списком его параметров образуют сигнатуру (signature) метода. Сигнатура вместе со списком модификаторов

  1.  ГЛАВА 1. КРАТКИЙ ОБЗОР

(modifiers) (таких как public и static), именем типа возвращаемого значения (retиrn type) и перечнем исключений (о последних мы расскажем в конце главы) формирует заголовок (header) метода. Наконец, об7Jявление (declaration) метода состоит из заголовка, за которым следует тело (body) метода - блок выражений, заключенных в фигурные скобки { И }.

В нашем примере тело метода main состоит из единственного выражения, в кото-

ром вызывается метод println (примите во внимание завершающий символ точки с запятой, ;). Обращение к методу выполняется с указанием иерархии объектов более высокого уровня (в данном случае, system.out - поле out принадлежит классу System) и названия метода (println), разделенных символом точки (.) ..

В классе HelloWorld метод print1n объекта out применяется для передачи последовательности символов, завершаемой признаком перехода на новую строку, в стандартный поток вывода. Здесь в качестве параметра метода используется строковая константа-литерал "здравствуй, мир!". Строковый литерал это набор символов, заключенный в двойные кавычки, " и

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

Упражнение 1.2. Попробуйте что-то намеренно нарушить в тексте HelloWorld и посмотрите, к чему это приведет.

1.2. Переменные

в следующем примере на экран выводится часть бесконечной последовательности чисел Фибоначчи, начало которой выглядит так:

1

1

2

3

5

8

13

21

34

Последовательность чисел Фибоначчи начинается с двух единиц, и каждое последующее число представляет собою сумму двух предыдущих. Программа отображения чисел Фибоначчи на экране довольно проста, но привлекательна тем, что позволяет продемонстрировать приемы использования переменных (variables), организации циклов (loops) и выполнения основных арифметических Операций. Ниже приведен текст программы.

class Fibonacci {

 /** Вывод на экран последовательности

  * чисел Фибоначчи, меньших 50 */

  

 public static void main(String[] args){

  int lo = 1;

  int hi = 1;

  System.out.println(lo);

  while(hi<50){

   System.out.println(hi);

   

1.2. ПЕРЕМЕННЫЕ                      25

hi = lo + hi; // новое значение hi

 lo = hi - lo; /* новое значение lo

    * равное старому значению hi */

 }

}

}

В примере объявляется класс Fibonacci, который, подобно HelloWorld, содержит метод main. Две первые строки main - это выражения объявления локальных (local) переменных, lo и hi. Переменная hi предназначена для хранения последнего найденного числа последовательности, а 1 о - предыдущего. Локальные переменные объявляются внутри блока кода, такого как тело метода, I! отличие от полей, которые входят в набор членов класса. Объявление каждой переменной должно быть снабжено описанием ее типа, которое располагают непосредственно перед идентификатором переменной. Так, переменные lo и hi относятся к типу int, предназначенному для хранения целых 32-битовых знаковых чисел из интервала от -2³¹ до 2³¹ -1.

Язык программирования Java поддерживает встроенные «простые» типы данных, позволяющие хранить целые и булевы значения, числа с плавающей запятой и символы. Простые типы естественны и легки для понимания, чего I! общем случае не скажешь об "объектных" типах, определяемых программистом. Тип любой переменной программы должен быть задан явно. Ниже приведено краткое описание простых типов Java.

 Наименование типа                            Допустимые значения____________________

boolean      true или false

char      16-битовый символ стандарта Unicode 2.1

byte       8-битовое целое число со знаком

short      16-битовое целое число со знаком

int      32-битовое целое число со знаком

long      64-битовое целое число со знаком

float  32-битовое число с плавающей запятой,                       соответствующее спецификации IEEE 754-1985

 double  64-битовое число с плавающей запятой,                        соответствующее спецификации IEEE 754-1985

__________________________________________________________________________________________

В программе Fibonacci переменные hi и lo объявлены с одновременной инициализацией их значением 1. При объявлении переменной ее исходное значение задается посредством выражения инициализации и оператора =. Оператор = (называемый еще оператором присваивания) сохраняет в переменной, расположенной в левой части конструкции, результат вычисления выражения из правой части.

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

Поскольку обе переменные, lo и hi, относятся к одному типу, int, мы могли бы воспользоваться краткой формой их объявления. Допустимо объявлять не-

  1.                                                                                                                                  ГЛАВА 1. КРАТКИЙ ОБЗОР

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

int lo = 1, hi = 1; 

Более удобочитаемая форма выглядит так:

int  lo = 1,

          hi = 1;

обратите внимание на то, что наличие в приведенном тексте символов перехода

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

Конструкция while в нашем примере демонстрирует один из способов организа

ции циклических вычислений. Значение аргумента while(логического выражения hi < 50) проверяется - если утверждение истинно (true), выполняется тело цикла, после чего выражение вновь подвергается проверке. Процесс повторяется до тех пор, пока проверка не выявит значения ложь (false). Вообще говоря, возможны случаи, когда логическое выражение whilе никогда не принимает значения "ложь", _ такой цикл способен выполняться бесконечно, если не будет принудительно прерван командой break либо возникшей исключительной ситуацией.

Тело цикла whilе остоит из единственного выражения. В этом качестве может выступать простое выражение (скажем, вызов не которого метода), вложенная управляющая структура либо блок (block) - последовательность (возможно,

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

Аргументом whilе служит логическое булево выражение, которое может прини-

мать одно из двух предопределенных значений - true (истина) или false (ложь). Булевы выражения обычно формируют с помощью операторов сравнения - < (меньше), <= (меньше или равно, или не больше), > (больше), >= (больше или равно, или не меньше), == (равно) и 1= (не равно). Булево выражение hi < 50 в рассматриваемом нами примере обозначает следующее условие: действительно ли текущее значение переменной hi меньше числа 50. Если условие истинно, выполняется тело Цикла, в котором предусмотрены вывод на экран текущего числа Фибоначчи и вычисление следующего. Если же переменная hi приобретает значение, большее или равное 50, управление передается на первую строку кода, следующую за телом цикла whilе,  в данном случае, это скобка, обозначающая конец объявления метода main, так что наша программа попросту завершает работу.

Чтобы вычислить очередное число последовательности Фибоначчи, мы выполняем несколько простых арифметических операций и, как легко видеть, вновь применяем оператор присваивания (=), сохраняющий значение из правой части выражения в переменной, расположенной в левой его части. Как вы легко догадались, оператор + (плюс) вычисляет сумму своих аргументов (операндов), а оператор - (минус) - их разность. В языке Java предусмотрено множество арифметических операторов, применяемых по отношению к целым и вещественным числовым типам. Наиболее потребительны операторы сложения (+), вычитания (-), умножения (~) и деления (/), а некоторые другие будут рассмотрены позже.

Обратите внимание, что в примере программы Fibonacci; методу println передается числовой аргумент, в то время как ранее, в тексте HelloWor1d, нами использовался строковый. ргintln  это характерный пример одной из многочис-

1.2. ПЕРЕМЕННЫЕ            27

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

Упражнение 1.3. Снабдите выводимый на экран список чисел Фибоначчи заголовком.

Упражнение 1.4. Напишите программу, которая генерирует иную последовательность, например список квадратов чисел.

1.3. Комментарии

Изучая программный код примеров, вы наверняка не могли не заметить текст на обычном "человеческом" языке - он оформлен в виде комментариев (comments). Существуют три стиля комментирования, и все они проиллюстрированы в программе Fibonacci. Комментарии позволяют снабдить код внятными описаниями, содержащими дополнительные разъяснения, которые способны помочь будущим пользователям программы. В роли подобного пользователя можете оказаться и вы сами - скажем, через пару месяцев или лет. Не ленясь и подробно комментируя собственный программный код сегодня, вы существенно облегчите свою участь завтра. Кроме того, процесс написания комментариев зачастую помогает отыскать ошибки - это вполне естественно, поскольку, пытаясь описать словами назначение конструкций программы, вы невольно вынуждены еще раз над ними поразмыслить.

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

Третий способ комментирования, предусматривающий использование конструкций /** и */, продемонстрирован в самом начале программы. Комментарии с двумя символами звездочки (*) в начале применяют для документирования программ и называют кратким термином doc commeпts. Комментарии документирования предназначены для описания следующих за ними объявлений. В рассматриваемом при мере подобный комментарий предшествует объявлению метода main. Если комментарии оформлены в строгом соответствии с указанным правилом, код может быть успешно обработан специальными программными средствами, позволяющими автоматически извлечь информацию комментариев и сгенерировать на ее основе удобную справочную документацию. В соответствии с принятым соглашением строки, заключенные в пределах комментария документирования или обычного комментария в стиле /* ... * /, снабжаются начальным символом звездочки (*), который игнорируется инструментами автоматического документирования и позволяет наглядно указать читателю границы комментария.

28          ГЛАВА 1. КРАТКИЙ ОБЗОР

1.4. Именованные константы

Константы  (constants) - это значения вида 12, 17.9 или "строка, подобная этой". Константы - или, каких еще называют, литералы (literals) предлагают удобный способ задания значений, которые не нуждаются в    первоначальных и повторных вычислениях и, по определению, остаются неизменными на протяжении всего сеанса

работы программы.

В примере Fibonacci, мы выводили на экран фрагмент возрастающей последовательности значений, не превышающих 50. Константа 50 была использована :IIУТРИ логического условия цикла whil е и комментария документирования, описывающего метод main. Теперь предположим, что текст программы следует "заменить таким образом, чтобы она отображала все числа Фибоначчи, меньшие 100. Вам придется последовательно просмотреть код с целью обнаружения и исправления всех экземпляров константы 50. Согласны, в данном примере сделать ЭТО несложно, но в общем случае подобный процесс заведомо трудоемок и чреват ошибками. Кроме того, читателю, встречающему в тексте программы выражение, схожее с hi < 50, подчас бывает трудно понять его истинный смысл. Подобные числа (о них в шутку говорят как о "магических") серьезным образом сужают возможности восприятия кода и его дальнейшего развития.

Именованная константа (named constant) - это постоянное значение, на которое можно сослаться по имени. Например, в программе Fibonacci верхнюю границу выводимых значений уместно обозначить идентификатором МАХ. Если говорить о синтаксисе, именованная константа объявляется в виде поля соответствующего типа, инициализированного требуемым значением. Такое выражение само по себе еще не будет именованной константой - это всего лишь обычное поле, значение которого задано с помощью оператора присваивания. Чтобы снабдить значение признаком "постоянства", в объявлении поля надлежит употребить модификатор final. Поле или переменная, обозначенные служебным словом final, будучи инициализированными один раз, далее не способны к изменению исходных значений - они приобретают свойство устойчивости. Кроме того, поскольку именованную константу не имеет смысла различать в составе отдельных экземпляров одного и того же класса, в ее объявлении употребляют модификатор statiс.

Теперь пример программы Fibonacci легко переписать следующим образом:

class Fibonacci2 {

 static final int MAX = 50;

 /** Вывод на экран последовательности

  * чисел Фибоначчи, меньших MAX */

  

 public static void main(String[] args){

  int lo = 1;

  int hi = 1;

  System.out.println(lo);

  while(hi<MAX){

   System.out.println(hi);

   hi = lo + hi;   

   lo = hi - lo;   

  }

 }

 

}

1.4. ИМЕНОВАННЫЕ КОНСТАНТЫ         29

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

class Suit { // Масть

final static int CLUBS   = 1; // трефы

final static int DIAMONDS = 2; // бубны

final static int HEARTS   = 3; // черви

 final static int SPADES   = 4; // пики

}

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

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

Упражнение 1.6. Измените программный код упражнения 1.3, предусмотрев использование в качестве заголовка последовательности чисел Фибоначчи именованной строковой константы.

1.5. СИМВОЛЫ Unicode

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

class CircleStuff {

static final double π = 3.14159265358979323846;

}

Язык программирования Jаvа позволяет нам смело вступить в сферу технологий программного обеспечения, заранее и всесторонне приспособленного к потребностям мирового сообщества: программы пишутся с применением Unicode международного стандарта символов. Символы Unicode представляются 16битовыми числами, диапазона изменения которых вполне достаточно для набора текстов на большинстве основных языков мира. Вот почему для обозначения константы в рассматриваемом примере мы вправе употребить символ π - допустимую букву из раздела таблицы Unicode, относящегося к греческому языку. Разумеется, большая часть существующего на сегодняшний день программного кода набрана в ASCII, стандарте 7-битовых символов, либо 180 Latin-1 - в соответствии со спецификацией, отводящей каждому символу 8 битов.

30          ГЛАВА 1. КРАТКИЙ ОБЗОР

1.6. Управляющие структуры

Термином управляющие структуры обозначают конструкции языка программирования, позволяющие регламентировать порядок выполнения выражений программы. В языке Java предусмотрены и другие конструкции for, if ... else, switch и do ... whi1e. Ниже представлен дополненный вариант программы, который позволяет нумеровать числа Фибоначчи и маркировать символом звездочки те из них, которые являются четными.

class ImprovedFibonacci {

  

  /** Вывод на экран последовательности

   * нескольких чисел фибоначчи;

   * четные числа помечаются символом '*' */

  static final int MAX_INDEX = 9;

  

  public static void main(String[] args){

   int lo = 1;

   int hi = 1;

   String mark;

   

   System.out.println("1:" + lo);

   for(int i = 2;i <= MAX_INDEX;i++){

    if(hi % 2 == 0)

     mark = "*";

    else

     mark = "";

    

    System.out.println(i + ":" + hi + mark);

    hi = lo + hi;   

    lo = hi - lo;

   }    

  }  

 }

Вот как выглядит результат работы программы:

1: 1

2: 1

3: 2*

4: 3

5: 5

6: 8*

7: 13

8: 21

9: 34*

Чтобы облегчить задачу получения номеров элементов последовательности, циклу while мы предпочли конструкцию for, в которое добавлены секции инициализации переменной цикла и измене-

1.6. УПРАВЛЯЮЩИЕ СТРУКТУРЫ          31

ния ее значения (в данном случае, приращения), можно считать обобщением whi1е. Цикл for, использованный в программе ImprovedFibonacci, равнозначен следующему циклу whi1е:

int i = 2;  // объявление и инициализация переменной цикла while (i <= MAX_INDEX) {

// ... операции вычисления очередного числа фибоначчи

// и его вывода на экран ...

i++;  // приращение содержимого переменной на единицу

}

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

Оператор ++ (плюс-плюс, или оператор инкремента), который использован в приведенном выше фрагменте кода, возможно, вам еще не знаком, если только у вас нет опыта программирования на языках, родственных С. Оператор предназначен для увеличения значения предшествующей ему переменной (в данном случае, i) на единицу. Оператор ++ может употребляться как в nрефиксной форме, когда он размещается перед идентификатором переменной, так и в постфиксной, если располагается следом. Названные формы обладают незначительными семантическими различиями, которые мы обсудим более подробно в главе 6. Оператор противоположного назначения, -- (минус-минус, или оператор декремента), служит для уменьшения величины на единицу и так же применяется в двух формах, префиксной и постфиксной. Выражение

i++;

равнозначно следующему: i = i+1;

Выражения, в которых присваиваемое переменной значение вычисляется на основе предыдущего содержимого той же переменной, в практике программирования встречаются настолько часто, что в языках (и Java в том числе) предусмотрены соответствующие краткие синтаксические формы. Например, выражение      i = i+1 может быть переписано таким образом:

i += 1;

Последнее означает, что аргумент правой части оператора += (здесь 1) прибавляется к текущему значению переменной из левой части (в данном случае, i) и результат сохраняется в той же переменной. Подобным образом разрешенo "совмещать" с оператором = все бинарные (binary) операторы языка (т.е. такие, которые предполагают наличие двух аргументов): -=, *=, /= и Т.д.

32          ГЛАВА 1. КРАТКИЙ ОБЗОР

в примере ImprovedFibonacci внутри тела цикла for мы воспользовались конструкцией ifelse  для про верки того, является ли текущее значение значение переменной hi четным числом.

В предложении if нашего примера с помощью оператора вычисления остатка, % проверяется условие четности значения hi - оператор определяет остаток от деления аргумента левой части (hi) на операнд правой части (2). Если значение hi четно, остаток равен нулю, Т.е. логическое выражение становится истинным, и следующий оператор присваивания сохраняет в переменной mark специальный признак четности текущего числа Фибоначчи. Предложение еlse выполняется для нечетных чисел - переменной mark присваивается пустая строка.

Команды, в которых вызывается метод println, на сей раз более сложны, поскольку аргументами println служат выражения, сами по себе подлежащие предварительному вычислению. В первом случае используется выражение "1: " + lo, которое предполагает операцию сцепления строкового литерала "1: "с содержимым числовой переменной 1о, преобразованным в символьный вид. Поскольку изначально переменная lо содержит значение 1, в результате на экран будет выведена строка 1: 1. Оператор + выполняет функции сцепления строк (иногда употребляют заимствованный англоязычный термин конкатенация. Прим.. перев.), если по меньшей мере один из его аргументов представляет собой строку, и сложения - если все операнды относятся к числовым типам. Употребление оператора сцепления строк непосредственно в списке аргументов метода удобная краткая форма кодирования, применяемая взамен более утомительной и многословной:

String temp = "1: " + lo;

System.out.print1n(temp);

Аргументом метода println, вызываемого в теле цикла for, служит строка, состоящая из символьного представления текущего значения переменной цикла i, к Которому далее "прицеплены" литерал разделителя ": ", преобразованное в строку Содержимое числовой переменной hi и значение строковой переменной mark.

Упражнение 1.7. Измените конструкцию for так, чтобы отсчет переменной цикла велся в обратном, убывающем, порядке.

1.7. Классы и объекты

Java, подобно МНОГИМ другим современным языкам, предоставляет в распоряжение разработчика инструменты объектно-ориентированного программирования. Каждому объекту программы отвечает класс (class), определяющий некую

1.7. КЛАССЫ И ОБЪЕКТЫ                 33

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

Поля (fields) - элементы данных (переменные), относящиеся к классу 11: его объектам и предназначенные для хранения информации о состоянии класса (объекта).

Методы (methods), содержащие исполняемый код класса. Методы строятся из выражений. Порядок вызова методов и наборы выражений, из которых они состоят, отвечают существу решаемой задачи.

Классы и интерфейсы (interfaces), выполняющие роль членов класса (Об интерфейсах мы расскажем ниже).

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

class Point {

public double х, у;

}

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

Члены классов могут относиться к различным уровням видимости (visibility) и доступа (accessibility). Наличие в объявлении полей х и у класса Роint служебного слова publiс означает, что любой код, обладающий правами доступа к объекту класса Роint, способен считывать значения этих переменных и изменять их. Существуют и другие уровни доступа, которые ограничивают возможности обращения к коду класса или связанных с ним классов.

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

Объекты (objects) классов, зачастую называемые экземплярами (instances), создаются с помощью выражений, содержащих служебное слово new.

Вновь созданные объекты размещаются в области системной памяти, известной как куча (heap). Доступ к объектам осуществляется посредством ссылок (references) - любая переменная, которая, как может показаться на первый взгляд, содержит сам объект, на самом деле хранит ссылку на этот объект. Типы подобных переменных называют ссылочными, или объектными, - в отличие от простых, когда переменные непосредственно содержат значения, соответствующие определенному типу. Ссылке присваивается значение null, если она не отвечает ни одному реально существующему объекту.

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

Вернемся к примеру класса Роint и предположим, что перед нами стоит задача создания графического приложения, в котором придется иметь дело с мно-

34          ГЛАВА 1. КРАТКИЙ ОБЗОР

 

жеством точек. Каждая из точек может быть представлена своим собственным объектом класса Point. Приведенный ниже код показывает приемы создания и инициализации объектов класса Point.

Point lowerLeft = new Point();

 Point upperRight = new Point();

 Point middlePoint = new Point();

 

 lowerLeft.x = 0.0;

 lowerLeft.y = 0.0;

 

 upperRight.x = 1280.0;

 upperRight.y = 1024.0;

 

 middlePoint.x = 640.0;

 middlePoint.y = 512.0;

Каждый объект класса Роint уникален и обладает собственными копиями полей х и у. Если, например, изменить значение переменной х, принадлежащей объекту lowerLeft, это никоим образом не скажется на содержимом одноименных полей объектов upperRight и middlePoint. Такие поля называют переменными экземпляра (instance variables), поскольку в каждом экземпляре класса существует независимая копия поля.

1.7.2. Статические поля

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

В некоторых ситуациях, однако, полезно иметь возможность объявления полей, общих для всех объектов класса. Подобные поля носят название переменных класса (class variables) и в отличие от переменных экземпляра принадлежат классу в целом.

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

Чтобы получить поле, относящееся к классу в целом, выражение его объявления Следует снабдить модификатором statiс. Такие поля класса называют статическими  (static).

public static Point origin = new point();

1.7. КЛАССЫ И ОБЪЕКТЫ          35

Поместив это объявление в текст класса Роint, мы Сможем гарантировать Существование в точности одной порции данных под названием Роint.огigin, которая всегда ссылается на объект точки с координатами (0,0). Это статическое поле в нашем полном распоряжении, независимо от того, сколько объектов Роint мы создали (либо не создали вовсе). Значения Роint.огigin.x и Роint.огigin.у изначально равны нулю - числовое поле всегда по умолчанию инициализируется нулем, если явно не задана другая величина.

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

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

1.7.3. Сборка мусора

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

1.8. Методы и параметры

Объекты ранее созданного класса Роint открыты для использования в любом коде, где имеются ссылки на них, поскольку поля Роint объявлены как public.point, - это пример простейшей разновидности классов. Некоторые реальные классы и в самом деле могут быть настолько же просты, если они проектируются, скажем, для сугубо внутренних целей пакета (набора взаимосвязанных классов) или в тех случаях, когда подобного тривиального контейнера данных вполне достаточно для решения какой-либо частной задачи.

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

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

public void clear() {

x = 0;

y = 0;

}

  1.  ГЛАВА 1. КРАТКИЙ ОБЗОР

метод clеаг не имеет параметров, поэтому список таковых, ограниченный круглыми скобками ( и ), пуст. Метод объявлен как void, а это значит, что он I'P возвращает каких бы то ни было значений. В теле метода разрешено непосредственно обращаться к любым другим методам и полям того же класса - например, мы просто напишем x и y, ну указывая явно полного имени класса.

1.8.1. Вызов метода

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

Чтобы осуществить вызов (invocation) метода, следует указать имена соответствующего целевого (target) объекта и самого метода, разделяя их символом точки. Аргументы (arguments) передаются методу в виде списка значений, перечисленных через запятую и заключенных в круглые скобки. При вызове методов, не предусматривающих задания параметров, круглые скобки все еще обязательны, хотя в промежутке между ними ничего не вводится.

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

В момент вызова метода выполнение текущего блока кода приостанавливается и управление передается первой инструкции тела вызываемого метода. По завершении кода метода управление возвращается инструкции, следующей за командой вызова метода. После передачи управления коду метода целевой объект, Владеющий методом и указанный при вызове, получает статус текущего (current), или объекта-приемника (receiver), - с точки зрения самого метода. Доступ к аргументам, переданным методу, осуществляется посредством параметров, задаваемых в конструкции объявления метода.

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

public double distance(point that) {

double xdiff = х - that.x;

double ydiff = у - that.y;

return Math.sqrt(xdiff * xdiff + ydiff * ydiff);

}

Команда return завершает выполнение метода и передает управление тому блоку программы, откуда метод был вызван. В тексте метода distance мы воспользовались методом sqrt из состава класса Math стандартной библиотеки Java, ко-

1.8. МЕТОДЫ И ПАРАМЕТРЫ          37

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

Чтобы продемонстрировать прием вызова метода distance, мы обратимся ~ созданным ранее объектам lowerLeft и upperRight класса Point:

double d = lowerLeft.distance(upperRight);

Здесь объект upperRight передается методу distance объекта lowerLeft в качестве аргумента и выглядит в теле distance как параметр that. После выполнения: выражения переменной d будет присвоено числовое значение, равное расстоянию между точками, которые описаны объектами lowerLeft и upperRight.

1.8.2. Ссылка this

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

Следующее объявление метода Clеаг равнозначно приведенному выше:

public void clear() {

this.x = 0;

this.y = 0;

}

Обычно слово this используют при необходимости вызова других методов с передачей ссылки на текущий объект в качестве параметра. Нередко ссылку this применяют и непосредственно в контексте метода для явного именования членов текущего объекта. Вот как может выглядеть еще один метод класса Роint, названный нами move, который изменяет значения координат точки, присваивая полям Х и у объекта величины, переданные в качестве аргументов:

public void move(double х, double у) {

 this.x = х;

this.y = у;

}

Ссылка this в данном случае используется для уточнения того, о каких именно переменных Х и у идет речь в каждом отдельном случае. Употребление в объявлении метода move наименований параметров Х и у вполне оправданно, поскольку они предназначены для хранения передаваемых аргументов-координат. Но эти имена совпадают с идентификаторами полей класса и полностью перекрывают их в контексте метода. Если написать просто x = x, это будет означать, что значение параметра x присваивается тому же параметру , но никак не одноименному полю класса, чего нам как раз и хотелось бы добиться. А выражение this.x позволяет сослаться на поле x текущего объекта и не имеет отношения к параметру x.

Упражнение 1.8. Пополните класс Роint методом, который присваивает значения координат точки текущего объекта полям объекта Роint, передаваемого в виде аргумента.

38          ГЛАВА 1. КРАТКИЙ ОБЗОР

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

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

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

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

Статическому методу запрещено непосредственно адресоваться к нестатическим членам класса. При вызове статического метода подразумевается отсутствие определенного частного экземпляра класса и поэтому метод не обладает возможностью обращения к ссылке this. Впрочем, вовсе не возбраняется передавать статическому Методу аргумент в виде явной ссылки на объект. Вообще говоря, статические методы предназначены для решения задач уровня класса, а нестатические позволяют учитывать особенности конкретных объектов. Вызывать статический метод для обращения к Полям частного объекта - это все равно, что требовать от компании Sony изменения серийного номера плеера, болтающегося на ремне у праздного подростка, который снует на своем скейтборде где-нибудь в районе Елисейских полей, Андреевского Спуска или ЦПКиО им. Горького.

1.9. Массивы

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

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

 

1.9. МАССИВЫ             39

пример программы, моделирующей игру в карты, объявление класса Deck (колода) могло бы выглядеть так:

public class Deck {

public static final DECK_SIZE = 52;

private card[] cards = new card[DECK_SIZE];

public void print() {

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

 system.out.println(cards[i]);

 }

// ...

}

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

Поле cards объявлено в виде массива объектов типа Card посредством указания имени типа и следующей за ним пары квадратных скобок, [ и ]. С помощью операторов присваивания и new переменная cards инициализируется массивом ссылок на объекты типа card, число которых равно значению DECK_SIZE. Каждой ссылке на объект card массива неявно присваивается значение null. Длина массива задается при его создании и далее никогда не изменяется.

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

Изучая код класса Deck, вы могли обратить внимание, что объект массива как такового обладает полем length, которое содержит данные о длине массива. Интервал изменения индекса, обозначающий границы массива, - это диапазон целых чисел от 0 до length-l включительно. Одна из частых ошибок программирования связана с попытками доступа к элементам массива за пределами его границ. Для предотвращения ошибок такого рода компилятор Java снабжает каждое обращение к элементу массива инструкциями по проверке границ - если индекс элемента выходит за пределы допустимого диапазона, исполняющая система регистрирует этот факт и генерирует исключение типа IndexOutOfBoundsException. Более подробную информацию по вопросам обработки исключительных ситуаций вы найдете в этой главе чуть ниже.

Массив нулевой длины называют пустым (empty). В теле метода, которому массив передается в качестве аргумента, обычно полезно предусмотреть проверку длины массива, чтобы убедиться, что тот действительно не пуст. Однако, прежде чем анализировать длину массива, следует удостовериться, что ссылка на массИВ реальна, Т.е. отлична от значения null. Если результат проверки любого из эти:){ условий ложен, метод может зафиксировать наличие проблемы, генерируя исключение типа  IllegalArgumentException. Ниже рассмотрен пример метода, который вычисляет среднее значение элементов целочисленного массива.

40          ГЛАВА 1. КРАТКИЙ ОБЗОР 

static double average(int[] values) {

if (values == null)

 throw new IllegalArgumentException();

else

 if (values.length == 0)

  throw new IllegalArgumentException();

 else {

  double sum = 0.0;

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

   sum += values[i];

  return sum / values.length;

 }

}

Код вполне работоспособен, но внутренняя логика метода с трудом поддается осмыслению из-за нагромождения вложенных одна в другую конструкций ifelse. Чтобы избежать необходимости применения двух выражений if, мы попытаемся, использовав булев оператор включающего ИЛИ ( | ) свести конструкцию к единому выражению, проверяющему, равен ли аргумент значению null либо равна ли нулю его длина:

if (values = = null I values.length = = 0)

throw new IllegalArgumentException();

Увы, этот код неверен. Даже в том случае, когда значение values действительно равно null, программа все еще будет пытаться получить доступ к полю length, поскольку обычные булевы операторы всегда проверяют оба операнда. Подобная ситуация, возникающая при выполнении логических операций, настолько распространена, что для ее преодоления разработаны специальные операторы языка. Условные булевы операторы обращаются к правому операнду только в тех случаях, когда не могут вычислить результат всего выражения на основе значения левостороннего операнда. Код примера легко исправить, если прибегнуть к оператору условного ИЛИ С 11):

if (values = = null ||  values.length = = 0)

throw new IllegalArgumentException();

Теперь в случае, когда values равно null, значение всего логического выражения становится равным true и программа более не предпринимает попыток обращения к полю length.

Бинарные булевы операторы - И ( & ), включающее ИЛИ ( 1) и исключающее ИЛИ  ( || ) - существуют в двух ипостасях: они выполняют функцию логических операторов, когда их операнды представляют собой булевы значения.

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

 Упражнение 1.10. Измените код программы ImprovedFibonacci таким образом, чтобы последовательность чисел заносилась в массив. Решите задачу, создав новый класс, позволяющий сохранять не только числа Фибоначчи, но и при-

1.9. МАССИВЫ            41

знак типа bооlеаn для каждого из них, который фиксирует факт четности числа, а затем создайте массив объектов этого класса

1.10. Объекты String

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

Со строковыми литералами вы познакомились буквально на первых страницах нашей книги. Когда вы пишете выражение, подобное sуstеm.оut.ргintln("Здравствуй, мир!");, компилятор на самом деле создает объект типа String, инициализирует его значением заданного строкового литерала и затем передает методу println в качестве аргумента.

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

class StringsDemo {

public static void main(String[] args) {

 string myName = "Вася";

 myName = myName + " Сидорчук";

 sуstеm.оut.ргintln("имя = " + myName);

 }

}

Мы объявляем переменную myName класса String и инициализируем ее значением строкового литерала. Далее, пользуясь оператором сцепления строк (+), или конкатенации, создаем новый объект String (да, именно новый - на этом аспекте более подробно мы остановимся чуть ниже) и сохраняем ссылку на него в той же переменной. Наконец, передаем методу ргintln еще один вновь созданный объект String, содержимое которого помещается в стандартный поток вывода. Вот как выглядит на экране результат работы программы:

Имя = Вася Сидорчук

Оператор конкатенации позволено использовать и в краткой форме, +=, которая предусматривает сцепление содержимого исходного объекта, указанного в левой части, и переменной или литерала правой части с

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

class BetterStringsDemo {

public static void main(String[] args) {

 String myName = "Вася";

 String occupation = "специалист по Java";

 

 myName += " сидорчук";

 myName += " ";

 myName += "(" + occupation + ")";

 System.оut.рrintln("имя = " + myName);

 }

}

Сейчас на экран будет выведено следующее:

42          ГЛАВА 1. КРАТКИЙ ОБЗОР

имя = Вася Cидорчук (специалист по Java)

Объект string обладает методом length, который возвращает количество символов, содержащихся в строке. Символы нумеруются индексными значения от 0 до length()-1. Символ, расположенный на определенной позиции, может быть найден с помощью метода charAt, который в качестве параметра принимает значение индекса и возвращает соответствующий индексу символ. Нетрудно выполнить и обратную операцию, связанную с получением массива таких же символов, какие хранятся в переменной String, - для этой цели применяется метод toCharArray класса String.

Объекты класса String доступны только для чтения: их содержимое после

инициализации остается неизменным. Рассмотрим следующий фрагмент кода:

str = "Вася";

// .,. здесь что-то с Васей происходит ...

str = "Сидорчук";

Второй оператор присваивания помещает новое значение в переменную str, которая становится ссылкой на совершенно иной строковый объект с содержимым "Сидорчук". Каждый раз при выполнении операции с объектом String (такой как, например, += в программе BetterStringsDemo) вам может показаться, что меняется только содержимое этого объекта, хотя в действительности вы получаете совершенно новый объект String, а "старый" остается нетронутым. В главе 9, где приведены более подробные сведения о классе String, рассмотрен и класс StringBuffer, который позволяет оперировать строковыми объектами, допускающими непосредственное изменение.

Метод equals обеспечивает самый простой способ сопоставления содержимого двух объектов String и выявления факта его тождественности:

if (onestr.equals(twoStr))

// В объектах хранятся одинаковые строки

Другие методы, позволяющие сравнивать части строк или игнорировать различия в регистре отдельных символов, рассмотрены в главе 9. Оператор == (равно), Применяемый по отношению к объектам string, позволяет определить, ссылаются ли две переменные, скажем, oneStr и twoStr, на один и тот же объект, оператор не проверяет, совпадает ли их содержимое.

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

Упражнение 1.12. Исправьте текст приложения ImprovedFibonacci таким Образом, чтобы вычисленные значения совместно с признаками четности предварительно сохранялись в массиве объектов String.

1.10. ОБЪЕКТЫ STRING           43

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

Одно из основных преимуществ объектного подхода к программированию состоит в возможности наследования (inheritance), или расширения (extending), функционального потенциала базового (родительского - superclass) класса при построении на его основе производного (дочернего - subclass) класса. Предоставляя в распоряжение программиста новые инструменты, производный класс сохраняет способность обращения ко всем элементам исходного класса. При создании дочернего класса тот наследует поля и методы базового класса.

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

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

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

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

Рассмотрим при мер наследования класса. Мы расширим ранее созданный класс Роint таким образом, чтобы обеспечить возможность представления данных об экранном пикселе. Новый класс рiхеl, помимо хранения координат х и у пикселя, предусматривает средства описания его цвета.

class Pixel extends Point {

Color color;

  

public void Clear() {

 super.clear();

 color = null;

}

}

44          ГЛАВА 1. КРАТКИЙ ОБЗОР

Производный класс рiхеl расширяет как набор данных, так и характеристики поведения базового класса point он вводит новое поле со1ог типа Color и переопределяет метод с1еаг класса Роint.

Объекты класса рiхеl могут быть использованы в любом коде, предназначенном для работы с объектами класса Роint. Например, если в списке параметров метода предусмотрен объект типа point, ничто не запрещает передать вместо него объект рiхеl - программа по-прежнему будет работать. Если в вашем распоряжении имеется объект типа рiхеl, вы вольны пользоваться любым кодом, оперирующим объектами Роint. Подобный механизм языка носит название полиморфизма (роlуmогрhism): скажем, один и тот же объект рiхе1 допускает интерпретацию в нескольких (poly-) формах (-morph) - он способен выступать в роли собственно объекта рiхеl и принимать облик объекта Роint.

Класс рiхеl расширяет (extends) набор функций класса Роint. Расширенные функции могут быть совершенно новыми (как, например, средства хранения информации о цвете точки); в других случаях они способны накладывать дополнительные ограничения в рамках исходной модели. Примером класса, реализующего ограничения, может послужить тот же рiхеl, в котором предполагается, что соответствующие объекты пикселей должны "обитать" в пределах экрана, Т.е. значения их координат х и у обязаны удовлетворять условиям, накладываемым объектом некоего типа Screen. Если базовый класс Роint сам по себе гипотетически допускает задание координат из ограниченного диапазона, производный класс рiхеl, вводящий подобное ограничение на практике, никоим образом не нарушит правила поведения, установленные базовым классом.

Производный класс часто переопределяет функциональные характеристики базового, предлагая новые версии одного или нескольких унаследованных методов. С этой целью в тексте производного класса объявляются методы с теми же сигнатурами и типами возвращаемых значений, что и в базовом. В примере класса Рiхеl мы переопределили метод с1еаг, чтобы реализовать функции, которые требуются для решения новой задачи. Унаследованный метод с1еаг базового класса Роint, который в данном случае может быть вызван командой super.clear() (подробнее об этой конструкции рассказано в следующем разделе), способен обращаться только к членам "родного" класса Роint и, что вполне очевидно, не "осведомлен" о "новшествах", таких как поле соlor, класса рiхеl.

1.11.1. Вызов методов базового класса

Чтобы снабдить объект рiхеl требуемыми функциями "очистки" своего внутреннего состояния, в классе Рiхе1 мы реализовали новую версию метода с1еаг, в теле которого посредством ссылки 5uper первым делом вызывается одноименный метод базового класса Роint. Ссылка 5uper во многом напоминает ссылку this, о которой МЫ говорили выше, за тем исключением, что 5uper позволяет адресовать элементы базового класса, а this ссылается на текущий объект.

Вызов super.clear() с точки зрения базового класса point выглядит как обычное обращение к методу сlеаг объекта Роint. После выполнения команды suрег.с1еаг()для очистки той части объекта Рiхеl, которая определена в Классе Роint, мы осуществляем дополнительную операцию присваивания ссылке сolог заведомо пустого и безопасного значения null.

Резонно спросить, что произойдет, не будь вызова super.сlеаr()? В этом Случае метод сlear класса рixel, разумеется, выполнит свою часть работы,

1.11. НАСЛЕДОВАНИЕ            45

 

присвоив переменной соlor значение null о поля х и у, которые Рiхеl унаследовал от класса Роint, не получат каких бы то ни было "Чистых" значений. Если поля объекта - в том числе и те, которые объявлены в базовом Классе,  остаются в состоянии, не отвечающем ситуации, это трудно не назвать явной ошибкой программиста.

При вызове super.тethod() Исполняющая система Java осуществляет просмотр иерархии классов в направлении от производных к базовым и пытается отыскать базовый класс, содержащий требуемый метод тethod. Если, например, класс Роint не содержал бы метода сlеаг, система перешла бы к классу, базовому по отношению к Роint, и т.д.

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

Point point = new Рiхеl ();

point.clear(); // Вызывается метод сlеаг класса Рiхеl 

Здесь программа обращается к методу Сlеаr класса Рiхеl, хотя переменная, содержащая объект Рiхеl, объявлена в Виде ссылки на объект класса Роint. Но если осуществить вызов super.сlеаг() из тела любого метода объекта Рiхеl, будет востребован метод сlеаг класса Роint.

1.11.2. Класс Object

Все классы - даже те из них, которые явно не расширяют какого бы то ни было базового Класса, - косвенным образом наследуют класс Object. Все объекты Java полиморфным образом относятся к классу Object, общему (родовому) типу, и переменные этого типа могут ссылаться на объекты любого класса:

Object objRef = new Piхед();

objRef = "Какая-то строка";

Пример демонстрирует, что переменная objRef способна содержать ссылки на объекты типов Piхеl и Stгing, хотя эти классы никак между собой не связаны, за исключением того, что оба неявным образом унаследованы от класса Object. В составе класса Object объявлено несколько весьма важных методов, более подробные сведения о которых приведены в главе 3.

1.11.3. Преобразованиl Типов

Следующий фрагмент Кода, на первый взгляд, вполне приемлем (хотя и не

особенно полезен), но порождает ошибку компиляции:

String name = "Вася";

Object obj = name;

name = obj; // НЕВЕРНО: ошибка компиляции!

Мы объявляем и инициализируем ссылку на объект Stгing, присваиваем ее Переменной типа Object, а затем пытаемся выполнить обратное присваивание, чтобы вернуться к исходному положению вещей, сохранив в неприкосновенности дорогое сердцу имя. Что ж тут плохого? Проблема состоит в том, что хотя String - это всегда Object, Object, - это вовсе не обязательно String, и даже если в данном случае мы видим, что obj и в самом деле указывает на объект Stгing, компилятор не столь разумен и дальновиден. Чтобы помочь компилятору разобраться в ситуации, следует внятно подчеркнуть, что объект, адре-

46             ГЛАВА 1.КРАТКИЙ ОБЗОР

суемый переменной obj, действительно относится к классу String и поэтому ссылку на него разрешается присвоить переменной name:

name = (String)obj; // вот так-то лучше!

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

Упражнение 1.13. Набросайте эскиз иерархии классов, отображающий зависимости между различными моделями компактных кассетных проигрывателей компании Sony, о которых мы говорили ранее. Обеспечьте сокрытие данных, объявив все поля классов как private, а методы как publiс. Какие методы следовало бы предусмотреть в базовом классе (назовем его Wa1kman), а какие целесообразно вынести в производные классы?

1.12. Интерфейсы

Иногда в ходе проектирования возникает потребность в том, чтобы объявить Методы, которые должны поддерживаться объектами класса, не предлагая конкретной реализации этих методов: в такой ситуации важно просто обеспечить гарантии того, что поведение объектов класса удовлетворяет совокупности некоторых критериев (ее принято называть контрактом), - детали реализации методов до определенного момента не важны. В каждом подобном объявлении Должна быть оговорена сигнатура метода и тип возвращаемого им значения, Чтобы любой класс, реализующий этот метод, подчинялся предложенному вами Контракту. Например, метод предназначен для поиска заданного объекта в некотором множестве объектов определенного типа - вас пока не интересует, каким именно образом организовано множество, к которому должен обращаться этот метод. Ваша настоящая цель состоит в том, чтобы будущие реальные версии метода одинаково успешно справлялись со связанным списком, хеш-таблицей либо любой другой структурой данных.

Для практического воплощения подобной модели действий язык программирования Java предусматривает средства определения интерфейсов. Интерфейсы подобны Классам, но содержат только заголовки (не полные объявления!) методов. Разработчик интерфейса предусматривает методы, которые должны поддерживаться классами, реализующими интерфейс, и оговаривает назначение каждого из методов. Ниже

1.12. ИНТЕРФЕЙСЫ            47

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

interface Lookup {

/** возвращает значение, соответствующее  содержимому name, либо null,

* если значение не найдено */

object find(string name);

}

В интерфейсе Lookup определен единственный метод, find, который принимает в качестве параметра объект типа String и возвращает значение, соответствующее содержимому переданной строки, либо null, если значение не найдено. Метод, объявленный в интерфейсе, лишен блока тела - ответственность за его практическую реализацию возлагается на производный класс - и поэтому вместо тела метода вводится просто символ точки с запятой. Код, в котором используются ссылки типа Lookup (ссылки на объекты классов, реализующих интерфейс Lookup), способен вызывать метод find и получать конкретные результаты независимо от действительного типа объектов:

void processvalues(String[] names, Lookup table) {

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

   object va1ue = table.find(names[i]);

   if (value != null)

     processvalue(names[i], value);

  }

 }

Класс может одновременно реализовать столько интерфейсов, сколько необходимо. Класс, рассмотренный в следующем примере, реализует (implements) интерфейс Lookup, используя простые массивы (для краткости мы опустили описания методов, ответственных за ввод и удаление данных).

class SimpleLookup implements Lookup {

private String[] names;

private Object[] values;

public Object find(String name) {

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

  if (names[i].equals(name))

   return values[i];

 }

 return null; // Значение не найдено

}

 // ...

}

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

48          ГЛАВА 1. КРАТКИЙ ОБЗОР

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

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

Упражнение 1.14. Разработайте интерфейс, который расширяет интерфейс Lookup посредством объявления методов add и remove, предусматривающих возможность ввода и удаления данных соответственно. Реализуйте расширенный интерфейс в новом производном классе.

1.13. Исключения

Что следует предпринять, если в программе возникает ошибка? Во многих языках программирования факт возникновения ошибочной ситуации фиксируется каким-нибудь необычным значением (скажем, -1), которое возвращается функцией, виновной в происшедшем. Программисты зачастую всецело пренебрегают средствами проверки корректности работы функций, полагая, что "вот сейчас-то ошибки быть не может". Существует и другая крайность - если пытаться выявить и обработать абсолютно все ситуации, чреватые ошибками, с помощью нагромождения условных выражений, код, доселе простой и понятный, приобретет настолько запутанный и угрожающий вид, что внешняя логика программного текста будет совершенно утрачена. Некая заведомо простая задача, связанная, например, с пересылкой данных из файла в память, может потребовать, скажем, семи строк кода. Снабдив программу исчерпывающими инструкциями обработки ошибок, вы наверняка увеличите объем кода раз, этак, в пять или шесть. Разумеется, недопустимо, чтобы основные функции программы, ответственные за решение задачи как таковой, терялись в дебрях вспомогательных конструкций подобно иголке в стоге сена.

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

Метод, обнаруживший неожиданную ошибочную ситуацию, генерирует, или, как принято выражаться, выбрасывает (throw), исключение. Исключения могут быть обработаны, или, попросту говоря, отловлены (catch), кодом, который, Возможно, расположен в стеке вызовов несколько ранее, - подобный код способен справиться с ситуацией таким образом, как это предусмотрено программистом, и позволить приложению продолжить работу. Исключения, не подверг-

1.13. ИСКЛЮЧЕНИЯ            49

шиеся обработке, приводят к завершению потока вычислений, но прежде Соответствующему объекту класса ThreadGroup все-таки предоставляется последний шанс каким-то наиболее приемлемым в конкретной ситуации способом "поладить" с исключением - вполне вероятно, не предложив ничего другого, кроме печальной констатации происшедшего. Классы Thread и ThreadGroup, имеющие непосредственное отношение к потокам вычислений, подробно рассмотрены в главе 10.

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

Обработка исключений реализуется в рамках следующей общей конструкции: try ... catch ... finally. Программа пытается (try) выполнить определенные операции; если в результате работы возникает или Принудительно выбрасывается (throw) исключение, оно отлавливается (catch) и обрабатывается; и, наконец (finallу), программа получает возможность осуществить дополнительные действия по приведению своего внутреннего состояния в порядок, необходимые либо в случае нормального протекания процесса, либо после обработки исключительной ситуации, независимо от того, что произошло на самом деле.

Ниже приведен пример метода getDataSet, который возвращает набор данных, считанных из файла. Если файл не может быть найден или происходит любая другая ошибка ввода-вывода, метод генерирует соответствующее исключение. Прежде всего мы определяем новый тип исключения BadDataSetExcepti оп, отвечающий природе вероятных ошибок. Затем в теле некоторого класса MyUtilities объявляем метод getDataSet и с помощью предложения throws этого объявления оговариваем, что метод способен выбрасывать исключение определенного нами типа.

class BadDataSetException extends Exception {}

class Myutilities {

public double[] getDataSet(String setName) throws

  BadDataSetException

 {

  Ыtring file = setName + ".dset";

FilelnputStream in = null;

  try {

  in = new FilelnputStream(file);

  return readDataSet(in);

  } catch (IoException е) {

     throw new BadDataSetException();

} finally {

    try {

     if (in != null) in.closeO;

    } catch (IoException е) {

 

50          ГЛАВА 1. КРАТКИЙ ОБЗОР

         ;   // Можно не обращать внимания

// данные уже получены!

}

}

}

// ... объявление метода readDataSet ...

}

Сначала, используя содержимое параметра setName, мы формируем имя файла, а затем в блоке try пытаемся открыть этот файл и прочесть данные из него с помощью метода readDataSet. При благополучном исходе readDataSet возвращает массив значений типа doub1е, который, в свою очередь, служит результатом работы метода getDataSet. Если операции открытия или Чтения файла приводят к Возникновению Исключительной ситуации, управление Переедается блоку catch. В последнем создается и выбрасывается объект Исключения BadDataSetException, подменяющий собою объект Исключения стандартного типа IOException. Некоторый код, содержащий вызов метода getDataSet, позже Сможет отловить объект Исключения созданного нами типа и отреагировать на обстоятельства должным образом. Далее в любой ситуации - либо После успешного открытия и чтения файла, либо вслед за инструкциями блока catch - Выполняется предложение finally, Позволяющее произвести необходимые завершающие операции (в нашем примере - закрыть файл, если Предварительно тот был успешно открыт). Если иключение генерируется при попытке закрытия файла, оно отлавливается, но игнорируется - в тексте вы видите отдельный символ точки с запятой, обозначающий пустое выражение. Вообще говоря, пренебрегать Исключениями, возникающими в процессе работы программы, не стоит, но в данном случае предполагается, что ошибка проявляется уже после успешного Извлечения данных из файла, и поэтому можно считать, что оговоренный контракт удовлетворен и миссия, возложенная на метод, Выполнена. Если ошибка повторится, она будет зафиксирована при следующей попытке работы с файлом, и тогда, вероятно, программа сможет предпринять более эффективные меры для разрешения проблемы.

Еще раз подчеркнем, что блок finally предоставляет возможность задать все необходимые инструкции восстановительного характера, Которые должны выполняться в любой ситуации. Вообще говоря, ничто не запрещает применять и такую конструкцию try ... finallу, в которой Вовсе отсутствует блок catch. Это позволит гарантировать, что код finallу будет Выполнен всегда, даже в том Случае, когда выбрасывается Исключение, для которого соответствующее предложение catch не предусмотрено.

Если метод способен генерировать Исключения, типы этих исключений должны быть перечислены в секции throws объявления метода (как в Примере, приведенном Выше). Конкретному методу Позволено выбрасывать только те Исключения, которые указаны в его объявлении, - вот почему их называют объявляемыми. В теле метода исключения могут выбрасываться либо Принудительно, посредством Выражения throw, либо косвенно, при вызове другого метода, который способен иx генерировать. Исключения классов RuntimeException, Еrror и производных от них типов относятся к категории необьявляемых исключений и могут генерироваться при любых обстоятельствах без предварительного уведомления.

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

1.18. ИСКЛЮЧЕНИЯ            51

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

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

Упражнение 1.15. Добавьте в состав класса BadDataSetException поля для хранения имени файла и объекта исключения IOException, который фиксирует проблему, чтобы код, вызывающий метод getDataSet, получил в свое распоряжение более полную информацию о природе ошибки.

1.14. Пакеты

При разработке программного кода, который, как предполагается, будет использоваться повторно, возникает проблема конфликтов имен. Совершенно неважно, насколько тщательно вы подошли к задаче выбора идентификаторов собственных классов, - возможно, кто-то еще из числа будущих пользователей вашего кода употребит аналогичные имена для других целей. Если вы применяете тривиальные, легко предсказуемые имена, проблема еще более усугубляется - те же идентификаторы почти наверняка будут использованы и другим программистом, который, как и вы, исповедует принцип просто ты и стремится обеспечить удобочитаемость собственного кода. Слова, подобные list (список), event (событие), component (компонент) и Т.Д., употребляются настолько часто, что их взаимное "пересечение" можно гарантировать с полной уверенностью.

Традиционное решение проблемы конфликтов имен во многих языках программирования состоит в использовании в идентификаторах классов (а также типов, глобальных функций и т.п.) префиксов, соответствующих именам пакетов. Такое соглашение позволяет создавать контексты имен, препятствующие возникновению подобных конфликтов. Префиксы обычно представляют собой аббревиатуры из нескольких символов, соответствующие наименованиям библиотек (примером может служить префикс Xt, отвечающий продукту X-Windows Toolkit).

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

52          ГЛАВА 1. КРАТКИЙ ОБЗОР

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

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

Ниже приведен текст примера, в котором создается объект класса Date стандартного пакета uti1, позволяющий получить системные дату и время. При обращении к классу указывается его полное имя. Как и во всех других классах, оперирующих временными данными, время отсчитывается в миллисекундах, начиная с 00:00:00 часов 1 января 1970 года по Гринвичу.

class Date1 {

public static void main(string[] args) {

java.util.Date now = new java.util.Date();

System.out.println(now);

}

}

А теперь рассмотрим другую версию примера, в которой предусмотрена инструкция предварительного импорта класса Date:

import java.util.Date;

class Date2 {

public static void main(String[] args) {

Date now = new Date();

System.out.println(now);

}

}

Когда компилятор, анализируя текст программы, встречает объявление объекта now, он воспринимает Date именно как тип Java.util.Date, Поскольку последний - это единственный из всех мыслимых типов Date, Который в данном Случае известен компилятору. Инструкция импорта просто предоставляет компилятору дополнительные сведения и никоим образом не означает, что в текущий файл будут "включены" какие-то дополнительные данные.

Строго говоря, соглашение, касающееся правил именования пакетов, все еще Окончательно не разрешает проблемы конфликтов идентификаторов. Два наудачу Взятых проекта вполне могут быть реализованы в виде пакетов с одинаковыми названиями. Поэтому соглашение требует развития. Уточненные рекомендации Выглядят так: следует предварять наименование пакета обратным доменным Intrnеt-адресом той организации, которая создала пакет. Например, если компания АВС Corporation обладает доменным именем abc. сот, при обращении к

1.14. ПАКЕТЫ             53

 

разработанным ею пакетам Java необходимо предварительно задавать строку соm.abc, как, скажем, в случае com.abc.tools.

Употребление в именах пакетов разделительного символа точки иногда способно при водить к недоразумениям, поскольку тот же символ применяется и при обращении к методам и полям объектов, заданных посредством ссылок. Подобный синтаксис в контексте инструкции не позволяет получить вполне точный ответ на вопрос, что именно может быть импортировано. Новички часто пытаются выполнить команду в надежде избежать неприятной повинности, связанной с вводом соответствующего префикса при каждом обращении к методу ргintln. Такой подход ошибочен, поскольку system - это класс, а out - статический объект в составе класса system, поддерживающий, в частности, и метод ргintln.

С другой стороны, java.util.Date - это класс, который может быть импортирован (часто в инструкции import применяют аргумент, подобный java. uti1 . *, если необходимо импортировать все содержимое указанного пакета). Если при попытке импорта вы сталкиваетесь с проблемой, убедитесь, что в качестве аргумента инструкции указан именно тип.

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

package com.sun.games;

class Card {

//…

}

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

1.15. Платформа Java

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

Универсальные определения, лежащие в основе языка Java как такового,

реализуются на уровне машинного языка, байт-кода, в который транслируется

54          ГЛАВА 1. КРАТКИЙ ОБЗОР

 

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

Виртуальная машина обеспечивает среду исполнения, которая позволяет приклад ной программе обращаться к самой виртуальной машине (например, принудительно запускать процесс сборки мусора) и взаимодействовать с окружающим миром (скажем, с помощью потоков ввода-вывода, таких как System.out). Среда исполнения, или исполняющая система (runtime system), содержит в своем составе менеджер безопасности (security manager) или контроллер доступа (access controller), который способен, например, запретить приложению обращаться к локальному диску для чтения/записи или регламентировать возможность подключения к конкретным компьютерам сети. Совокупность операций, которые разрешено выполнять, определяется политикой безопасности (security policy), вступающей в силу в момент старта приложения.

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

Сочетание всех названных свойств обеспечивает меру независимости системы от особенностей конкретной платформы, достаточную для реализации модели безопасности, которая гарантирует возможность выполнения кода, загруженного из сетевых источников различных степеней надежности. Исходный программный текст, скомпилированный в байт-код Java, способен выполняться на любом компьютере, обладающем виртуальной машиной Java. Код может быть выполнен с соблюдением требований соответствующего уровня защиты, что позволяет предохранить систему от вредоносных последствий выполнения программ, которые написаны неаккуратно или злонамеренно. Уровень надежности (безопасности) источника байт-кода может быть задан явно - про грамма, хранящаяся на локальном диске или в защищенной сети, заслуживает, как правило, большего доверия, нежели код неизвестного происхождения, загруженный из Internet.

1.16. Краткие сведения о других темах

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

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

1 Вполне допустима, разумеется, и аппаратная реализация виртуальной машины Java, Предполагающая использование полупроводниковых микросхем специального назначения. Подобный подход никоим образом не воздействует на степень переносимости байт-кода; речь идет Просто об альтернативном способе практического воплощения виртуальной машины.

1.16. КРАТКИЕ СВЕДЕНИЯ О ДРУГИХ ТЕМАХ         55

Типы и метаклассы. В языке предусмотрены специальные классы-оболочки (wrapper classes) для представления простых типов (такие как Integer, doublе или boolean) и реализован механизм рефлексии (ref1ection), позволяющий получать информацию об объектах программы и создавать их динамически. За деталями обращайтесь к главе 11.

Ввод-вывод. Стандартный пакет java.io содержит множество различных инструментальных средств программирования операций ввода-вывода (input/output _ I/0) данных. Информация об особенностях реализации в Java процедур ввода-вывода приведена в главе 15.

Коллекции. Пакет java.util предлагает немало полезных типов коллекций (collections) - в их числе можно назвать, например, List и HashMap. Вопросы применения классов коллекций освещены в главе 16.

Прикладные Интерфейсы u классы. Пакет java.util содержит массу любопытных классов прикладного назначения, таких как BitSet и Date. За подробностями мы отсылаем вас к главе 17.

Не так быстро, п-п-пожалуйста!

Я записываю ...

Реплика из кинофильма

56         ГЛАВА 1. КРАТКИЙ ОБЗОР




1.  Пласт эпителия образован клетками ядра которых расположены неодинаково по отношению к базальной мембране
2. Легка промисловість України i транспорт
3. Рассмотрение дел о банкротстве в хозяйственных судах Украины
4. Подрядная кооперация
5. 2014 навчального року групи Кількість учнів за
6. 35 причин заняться йогой Перечисленные в этой статье достоинства йоги помогут вам детально понять смысл з
7. Значення газообміну для дітей 3-7 років
8. Академия Делового Развития
9. История Тамани
10. Принцип действия зеркальной антенны
11. Налоги и их влияние на развитие предпринимательской деятельности в России.html
12. це соціальнодемографічна група яка займає певне місце в соціальній структурі суспільства характеризуєть
13. Тема - Решение задач и спорных практических ситуаций связанных с определением предмета и средств доказывани
14. Организация и методика послеубойной ветеринарно-санитарной экспертизы туш и внутренних органов сельскохозяйственных и диких промысловых животных (птицы)
15. наслідковими відносинами які можуть бути обмірювані кількісними і якісними змінними
16. Криминальный незаконный аборт
17. О ПРИМЕНЕНИИ СУДАМИ ОБЩЕЙ ЮРИСДИКЦИИ ОБЩЕПРИЗНАННЫХ ПРИНЦИПОВ И НОРМ МЕЖДУНАРОДНОГО ПРАВА И МЕЖДУНАРОДНЫХ
18. тема отношений человека
19. Влияние налоговой политики на формирование доходов бюджета Республики Беларусь
20. У меня проблемная кожа