Vasil'Iev - Java. Obiektno-Oriientirovann - Unknown
Vasil'Iev - Java. Obiektno-Oriientirovann - Unknown
2-018я7
УДК 004.43(075)
В19
Васильев А. Н.
В19 Java. Объектно-ориентированное программирование: Учебное пособие. —
СПб.: Питер, 2011. — 400 с.
ISBN 978-5-49807-948-6
Учебное пособие предназначено для изучающих объектно-ориентированное программирование
в вузе, а также для всех желающих самостоятельно изучить язык программирования Java. Книга
охватывает все базовые темы, необходимые для эффективного составления программ на Java,
в том числе базовые типы данных, управляющие инструкции, особенности описания классов
и объектов в Java, создание пакетов и интерфейсов, перегрузку методов и наследование. Особое
внимание уделяется созданию приложений с графическим интерфейсом.
В первой части книги излагаются основы синтаксиса языка Java. Материала первой части кни-
ги достаточно для написания простых программ. Во второй части описываются темы, которые бу-
дут интересны тем, кто хочет освоить язык на профессиональном уровне. Каждая глава книги со-
держит теоретический материал, иллюстрируемый простыми примерами, позволяющими под-
черкнуть особенности языка программирования Java. В конце каждой главы первой части имеется
раздел с примерами решения задач.
Учебное пособие соответствует Государственному образовательному стандарту 3-го поколения
для специальностей «Информатика и вычислительная техника», «Информационные системы
и технологии», «Прикладная информатика» и «Фундаментальная информатика и информационные
технологии».
ББК 32.972.2-018я7
УДК 004.43(075)
Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было фор-
ме без письменного разрешения владельцев авторских прав.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как
надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не
может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за
возможные ошибки, связанные с использованием книги.
Примеры программ............................................................................................................... 92
Резюме......................................................................................................................................108
Глава 4. Классы и объекты..................................................................... 110
Знакомство с ООП..............................................................................................................110
Создание классов и объектов..........................................................................................114
Статические элементы.......................................................................................................118
Доступ к членам класса.....................................................................................................121
Ключевое слово this............................................................................................................124
Внутренние классы.............................................................................................................126
Анонимные объекты...........................................................................................................128
Примеры программ.............................................................................................................129
Резюме......................................................................................................................................151
Глава 5. Методы и конструкторы............................................................ 153
Перегрузка методов.............................................................................................................153
Конструкторы........................................................................................................................156
Объект как аргумент и результат метода...................................................................159
Способы передачи аргументов........................................................................................164
Примеры программ.............................................................................................................166
Резюме......................................................................................................................................197
Глава 6. Наследование и переопределение методов................................. 198
Создание подкласса.............................................................................................................198
Доступ к элементам суперкласса...................................................................................200
Конструкторы и наследование........................................................................................202
Ссылка на элемент суперкласса.....................................................................................204
Переопределение методов при наследовании...........................................................208
Многоуровневое наследование.......................................................................................212
Объектные переменные суперкласса и динамическое
управление методами......................................................................................................215
Абстрактные классы...........................................................................................................218
Примеры программ.............................................................................................................220
Резюме......................................................................................................................................227
Программное обеспечение
Необходимо отдать должное компании Sun Microsystems. Она не только пред-
ложила достаточно оригинальный и мощный язык программирования, но и со
здала широкий спектр программных средств, в основном распространяющихся
на условиях лицензии с открытым кодом. Загрузить все (или практически все)
необходимое для работы программное обеспечение можно на сайте www.java.
com, посвященном технологии Java.
Для того чтобы программировать в Java, необходимо установить среды JDK ( Java
Development Kit — среда разработки Java) и JRE ( Java Runtime Environment —
среда выполнения Java). Обе свободно загружаются с сайта www.java.com (или
www.oracle.com). В принципе, этого для работы достаточно. Однако лучше все же
прибегнуть к помощи какой-нибудь интегрированной среды разработки. Луч-
шим выбором в этом случае будет среда NetBeans, которая доступна на сайте
www.netbeans.org. Причем к услугам пользователей предоставляются полные
версии среды, включая системы JDK и JRE. Можно также воспользоваться сре-
дой Eclipse, которая свободно доступна на сайте www.eclipse.org. Правда, работа
с этой средой имеет свои особенности. Используемому при программировании
в Java программному обеспечению посвящено приложение в конце книги.
Обратная связь
Полезную для себя информацию читатели могут найти на сайте автора www.
vasilev.kiev.ua. Свои замечания, пожелания и предложения можно отправить по
электронной почте на адрес [email protected] или [email protected].
Программные коды
Рассмотренные в книге программные коды можно загрузить через Интернет с сайта
издательства www.piter.com или персональной страницы автора www.vasilev.kiev.ua.
От издательства 13
Благодарности
К чтению курса лекций по Java на медико-инженерном факультете Националь-
ного технического университета «Киевский политехнический институт» автора
приобщил декан (на тот момент) факультета, заведующий кафедрой медицин-
ской кибернетики и телемедицины профессор Яценко Валентин Порфирьевич. Эту
приятную традицию поддержал нынешний декан медико-инженерного факульте-
та, заведующий кафедрой биомедицинской инженерии, профессор Максименко
Виталий Борисович. Автор считает своей приятной обязанностью выразить им
за это свою искреннюю благодарность.
Автор выражает искреннюю признательность издательству «Питер» и лично
Андрею Юрченко за профессиональную и эффективную работу по выпуску кни-
ги. Хочется также поблагодарить редактора Алексея Жданова за его полезные
замечания, благодаря которым книга стала намного лучше.
От издательства
Ваши замечания, предложения и вопросы отправляйте по адресу электронной
почты [email protected] (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
Подробную информацию о наших книгах вы найдете на веб-сайте издательства
http://www.piter.com.
Часть I. Введение в Java
Глава 1. Основы Java
Фандорин, у меня времени нет! Скажите по-человечески.
Я не понимаю этого языка.
Из к/ф «Статский советник»
Простые программы
отдельная глава книги (см. главу 8). Массивы описываются в следующей главе.
Желающие побольше узнать о способах передачи аргументов методам могут
обратиться к главе 4, посвященной созданию классов и объектов. Квадратные
скобки можно указывать после ключевого слова String или после имени ар-
гумента args.
Тем, кто программирует в С++, многое из приведенного уже знакомо. Для тех,
кто ничего знакомого во всем этом не увидел, резюмируем: на ближайшее время
все наши программы будут иметь следующую структуру:
class имя_класса{
public static void main(String[] args){
программный_код
}
}
Название класса (параметр имя_класса) задается пользователем — это, фактиче-
ски, название программы. В месте, где указан параметр программный_код, указыва-
ется непосредственно выполняемый при вызове программы программный код.
В рассматриваемом примере программный код состоит всего из одной команды
System.out.println("Мы программируем на Java!"). Команда заканчивается точкой
с запятой — это стандарт для Java. Командой с помощью встроенного метода
println() на консоль (по умолчанию консолью является экран) выводится со-
общение "Мы программируем на Java!". Текст сообщения указан аргументом метода.
Метод вызывается через поле-объект out объекта потока стандартного вывода
System. Подробнее система ввода-вывода обсуждается во второй части книги, по-
сле того как мы подробнее познакомимся с классами и объектами. Пока же сле-
дует запомнить, что для вывода информации на экран в консольных приложе-
ниях используется инструкция вида System.out.println(), где в круглых скобках
указывается выводимый текст, числовые значения, имена переменных и т. д. —
все то, что можно указывать аргументом метода println(), и каковы будут по-
следствия, описано в главе 8, посвященной работе с объектами класса String
и StringBuffer.
Если читатель испытывает трудности с компиляцией и запуском программы из
листинга 1.1, рекомендуем ему обратиться к приложению в конце книги, посвя-
щенному методам практического использования среды разработки NetBeans.
Комментарии
Обычно программы пишут для того, чтобы обрабатывать данные. Методы и воз-
можности по обработке данных в значительной степени зависят от типа данных.
Язык Java относится к строго типизованным языкам. Это означает, что любая
переменная в программе относится к определенному типу данных — одному
и только одному. В Java все данные можно разделить на простые и ссылочные.
Ссылочные данные реализуются через иерархию классов. Простые данные —
это скорее дань традиции. Забегая наперед, отметим, что для простых типов
данных существуют ссылочные аналоги.
Разница между простыми и ссылочными типами на практике проявляется при
передаче аргументов методам. Простые типы данных передаются по значению,
ссылочные — через ссылку. Читателям, знакомым хотя бы с одним из современ-
ных языков программирования, эти термины должны быть знакомы. Способы
передачи аргументов методам в языке Java подробно обсуждаются в главе 4,
посвященной работе с классами и объектами. Пока же заметим, что простые
типы данных являются, по сути, базовыми. Именно данные этих типов будут
наиболее часто использоваться в первой части книги.
В Java существует четыре группы базовых типов: для работы с целыми числами,
для работы с числами в формате с плавающей точкой (действительные числа),
20 Глава 1. Основы Java
Приведение типов
byte a=1,b=2,c;
// Нет ошибки – явное приведение типа:
c=(byte)(a+b);
Командой (byte)(a+b) вычисляется сумма значений переменных a и b, а резуль-
тат преобразуется к типу byte. Поскольку в правой части от оператора при-
сваивания стоит переменная того же типа, проблем не возникает. Тем не менее
следует понимать, что явное приведение типа потенциально опасно, поскольку
может приводить к потере значения. Такие ситуации должен отслеживать про-
граммист — системой они не отслеживаются.
Аналогичную процедуру можно применять и к литералам. Кроме того, изменять
тип литералов можно с помощью суффиксов. Так, суффикс L у целочисленного
литерала (например, 123L) означает, что он принадлежит к типу long, а суф-
фикс F у литерала, обозначающего действительное число (например, 12.5F), озна
чает, что этот литерал относится к типу float. В свете сказанного корректными
являются такие команды:
float x=2.7F;
float x=(float)2.7;
Кроме прочего, явное приведение типов часто используется вместе с операто-
ром деления.
В Java, как и в С++, допускается динамическая инициализация переменных.
При динамической инициализации значение переменной присваивается при
объявлении, причем значением является выражение, содержащее другие пере-
менные. Пример динамической инициализации переменной:
int a=3,b=4;
int c=a*a+b*b;
В данном случае переменная c инициализируется выражением a*a+b*b, то есть
получает значение 25. Главное и единственное условие для динамической ини-
циализации — все переменные, входящие в соответствующее выражение, долж-
ны быть предварительно объявлены и им должны быть присвоены значения.
Приоритет Операторы
1 Круглые скобки ( ), квадратные скобки [ ] и оператор «точка»
2 Инкремент ++, декремент --, отрицания ~ и !
3 Умножение *, деление / и вычисление остатка %
4 Сложение + и вычитание -
5 Побитовые сдвиги >>, << и >>>
6 Больше >, больше или равно >=, меньше или равно <= и меньше <
7 Равно == и неравно !=
8 Побитовое И &
9 Побитовое исключающее ИЛИ ^
10 Побитовое ИЛИ |
11 Логическое И &&
12 Логические ИЛИ ||
13 Тернарный оператор ?:
14 Присваивание = и сокращенные формы операторов вида op=
Примеры программ
Далее рассмотрим некоторые задачи, которые иллюстрируют возможности Java
и специфику синтаксиса этого языка.
Примеры программ 33
Орбита спутника
Следующая задача иллюстрирует работу с большими числами. Состоит она
в вычислении высоты орбиты спутника над поверхностью Земли, если известны
Примеры программ 37
Комплексные числа
Рассмотрим программу, в которой вычисляется целочисленная степень ком-
плексного числа. Напомним, что комплексным называется число в виде z = x + iy,
где x и y — действительные числа, а мнимая единица i2 = –1. Величина Re(z) = x
называется действительной частью комплексного числа, а величина Im(z) = y —
мнимой. Модулем комплексного числа называется действительная величина
r = x 2 + y 2 . Каждое комплексное число может быть представлено в тригоно-
метрическом виде z = r exp(ij) = r cos( j) + ir sin( j), где модуль комплексного чис-
ла r и аргумент j связаны с действительной x и мнимой y частями комплексного
числа соотношениями x = r cos( j) и y = r sin( j).
Если комплексное число z = x + iy необходимо возвести в целочисленную сте-
пень n, результатом является комплексное число zn = rn exp(inj) = rn cos(nj) +
+ irn sin(nj). Этим соотношением воспользуемся в программе для вычисления
целочисленной степени комплексного числа. Программный код приведен в ли-
стинге 1.6.
double x=1.0,y=-1.0;
int n=5;
double r,phi;
double Re,Im;
r=Math.sqrt(x*x+y*y);
phi=Math.atan2(y,x);
Re=Math.pow(r,n)*Math.cos(n*phi);
Im=Math.pow(r,n)*Math.sin(n*phi);
System.out.println("Re="+Re);
System.out.println("Im="+Im);}
}
В программе на основании действительной и мнимой частей исходного ком-
плексного числа вычисляются модуль и аргумент этого числа. На основании по-
лученных значений вычисляются действительная и мнимая части комплексного
числа, возведенного в целочисленную степень.
Действительные переменные x и y определяют действительную и мнимую части
исходного комплексного числа. Целочисленная переменная n содержит значе-
ние степени, в которую возводится комплексное число. В переменные Re и Im за-
писываются соответственно действительная и мнимая части комплексного числа-
результата возведения в степень.
Переменные r и phi типа double предназначены для записи в них модуля и ар-
гумента комплексного числа. Для вычисления модуля используется функция
вычисления квадратного корня Math.sqrt().
Аргумент комплексного числа вычисляется с помощью функции Math.atan2().
Аргументом функции atan2() указываются ордината и орта точки, а в качестве
результата возвращается полярный угол направления на эту точку. Для комплекс-
ного числа это означает, что результатом вызова функции, если первым аргумен-
том указать мнимую часть, а вторым действительную, является его аргумент.
Возведение в целочисленную степень выполняется с помощью функции Math.
pow(). Первым аргументом функции указывается возводимое в целочисленную
степень число, вторым аргументом — степень, в которую возводится число.
После выполнения всех необходимых расчетов действительная и мнимая части
комплексного числа-результата возведения в степень выводятся на экран. В ре-
зультате выполнения программы получаем:
Re=-4.000000000000003
Im=4.000000000000001
Справедливости ради следует отметить, что работу с комплексными числами
все же лучше реализовывать с помощью классов и объектов.
Прыгающий мячик
Рассмотрим такую задачу. Тело (упругий мячик) бросают под углом к горизонту
с некоторой начальной скоростью. При падении мячика на ровную горизон-
тальную поверхность происходит упругое отбивание, так что горизонтальная
40 Глава 1. Основы Java
System.out.println("Параметры:");
System.out.println("a="+a);
System.out.println("b="+b);
System.out.println("c="+c);
System.out.print("Решение для x: ");
// Вычисление решения уравнения и вывод на экран:
System.out.println(state?Math.asin(c/Math.sqrt(a*a+b*b))-alpha:"решений нет!");
}}
Основное место в программе — использование тернарного оператора в послед-
ней команде вывода на экран значения для корня уравнения. Предварительно
выводится справочная информация о значениях параметров уравнения.
В последней команде вывода аргументом метода println() указано выражение:
state?Math.asin(c/Math.sqrt(a*a+b*b))-alpha:"решений нет!"
Это результат вычисления тернарного оператора, проверяемым условием в кото-
ром указана логическая переменная state. Ранее значение этой переменной при-
сваивается командой state=a*a+b*b>=c*c. Значение переменной равно true в том
случае, если уравнение имеет решения, и false — если не имеет. В случае если
значение переменной state равно true, тернарным оператором в качестве резуль-
тата возвращается числовое значение Math.asin(c/Math.sqrt(a*a+b*b))-alpha, где
переменной alpha предварительно с помощью команды alpha=Math.asin(a/Math.
sqrt(a*a+b*b)) присвоено значение. В этих выражениях использованы встроен-
ные функции asin() и sqrt() для вычисления арксинуса и корня квадратного.
Таким образом, при истинном первом операнде тернарного оператора в качестве
значения возвращается решение уравнения. Если значение первого операнда
тернарного оператора (переменная state) равно false, в качестве результата воз-
вращается текст "решений нет!". Хотя при разных значениях первого операнда
возвращаются значения разного типа, поскольку вся конструкция указана ар-
гументом метода println() за счет автоматического приведения типов, в обоих
случаях результат преобразуется в текстовый формат. Результат выполнения
программы имеет вид:
Уравнение a*cos(x)+b*sin(x)=c
Параметры:
a=5.0
b=3.0
c=1.0
Решение для x: -0.8580262366249893
Если поменять значения исходных параметров уравнения, можем получить такое:
Уравнение a*cos(x)+b*sin(x)=c
Параметры:
a=5.0
b=3.0
c=10.0
Решение для x: решений нет!
44 Глава 1. Основы Java
Резюме
1. Язык программирования Java является полностью объектно-ориентированным.
Для создания даже самой простой программы необходимо описать, по крайней
мере, один класс. Этот класс содержит метод со стандартным названием main().
Выполнение программы отождествляется с выполнением этого метода.
2. В методе main() можно объявлять переменные. Для объявления переменной
указывается тип переменной и ее имя. Переменной одновременно с объ-
явлением можно присвоить значение (инициализировать переменную).
Переменная должна быть инициализирована до ее первого использования.
3. Существует несколько базовых типов данных. При вычислении выражений
выполняется автоматическое приведение типов. Особенность приведения
типов в Java состоит в том, что оно осуществляется без потери значений.
Также можно выполнять явное приведение типов, для чего перед выражением
в круглых скобках указывается идентификатор соответствующего типа.
4. Основные операторы Java делятся на арифметические, логические, побитовые
и сравнения. Арифметические операторы предназначены для выполнения
таких операций, как сложение, вычитание, деление и умножение. Логические
операторы предназначены для работы с логическими операндами и позво-
ляют выполнять операции отрицания, ИЛИ, И, ИСКЛЮЧАЮЩЕГО ИЛИ.
Операторы сравнения используются, как правило, при сравнении (на пред-
мет равенства или неравенства) числовых операндов. Результатом сравнения
является логическое значение (значение логического типа). Побитовые опе-
раторы служат для выполнения операций (логических) на уровне битовых
представлений чисел, а также побитовых сдвигов вправо и влево в побитовом
представлении числа.
5. В Java для основных арифметических и побитовых операторов, используемых
в комбинации с оператором присваивания для изменения значения одного из
операндов, имеются упрощенные формы. В частности, команда вида x =x op y
может быть записана как x op=y, где через op обозначен арифметический или
побитовый оператор.
6. В Java есть тернарный оператор, который представляет собой упрощенную
форму условного оператора. Первым его операндом указывается логическое
выражение. В зависимости от его значения в качестве результата возвраща-
ется второй или третий операнд.
Глава 2. Управляющие инструкции Java
Мы никогда ничего не запрещаем! Мы только советуем!
Из к/ф «Забытая мелодия для флейты»
// Условная инструкция:
if(x!=0){
z=y/x;
System.out.println("Значение z="+z);}
else System.out.println("Деление на нуль!");
}
}
В программе объявляются три целочисленные переменные x, y и z. Первым
двум переменным сразу при объявлении присваиваются значения. В условной
инструкции переменная x проверяется на предмет отличия ее значения от нуля
(условие x!=0). Если значение переменной не равно нулю, переменной z при-
сваивается результат деления значения переменной y на значение переменной x.
После этого выводится сообщение с указанием значения переменой z.
Если проверяемое условие ложно (то есть значение переменной x равно нулю),
выводится сообщение "Деление на нуль!". Для приведенных в листинге 2.1 зна-
чений переменных в результате выполнения программы появится сообщение
Значение z=2.
Еще один пример использования условной инструкции if() в упрощенной фор-
ме приведен в листинге 2.2.
// Индексная переменная:
int i;
// Переменная для вычисления суммы:
int sum=0;
// Инструкция цикла:
for(i=1;i<=100;i++){
sum+=i;}
System.out.println("Сумма чисел от 1 до 100: "+sum);}
}
Программой вычисляется сумма натуральных чисел от 1 до 100. Для этого вво-
дится целочисленная переменная sum, которая инициализируется с начальным
нулевым значением — в эту переменную записывается значение суммы. Вы-
числение суммы осуществляется посредством инструкции цикла. В нем исполь-
зуется целочисленная индексная переменная i. Объявляется переменная вне
инструкции цикла. В первом блоке (блок инициализации) индексной перемен-
ной присваивается значение 1. Проверяется условие i<=100, то есть инструкция
цикла выполняется до тех пор, пока значение индексной переменной не превы-
сит значение 100. В теле инструкции цикла переменная sum увеличивается на
текущее значение индексной переменной i. В третьем блоке инструкции цикла
командой i++ значение индексной переменной увеличивается на единицу.
Последней командой программы выводится сообщение о значении суммы. В ре-
зультате выполнения программы мы получаем сообщение:
Сумма чисел от 1 до 100: 5050
Чтобы посчитать сумму нечетных чисел в указанном диапазоне, в третьем бло-
ке изменения индексной переменной команду i++ достаточно заменить коман-
дой i+=2. Кроме того, индексную переменную можно инициализировать прямо
в первом блоке инструкции цикла. Пример измененной программы для вычис-
ления суммы нечетных чисел приведен в листинге 2.7.
int sum=0,i=1;
// Инструкция цикла с пустыми блоками:
for(;i<=100;){
sum+=i;
i+=2;}
System.out.println("Сумма нечетных чисел от 1 до 100: "+sum);}
}
Индексная переменная i и переменная для вычисления суммы sum инициали-
зируются при объявлении вне инструкции цикла. Поэтому в блоке инициали-
зации ничего инициализировать не нужно, и блок оставлен пустым (хотя точка
с запятой все равно ставится). Второй блок с проверяемым условием остался
неизменным. Третий блок также пустой. Команда i+=2, которая изменяет значе-
ние индексной переменной, вынесена в тело инструкции цикла.
Ситуацию можно усугубить, что называется, до предела, видоизменив програм-
му так, чтобы все три блока инструкции цикла были пустыми. Пример приведен
в листинге 2.10.
Инструкция do-while()
В Java, в отличие от языка С++, нет инструкции перехода goto(). Тем не менее
в Java могут использоваться метки. Обычно для этого применяют инструкции
break() и continue().
С инструкцией break() мы уже сталкивались. В общем случае она завершает ра-
боту инструкции цикла или инструкции выбора. Если после инструкции break()
указать метку, то управление передается команде, размещенной за помеченной
этой меткой инструкцией цикла, выбора или блоком команд (напомним, блок
команд заключается в фигурные скобки).
Инструкция continue() используется для завершения текущего цикла в инструк-
ции цикла и переходу к выполнению следующего цикла. Если после инструк-
ции continue() указать метку, то выполняется переход для выполнения итерации
помеченного меткой внешнего цикла. Таким образом, инструкция continue()
с меткой применяется только тогда, когда имеются вложенные циклы.
62 Глава 2. Управляющие инструкции Java
Примеры программ
Далее рассматриваются некоторые программы, в которых используются услов-
ные инструкции и инструкции цикла.
Вычисление экспоненты
В Java существует встроенная экспоненциальная функция Math.exp(),
результатом выполнения которой по аргументу x является значение ex, где
e = 2,718281828 — постоянная Эйлера. Для вычисления экспоненты использует-
ся сумма, которая представляет собой разложение экспоненциальной функции
в ряд Тейлора в окрестности нуля:
∞
xk x2 x3
ex = ∑ =1+ x + + + ...
k =0 k ! 2! 3!
Разумеется, на практике вычислить бесконечную сумму невозможно, поэтому
ограничиваются вычислением суммы для конечного количества слагаемых —
чем их больше, тем выше точность. В листинге 2.14 приведен пример програм-
мы, в которой на основе приведенной функции вычисляется значение экспо-
ненты. В этом случае используется инструкция цикла.
Числа Фибоначчи
Числами Фибоначчи называется последовательность натуральных чисел, пер-
вые два из которых равны единице, а каждое следующее число в последователь-
ности равняется сумме двух предыдущих. В листинге 2.15 приведен пример
программного кода, в котором на экран выводятся числа из последовательности
Фибоначчи. Как и в предыдущем случае, основу этой программы составляет
инструкция цикла.
Полет в атмосфере
Рассмотрим еще одну задачу, в которой вычисляется траектория движения тела,
брошенного под углом к горизонту с известной начальной скоростью при усло-
вии, что на тело, кроме силы тяжести, действует еще и сила сопротивления
воздуха. Предполагаем, что в атмосфере в зависимости от высоты над поверх-
ностью сила сопротивления воздуха различна: в первом нижнем слое она про-
порциональна квадрату скорости тела (и направлена против вектора скорости),
во втором, центральном слое, сила сопротивления воздуха пропорциональна
скорости тела, а в третьем, верхнем слое атмосферы, сила сопротивления воз-
духа отсутствует.
В программе задаются начальная скорость тела V, угол к горизонту α, под ко-
торым тело брошено, масса тела m, высота H1 (на этой высоте заканчивается
первый слой), высота H2 (на ней заканчивается второй слой), ускорение сво-
бодного падения g, коэффициенты пропорциональности γ 1 и γ 2 для силы
74 Глава 2. Управляющие инструкции Java
y n +1 = y n + U n ∆t ,
F x ( x , y )
Vn +1 = Vn − ∆t ,
m
F y ( x , y )
U n +1 = U n − g ∆t − ∆t .
m
В начальный момент, то есть при n = 0 , x 0 = 0 , y 0 = 0 , V0 = V cos(α)
и U 0 = V sin(α) , где V — модуль вектора начальной скорости, а α — угол гори-
зонта, под которым тело брошено.
Что касается проекций силы сопротивления воздуха, то для первой воздушной
зоны (первый слой, определяется условием y < H 1 ) проекции силы сопротив-
ления воздуха определяются соотношениями:
F x = γ 1Vn Vn2 + U n2 ,
F y = γ 1U n Vn2 + U n2 .
Переменная Назначение
g Переменная, содержащая значение для ускорения свободного падения
m Масса тела
V Начальная скорость тела (модуль)
alpha Угол к горизонту в градусах, под которым брошено тело
Примеры программ 77
Переменная Назначение
Резюме
1. Для создания точек ветвления в алгоритмах используют инструкции цикла
и условные инструкции. В Java применяются инструкции цикла for(), while()
и do-while(). Условные инструкции Java: if() и switch(). Последнюю обычно
называют инструкцией выбора.
Резюме 79
элементы равны –1. Таким образом, всего три единичных элемента и три эле-
мента со значением –1, остальные равны нулю.
Символьные массивы
— Что вы делаете?
— Не видите? Стреляю!
— Странный способ украшать дом
монограммой королевы.
Из к/ф «Приключения Шерлока Холмса
и доктора Ватсона»
одинаковый тип, у них разные размеры. Далее с помощью еще одной инструк-
ции цикла элементы массива data выводятся с интервалом в одну строку (для
вывода значений без перехода к новой строке используем метод print()). В ре-
зультате мы получаем числовой ряд:
1 3 5 7 9 11 13 15 17 19
Это те значения, которыми инициализировался массив nums. Интерес в данном
случае представляет то обстоятельство, что в инструкции цикла, обеспечиваю-
щей вывод значений массива data, верхняя граница для индексов элементов
массива определяется через свойство length массива data. Массив инициализи-
ровался с размером 20, а в конечном итоге его размер оказался равным 10! При-
чина очевидна. После выполнения команды data=nums переменная массива data
начинает ссылаться на тот же массив, что и переменная массива nums.
Особенности сравнения массивов на предмет равенства (неравенства) иллю-
стрируются программным кодом листинга 3.8.
Примеры программ
В этом разделе рассматриваются некоторые программы, в которых в том или
ином виде используются массивы. Для удобства программы разбиты по темати-
ческим группам. Кроме того, некоторые программы, в которых совместно с мас-
сивами используются функции (методы) и классы, рассматриваются в следую-
щих главах.
Умножение векторов
Через массивы очень удобно реализовывать в программах операции с векто-
рами. В частности, рассмотрим программу, в которой вычисляется
скалярное
и векторное произведения векторов. Напомним, что если a и b — это векторы
в Декартовом пространстве, скалярным произведением этих векторов называ-
ется число:
3
a × b = ∑ a k bk .
k =1
Примеры программ 93
Числа Фибоначчи
В предыдущей главе рассматривалась программа, в которой вычислялись числа
из последовательности Фибоначчи. Здесь мы рассмотрим программу, в которой
числами Фибоначчи заполняется массив. Программный код приведен в листин-
ге 3.10.
Работа с полиномами
Массивы могут быть полезными при работе с выражениями полиномиального
вида. Напомним, что полиномом степени n называется функция вида:
n
Pn ( x) = ∑ a k x k.
k =0
// Производная:
Q+=b[k]*q;
// Изменение множителя:
q*=x;}
// Последнее слагаемое полинома:
P+=a[a.length-1]*q;
// Вывод результата:
System.out.println("Полином P(x)="+P);
System.out.println("Производная P'(x)="+Q);}
}
В программе командой double[] a=new double[]{1,-3,2,4,1,-1} объявляется, соз-
дается и инициализируется массив с коэффициентами полинома. Командой
double[] b=new double[a.length-1] объявляется и создается массив для записи ко-
эффициентов полинома-производной. Размер этого массива на единицу меньше
размера первого массива. Здесь принята во внимание та особенность, касающая-
ся производной, что этот полином степени на единицу меньше степени исходно-
го полинома. Размер первого полинома возвращается инструкцией a.length.
Переменная x типа double содержит значение аргумента полинома, а перемен-
ная q того же типа представляет собой степенной множитель, используемый
в дальнейшем при вычислении значений полинома и производной от него. На-
чальное значение этой переменной равно 1. Целочисленная переменная k слу-
жит в инструкции цикла для индексации элементов массива, а переменные P и Q
типа double с нулевыми начальными значениями — для записи вычисляемых
в программе значений полинома и производной соответственно.
В инструкции цикла переменная k получает значения от 0 до b.length-1, что со-
ответствует диапазону индексации элементов массива b с коэффициентами для
полинома-производной. В рамках каждого цикла выполняется несколько команд.
Первой командой P+=a[k]*q изменяется значение полинома. Каждый раз добав-
ляется соответствующий полиномиальный коэффициент a[k], умноженный на
аргумент в соответствующей степени. Это значение вычисляется и записывается
в переменную q, начальное значение которой, напомним, равно единице. Коман-
дой b[k]=(k+1)*a[k+1] вычисляется коэффициент полинома-производной. Значе-
ние производной модифицируется командой Q+=b[k]*q, в которой использован
вычисленный на предыдущем этапе коэффициент полинома-производной, а ар-
гумент в нужной степени записан, как и ранее, в переменную q. Наконец, сама
эта переменная модифицируется командой q*=x (на следующем цикле степень
аргумента увеличивается на единицу).
После завершения цикла значение производной оказывается вычисленным,
хотя в полиноме не учтено еще одно последнее слагаемое. Эта ситуация исправ-
ляется командой P+=a[a.length-1]*q после инструкции цикла. В этой команде
используется последний элемент массива коэффициентов полинома и перемен-
ная q, которая после выполнения инструкции цикла содержит значение аргу-
мента в нужной степени.
98 Глава 3. Массивы
Сортировка массива
Существует несколько алгоритмов сортировки массивов. Достаточно популяр-
ным и простым, хотя и не очень оптимальным, является пузырьковая сортиров-
ка массива. Идея метода достаточно проста. Перебираются все элементы мас-
сива, причем каждый раз сравниваются два соседних элемента. Если элемент
с меньшим индексом больше элемента с большим индексом, элементы меняются
местами. После перебора всех элементов самый большой элемент оказывается
последним. После следующей серии с перебором и сравнением соседних эле-
ментов на «правильном» месте оказывается второй по величине элемент и т. д.
В результате элементы массива оказываются упорядоченными в порядке воз-
растания. Если нужно сортировать массив в порядке убывания, при переборе
и сравнении массива элементы меняются местами, если элемент с меньшим
индексом меньше элемента с большим индексом.
В листинге 3.12 приведен пример программы, в которой выполняется пузырько-
вая сортировка целочисленного массива.
// Результат:
System.out.println("\nМассив после сортировки:");
for(k=0;k<n;k++){
System.out.print(nums[k]+" ");}
}}
В программе объявляется целочисленный массив nums, и с помощью инструкции
цикла элементы массива заполняются случайными целыми числами. Для гене-
рирования случайных чисел служит функция Math.random(), которая возвращает
действительное число в диапазоне от 0 до 1. Для получения случайного целого
числа генерированное действительное число умножается на 5 и на размер мас-
сива (переменная n), после чего с помощью инструкции (int) явного приведения
типов результат путем отбрасывания дробной части приводится к целочислен-
ному формату. После вычисления очередного элемента массива он выводится на
экран. Элементы через пробел выводятся в одну строку.
Сортировка элементов массива выполняется с помощью вложенных инструк-
ций цикла. Индексная переменная m нумерует «проходы» — полный цикл пере-
бора и сравнения двух соседних элементов. После каждого такого «прохода»,
по меньшей мере, один элемент оказывается на «правильном» месте. При этом
нужно учесть, что когда предпоследний элемент занимает свою позицию, по-
следний автоматически тоже оказывается в нужном месте. Поэтому количество
«проходов» на единицу меньше количества элементов в массиве. Внутренняя
индексная переменная k нумерует элементы массива. Она изменяется в преде-
лах от 0 до n-m-1. Здесь принято во внимание, во-первых, то обстоятельство,
что при фиксированном значении k сравниваются элементы с индексами k и k+1,
поэтому нужно учитывать, что индекс последнего проверяемого элемента на еди-
ницу больше верхней границы изменения индекса k. Во-вторых, если какое-то
количество «проходов» уже выполнено, то такое же количество последних эле-
ментов массива можно не проверять.
После того как сортировка массива выполнена, с помощью инструкции цикла
результат выводится на экран. Этот результат может иметь следующий вид:
Исходный массив:
63 18 5 30 70 13 21 42 47 38 52 43 51 44 34
Массив после сортировки:
5 13 18 21 30 34 38 42 43 44 47 51 52 63 70
Поскольку массив заполняется случайными числами, от запуска к запуску ре-
зультаты (значения элементов массива) могут быть разными. Неизменным оста-
ется одно — после сортировки элементы массива располагаются в порядке воз-
растания.
Задача перколяции
Задача перколяции в том или ином виде имеет отношение к решению целого
ряда прикладных вопросов. У этой задачи есть несколько формулировок. Рас-
смотрим один из наиболее простых вариантов. Итак, имеется сетка из полых
трубочек, которая может пропускать жидкость. Затем случайным образом вы-
бирают какое-то количество узлов сетки и перекрывают их, так что они больше
не могут пропускать жидкость. Необходимо определить, поступит ли жидкость,
поданная с одного края сетки, на другой ее край.
Воспользуемся при составлении программы следующим подходом. Создадим
в программе квадратную матрицу (двухмерный массив), соответствующую сет-
ке, на которой изучается перколяция. Элементы матрицы соответствуют узлам
сетки. На начальном этапе элементы могут принимать два значения: 0 — если
узел может пропускать жидкость, и 1 — в противном случае. Заполнение эле-
ментов массива выполняется с помощью генератора случайных чисел. В частно-
сти, генерируется случайное число в диапазоне от 0 до 1. Если это число больше
определенного значения p, элемент матрицы получает значение 1. В противном
случае значением элемента матрицы является 0. Таким образом, параметр p
представляет собой вероятность того, что узел будет пропускать жидкость.
После заполнения перколяционной матрицы начинается «заливка» жидкости.
Узел, в который попала жидкость, мы будем отмечать в перколяционной матри-
це значением 2. На начальном этапе перебираются элементы первого столбца
матрицы, и если значение элемента равняется 0, оно меняется на значение 2.
Здесь принято во внимание, что после «выключения» части узлов сетки некото-
рые из них могут оказаться в первом столбце.
Основная часть программы обеспечивает последовательный перебор всех эле-
ментов перколяционной матрицы. Если значение элемента равно 2 (в этом узле
уже есть жидкость), соседним элементам, если их текущее значение равно 0
(узел может пропускать жидкость), присваивается значение 2. После перебора
всех элементов они снова начинают перебираться и т. д., пока за весь перебор
элементов перколяционной матрицы ни один из элементов не изменится.
Для проверки того, достигла ли жидкость конечной части сетки, просматривает-
ся последний столбик перколяционной матрицы. Если значение хотя бы одного
элемента в этом столбце равно 2, имеет место протекание жидкости.
Вся описанная процедура позволяет определить, имеет ли место протекание
жидкости для данной конфигурации выведенных из строя узлов. В общем слу-
чае желательно иметь более надежные и объективные показатели. Обычно изу-
чают зависимость вероятности того, что сетка пропускает жидкость, от вероят-
ности того, что выбранный случайным образом узел сетки пропускает жидкость
(упоминавшийся параметр p). Для вычисления такой зависимости необходимо
провести статистические усреднения: при данном фиксированном значении p
104 Глава 3. Массивы
проделать описанную процедуру несколько раз (чем больше — тем лучше) и вы-
числить вероятность пропускания сеткой жидкости как относительное значение
случаев, когда сетка пропускала жидкость, к общему количеству случаев иссле-
дования сетки на предмет пропускания жидкости.
В программе, представленной в листинге 3.15 для нескольких значений параме-
тра p (вероятность пропускания жидкости отдельным узлом сетки), вычисля-
ется вероятность пропускания жидкости всей сеткой. Полученные значения за-
носятся в массив. В результате выполнения программы данные из этого массива
в виде импровизированной таблицы выводятся на экран.
// Определение протекания:
do{
// Начальное состояние счетчика:
count=0;
// Изменение состояния узлов сетки:
for(i=0;i<n;i++){
for(j=0;j<n;j++){
if(A[i][j]==2){
if(i>0&&A[i-1][j]==0) {A[i-1][j]=2; count++;}
if(i<n-1&&A[i+1][j]==0) {A[i+1][j]=2; count++;}
if(j<n-1&&A[i][j+1]==0) {A[i][j+1]=2; count++;}
if(j>0&&A[i][j-1]==0) {A[i][j-1]=2; count++;}
}}}
}while(count>0);
// Проверка последнего столбца перколяционной матрицы:
for(i=0;i<n;i++){
if(A[i][n-1]==2){
P[1][m]+=(double)1/N;
break;}
}}}
// Вывод результата на экран:
System.out.print("Протекание узла \t");
for(m=0;m<=M;m++){
System.out.print(Math.round(P[0][m]*100)/100.0+(m!=M?"\t":"\n"));}
System.out.print("Протекание сетки\t");
for(m=0;m<=M;m++){
System.out.print(Math.round(P[1][m]*100)/100.0+(m!=M?"\t":"\n"));}
}}
Переменные, использованные в программе, описаны в табл. 3.1.
Переменная Описание
Целочисленная переменная, определяющая количество измерений,
на основе которых вычисляется оценка для вероятности пропускания
N сетки при данном значении вероятности пропускания узла. При увели-
чении значения этой переменной точность оценок повышается, равно
как и время расчетов
Целочисленная переменная, определяющая количество значений (M+1)
M вероятности пропускания узлов, для которых вычисляется вероятность
пропускания сетки
Целочисленная переменная, определяющая размер перколяционной
n сетки и, соответственно, размер матрицы A, в которую записывается со-
стояние узлов сетки
продолжение
106 Глава 3. Массивы
Переменная Описание
Целочисленная переменная-счетчик. Используется для подсчета коли-
чества элементов матрицы A, которым при определении пропускания
сетки было присвоено значение 2. При определении протекания сетки
запускается цикл, значение переменной count обнуляется, после чего
count проверяются все элементы матрицы A. При изменении значения эле-
мента матрицы значение переменной count увеличивается на единицу.
После перебора всех элементов значение count снова обнуляется, пере-
бираются все элементы матрицы A и т. д. Процесс продолжается до тех
пор, пока после перебора всех элементов матрицы значение переменой
count не изменится (останется нулевым)
Первое из набора значений для вероятности протекания узла. В про-
q грамме вычисляется вероятность протекания сетки для нескольких зна-
чений вероятности (точнее, (M+1)-го значения) протекания выбранного
случайно узла. Первое из этих значений равно q, последнее — q+M*dq
значения). Затем все в том же цикле вызывается еще один цикл (индексная
переменная k изменяется от 1 до N включительно), который обрабатывает
процесс проверки протекания перколяционной сетки при фиксированном
значении вероятности протекания узлов соответствующее количество раз. На
основании результатов работы этого цикла определяется оценка для вероят-
ности протекания сетки. В начале цикла случайным образом, в соответствии
с текущим значением вероятности протекания узлов сетки, элементы массива
A заполняются нулями и единицами. Для этого служит двойной цикл. Затем
с помощью еще одного цикла элементам массива A в первом столбце, значения
которых равны 0, присваиваются значения 2 — это означает, что в соответ-
ствующие узлы поступила жидкость. После этого начинается обработка про-
цесса заполнения сетки жидкостью. В частности, запускается цикл do-while().
В начале цикла переменной-счетчику count присваивается нулевое значение,
а проверяемым в цикле условием является count>0. В цикле do-while() пере-
бираются все элементы массива A. Если значение элемента равно 2, соседним
элементам, имеющим нулевые значения, также присваивается значение 2. Со-
седние элементы — это те, у которых один и только один индекс отличает-
ся на единицу от индексов текущего элемента. При этом нужно учесть, что
текущий узел может находиться не в центре сетки, а на ее границе. Поэтому
для соответствующего элемента операция смещения индекса на одну пози-
цию может привести к выходу за пределы массива A. В силу этого обстоятель-
ства проверяемое условие состоит не только в том, что значение элемента,
расположенного рядом с текущим, равно 0, но и в том, что текущий эле-
мент не является «граничным» и операция обращения к соседнему элементу
корректна. Указанное условие проверяется первым, а в качестве логического
оператора И используется сокращенный оператор &&. Напомним, что в этом
случае вычисляется первый операнд, и если он равен false, второй операнд
не вычисляется. Здесь это — важное обстоятельство, благодаря которому код
выполняется без ошибки.
Если хотя бы для одного элемента массива A значение изменено на 2, перемен-
ная count увеличивается на единицу. Таким образом, цикл do-while() выполня-
ется до тех пор, пока при переборе всех элементов массива A ни одно значение
не будет изменено.
После этого необходимо проверить результат — есть или нет протекание сетки.
Если протекание есть, это означает, что жидкость дошла до правого конца сетки,
а это, в свою очередь, означает, что последний столбец массива A содержит хотя
бы одно значение 2. Поиск этого значения осуществляется в еще одном цикле.
Если значение 2 найдено, вероятность P[1][m] увеличивается на величину 1/N,
после чего работа инструкции цикла заканчивается (командой break). На этом
основная, расчетная часть программы заканчивается. Далее результаты с по-
мощью двух инструкций цикла выводятся на экран. В частности, они могут вы-
глядеть следующим образом:
Протекание узла 0.57 0.58 0.59 0.6 0.61 0.62
Протекание сетки 0.0 0.07 0.4 0.78 0.95 1.0
108 Глава 3. Массивы
Резюме
1. Массивом называется совокупность переменных одного типа, к которым
можно обращаться по общему имени и индексу (или индексам). В Java все
массивы динамические — память под них выделятся в процессе выполнения
программы.
2. Создание массива можно условно разделить на два этапа. Во-первых, объ-
является переменная массива, которой впоследствии в качестве значения
присваивается ссылка на массив. Во-вторых, с помощью оператора new для
массива выделяется место. Результат (ссылка на массив) записывается в пере-
менную массива.
3. При объявлении переменной массива после идентификатора типа данных
указываются пустые квадратные скобки. Количество пар пустых скобок со-
ответствует размерности массива. При создании массива после оператора new
указывается тип элементов массива и в квадратных скобках — размер массива
по каждой из размерности.
4. При создании массива его элементы можно инициализировать (по умолча-
нию элементы созданного массива обнуляются). Список значений элементов
массива (список инициализации) указывается в фигурных скобках через за-
пятую. Этот список может указываться в команде объявления переменной
массива после имени переменной (через оператор присваивания). Можно
также указать список значений сразу после квадратных скобок после иден-
тификатора типа в инструкции выделения памяти под массив (оператором
new). В этом случае в квадратных скобках размер массива не указывается
(он определяется автоматически по количеству значений в списке инициа-
лизации).
5. Обращение к элементу массива выполняется в следующем формате: имя мас-
сива и в квадратных скобках индекс элемента. Индекс по каждой из размер-
ностей указывается в отдельных квадратных скобках. Индексация элементов
массива всегда начинается с нуля.
6. В Java выполняется проверка на предмет выхода индекса элемента массива
за допустимые границы. Длину массива (количество элементов) можно по-
лучить с помощью свойства length (указывается через точку после имени
массива).
Резюме 109
Знакомство с ООП
Классы и объекты
Чтобы решить проблему упорядочивания программного кода, было принято ре-
шение ввести четкое разграничение данных и методов обработки этих данных.
Более того, данные и соответствующие им методы объединили в одну структу-
ру, которая в ООП называется объектом.
Такой на первый взгляд искусственный прием позволяет четко разграничить
область применимости методов. Вся программа при этом имеет блочную струк-
туру, что существенно упрощает анализ программного кода. Но даже в таком
подходе было бы мало пользы, если бы каждый объект был абсолютно уникаль-
ным. Практика же такова, что каждый объект определяется некоторым общим
шаблоном, который называется классом. В рамках класса задается общий ша-
блон, то есть структура, на основе которой затем создаются объекты. Данные,
относящиеся к классу, называются полями класса, а программный код для их
обработки — методами класса.
В классе описывается, какого типа данные относятся к классу (данные называ-
ются полями класса), а также то, какие методы применяются к этим данным.
Затем в программе на основе того или иного класса создается экземпляр класса
(объект), в котором указываются конкретные значения полей и выполняются
необходимые действия над ними. Различие между классом и объектом поясним
на простом примере, не имеющем никакого отношения к программированию.
Поговорим о домашних животных, таких как коты и собаки. Проводя аналогию
с программированием, можем определить класс Кот и класс Собака. Опреде-
ление класса производится через указание полей (данных) и методов класса.
Для класса Кот в качестве полей укажем имя (кличку кота) и окрас (цвет). Для
класса Собака задаем поля имя (кличка собаки), окрас и породу. Помимо полей,
определим методы для этих классов. По большому счету метод — это то, что
112 Глава 4. Классы и объекты
может делать объект соответствующего класса (или что можно делать с объек-
том). Коты будут мяукать и ловить мышей, а собаки — лаять и вилять хвостом.
Отсюда методами класса Кот являются мяукать и ловить мышей, а класса Со-
бака — лаять и вилять хвостом. Таким образом, мы определили шаблоны, на
основании которых впоследствии будут создаваться экземпляры классов или
объекты. Разница между классом и объектом такая же, как между абстрактным
понятием и реальным объектом. При создании объекта класса задаем конкрет-
ные значения для полей. Когда мы говорим о собаке вообще, как понятии, мы
имеем в виду домашнее животное, у которого есть имя, окрас, порода и кото-
рое умеет лаять и вилять хвостом. Точно так же, понятие кот означает, что он
мяукает и ловит мышей, к нему можно обратиться по имени, и шубка у него
может быть какого-то цвета. Это — абстрактные понятия, которые соответству-
ют классу. А вот если речь идет о конкретном Шарике или Мурзике, то это уже
объекты, экземпляры класса.
Представим, что во дворе живут три собаки и два кота: Шарик (дворняжка
коричневого окраса), Джек (рыжий спаниель), Ричард (черная немецкая овчар-
ка), Мурзик (белый и пушистый кот) и Барсик (черный кот с белой маниш-
кой). Каждый из пяти этих друзей представляет собой объект. В то же время
они относятся к двум классам: Шарик, Джек и Ричард являются объектами
класса Собака, а Мурзик и Барсик — объектами класса Кот. Каждый объект
в пределах класса характеризуется одинаковым набором полей и методов. Од-
новременно с этим каждый объект уникален. Хотя Шарик, Джек и Ричард
являются объектами одного класса, они уникальны, поскольку у них разные
имена, породы и окрасы. Лают и виляют хвостом они тоже по-разному. Но даже
если бы мы смогли клонировать, например, Шарика и назвать пса тем же име-
нем, у нас, несмотря на полную тождественность обеих Шариков, было бы два
объекта класса Собака. Каждый из них уникален, причем не в силу каких-то
физических различий, а по причине того, что один пес существует независимо
от другого.
Забегая наперед, отметим, что данные и код внутри объекта могут быть от-
крытыми, доступными вне объекта, и закрытыми. В последнем случае доступ
к данным и коду может осуществляться только в рамках объекта.
С точки зрения указанного подхода класс является базовой единицей инкапсу-
ляции. Класс задает формат объекта, определяя тем самым новый тип данных
в широком смысле этого термина, включая и методы, то есть программный код
для обработки данных. Через концепцию класса данные связываются с про-
граммным кодом. В пределах класса данные представляются в виде переменных,
а программный код — в виде функций (подпрограмм). Функции и переменные
класса называются членами класса (соответственно, методами и полями).
Полиморфизм позволяет использовать один и тот же интерфейс для выполне-
ния различных действий. Здесь действует принцип «Один интерфейс — много
методов». Благодаря полиморфизму программы становятся менее сложными,
так как для определения и выполнения однотипных действий служит единый
интерфейс. Такой единый интерфейс применяется пользователем или програм-
мистом к объектам разного типа, а выбор конкретного метода для реализации
соответствующей команды осуществляется компьютером в соответствии с ти-
пом объекта, для которого выполняется команда.
Важнейшим механизмом в ООП является наследование. Именно наследование
позволяет усовершенствовать эволюционным способом программный код, со-
храняя при этом на приемлемом уровне сложность программы. Наследова-
ние — это механизм, с помощью которого один объект может получить свойства
другого объекта, что позволяет создавать на основе уже существующих объек-
тов новые объекты с новыми свойствами, сохраняя при этом свойства старых
объектов. Например, если мы решим создать новый класс Породистая собака,
который от класса Собака отличается наличием поля награды на выставках, то
в общем случае пришлось бы заново создавать класс, описывая в явном виде все
его поля и методы. В рамках ООП с помощью механизма наследования можно
создать новый класс Породистая собака на основе уже существующего класса
Собака, добавив в описании класса только новые свойства — старые наследу-
ются автоматически. Наследование — удобный и полезный механизм, который
детально описан в следующих главах книги.
Преимущества ООП
Применение концепции ООП позволило существенно упростить процесс напи-
сания программ и расширило возможности в составлении сложных программ-
ных кодов. Среди основных преимуществ ООП выделим следующие.
В ООП благодаря механизму наследования можно многократно использовать
созданный единожды программный код. Это позволяет существенно эконо-
мить время на создание нового кода.
Все ООП-программы достаточно хорошо структурированы, что улучшает
их читабельность, да и работать с таким структурированным программным
кодом намного приятнее.
114 Глава 4. Классы и объекты
Статические элементы
Как уже отмечалось, помимо обычных полей и методов, в классе могут быть ста-
тические члены. От нестатических членов статические принципиально отлича-
ются тем, что они общие для всех объектов данного класса. Например, если речь
идет о нестатическом поле, то у каждого объекта класса это поле имеет свое уни-
кальное для объекта значение. Если поле является статическим, то у всех объ-
ектов значение этого поля одно и то же. В некотором смысле статические поля
напоминают глобальные переменные языка С++, хотя аналогия достаточно натя-
нутая. Со статическими методами ситуация несколько сложнее, поэтому сначала
рассмотрим особенности объявления и использования статических полей.
Для объявления статического члена класса, в том числе статического поля, ис-
пользуется идентификатор static. Синтаксис описания статического поля сле-
дующий:
static тип_поля имя_поля;
Перед ключевым словом static также может следовать спецификатор доступа
к полю (см. далее в этой главе). Инициализация значения статического поля
Статические элементы 119
далеко не всегда к членам класса можно получить доступ вне пределов класса,
то есть из кода, не входящего в тело класса.
В Java в зависимости от доступности все члены класса можно разделить на три
группы: открытые, закрытые и защищенные. Во всех рассматривавшихся ранее
примерах все члены классов были открытыми и, в силу этого обстоятельства,
доступными за пределами класса. Таким образом, открытые члены класса — это
члены класса, которые доступны вне этого класса. Если в каком-то месте про-
граммы создается объект класса, то к полям и методам этого объекта можно
обращаться способом, описанным ранее (например, в формате объект.метод или
объект.поле).
Если поле или метод является закрытым, ничего из упомянутого сделать не
удастся. К закрытым членам класса доступ осуществляется только в пределах
класса. К закрытому полю можно обращаться в методах класса, но нельзя об-
ратиться к полю вне класса. Закрытые методы класса могут вызываться только
методами класса, но не могут вызываться извне класса.
Разница между закрытыми и защищенными членами класса проявляется толь-
ко при наследовании. Если о наследовании речь не идет, то можно полагать, что
защищенный член класса — это аналог закрытого члена.
Нельзя сказать, что все сказанное полностью описывает ситуацию с закрытыми
и открытыми членами класса. К этому вопросу мы еще вернемся при изучении
наследования, после того как познакомимся с пакетами. Пока же ограничимся
таким несколько упрощенным взглядом на предмет.
Для определения уровня доступности используются три идентификатора: public,
private и protected — идентификаторы доступа соответственно для открытых,
закрытых и защищенных членов. Идентификатор доступа указывается для каж-
дого члена класса отдельно. Здесь проявляется отличие языка Java от языка
С++: в С++ идентификаторы доступа указываются для групп членов.
Если идентификатор доступа не указан вовсе, соответствующий член считается
открытым. Именно этой особенностью мы пользовались ранее, когда не указы-
вали для членов класса идентификаторы доступа. Обращаем внимание читате-
лей, знакомых с С++: там по умолчанию члены класса считаются закрытыми.
В листинге 4.4 приведен пример использования открытых и закрытых членов
класса.
System.out.println("b="+b);}
// Открытый метод:
public void setab(int x,int y){
// Обращение к закрытым полям в классе:
a=x;
b=y;
System.out.println("Присвоены значения полям!");}
// Открытый метод:
void getab(){
System.out.println("Проверка значений полей:");
// Обращение к закрытому методу в классе:
showab();}
}
class PrivateDemo{
public static void main(String[] args){
// Создание объекта:
MyClass obj=new MyClass();
// Вызов открытых методов:
obj.setab(3,5);
obj.getab();
}}
В классе MyClass объявлены два закрытых целочисленных поля a и b. Поскольку
поля закрытые, присвоить им значения вне класса простым обращением к по-
лям невозможно. Для инициализации полей в классе объявляется метод setab().
У метода два целочисленных аргумента, значения которых присваиваются по-
лям a и b. Обращаем внимание, что метод setab() имеет доступ ко всем членам
класса, в том числе и к закрытым полям, поскольку метод описан в том же клас-
се, что и поля. Метод setab() описан с использованием идентификатора уровня
доступа public. Хотя в методе getab() идентификатор доступа не указан, метод
также является открытым (по умолчанию). В этом открытом методе выпол-
няется обращение к закрытому методу showab(), который обеспечивает вывод
значений закрытых полей a и b.
В главном методе программы класса PrivateDemo сначала создается объект obj
класса MyClass. Командой obj.setab(3,5) закрытым полям объекта obj присваи-
ваются значения 3 и 5 соответственно. Командой obj.getab() значения полей
выводятся на консоль. В результате выполнения программы получаем:
Присвоены значения полям!
Проверка значений полей:
a=3
b=5
Еще раз хочется отметить, что в методе main() нельзя, например, воспользовать-
ся инструкцией вида obj.a, obj.b или obj.showab(), поскольку соответствующие
члены класса MyClass являются закрытыми (private). В программе использован
следующий подход: доступ к закрытым членам осуществляется через открытые
124 Глава 4. Классы и объекты
Внутренние классы
Внутренний класс — это класс, объявленный внутри другого класса. Эту ситуа-
цию не следует путать с использованием в качестве поля класса объекта другого
класса. Здесь речь идет о том, что в рамках кода тела класса содержится описа-
ние другого класса, который и называется внутренним. Класс, в котором объ-
явлен внутренний класс, называется внешним. В принципе, внутренний класс
может быть статическим, но такие классы используются на практике крайне
редко, поэтому рассматривать мы их не будем, а ограничимся только нестатиче-
скими внутренними классами.
Внутренний класс имеет несколько особенностей. Во-первых, члены внутрен-
него класса доступны только в пределах внутреннего класса и недоступны во
внешнем классе (даже если они открытые). Во-вторых, во внутреннем классе
можно обращаться к членам внешнего класса напрямую. Наконец, объявлять
внутренние классы можно в любом блоке внешнего класса. Пример использова-
ния внутреннего класса приведен в листинге 4.6.
Внутренние классы 127
Анонимные объекты
Как уже отмечалось, при создании объектов с помощью оператора new возвраща-
ется ссылка на вновь созданный объект. Прелесть ситуации состоит в том, что эту
ссылку не обязательно присваивать в качестве значения переменной. В таких слу-
чаях создается анонимный объект. Другими словами, объект есть, а переменной,
которая бы содержала ссылку на этот объект, нет. С практической точки зрения
такая возможность представляется сомнительной, но это только на первый взгляд.
На самом деле анонимные объекты требуются довольно часто — обычно в тех
ситуациях, когда единожды используется единственный объект класса. Доста-
точно простой пример применения анонимного объекта приведен в листинге 4.7.
Примеры программ
Далее рассматриваются некоторые программы, в которых кроме главного класса
программы (класса, содержащего метод main()) описываются и используются
другие классы.
Схема Бернулли
Схемой Бернулли называется серия независимых испытаний, в каждом из ко-
торых может быть только один из двух случайных результатов — их принято
называть успехом и неудачей. Есть два важных параметра, которые определяют
все прочие свойства серии опытов: это вероятность успеха в одном опыте p
и количество опытов в серии n . Величина q = 1 − p называется вероятностью
неудачи в одном опыте. Достаточно часто на практике используется случайная
величина (назовем ее ξ ), которая определяется как число успехов в схеме Бер-
нулли. Математическое ожидание этой случайной величины M ξ = np , а диспер-
сия равна Dξ = npq . Среднее значение для количества успехов в схеме Бернул-
ли является оценкой математического ожидания, поэтому для схемы с большим
количеством испытаний с высокой вероятностью количество успехов в схеме
Бернулли близко к математическому ожиданию. Корень квадратный из диспер-
сии определяет характерную область разброса количества успехов по отноше-
нию к математическому ожиданию.
В листинге 4.8 приведен код программы, в которой для реализации схемы Бер-
нулли создается специальный класс.
Математические функции
Хотя в Java есть достаточно неплохая библиотека математических функций,
поэтому можно создать класс, в котором описать недостающие функции или
132 Глава 4. Классы и объекты
return s;}
// Ряд Фурье по косинусам:
static double FourCos(double x,double[] a){
int i,N=a.length;
double s=0;
for(i=0;i<N;i++){
s+=a[i]*Math.cos(Math.PI*x*i/L);}
return s;}
}
class MathDemo{
public static void main(String args[]){
System.out.println("Примеры вызова функций:");
// Вычисление экспоненты:
System.out.println("exp(1)="+MyMath.Exp(1,30));
// Вычисление синуса:
System.out.println("sin(pi)="+MyMath.Sin(Math.PI,100));
// Вычисление косинуса:
System.out.println("cos(pi/2)="+MyMath.Cos(Math.PI/2,100));
// Вычисление функции Бесселя:
System.out.println("J0(mu1)="+MyMath.BesselJ(2.404825558,100));
// Заполнение массивов коэффициентов рядов Фурье для функции y(x)=x:
int m=1000;
double[] a=new double[m];
double[] b=new double[m+1];
b[0]=MyMath.L/2;
for(int i=1;i<=m;i++){
a[i-1]=(2*(i%2)-1)*2*MyMath.L/Math.PI/i;
b[i]=-4*(i%2)*MyMath.L/Math.pow(Math.PI*i,2);}
// Вычисление функции y(x)=x через синус-ряд Фурье:
System.out.println("2.0->"+MyMath.FourSin(2.0,a));
// Вычисление функции y(x)=x через косинус-ряд Фурье:
System.out.println("2.0->"+MyMath.FourCos(2.0,b));
}}
В программе описывается класс MyMath, в котором объявлено несколько стати-
ческих функций. В частности, это функции вычисления экспоненты, косинуса
и синуса, а также функции Бесселя нулевого индекса. Во всех перечисленных
случаях для вычисления значений функций используется ряд Тейлора. Каждая
функция имеет по два аргумента: первый аргумент типа double определяет непо-
средственно аргумент математической функции, а второй целочисленный аргу-
мент определяет количество слагаемых в ряде Тейлора, на основании которых
вычисляется функция. Для экспоненты использован ряд:
N
xk
exp( x) ≈ ∑ .
k =0 k !
134 Глава 4. Классы и объекты
2L
синус-разложения коэффициенты разложения y n = (−1) n +1 , а для косинус-
n πn
2L((−1) − 1) L
разложения коэффициенты y n = 2 2
при n > 0 и y 0 = .
π n 2
Для заполнения массивов a и b соответствующими значениями в главном ме-
тоде программы используется цикл. После заполнения массивов командами
MyMath.FourSin(2.0,a) и MyMath.FourCos(2.0,b) вычисляются два различных ряда
Фурье для функции y( x) = x при значении x = 2 , то есть в обоих случаях точ-
ным результатом является значение 2. Желающие могут сопоставить точность
вычислений и количество слагаемых (размеры массивов a и b), оставленных
в рядах Фурье для вычисления этого результата.
class ObjList{
public static void main(String[] args){
// Исходный объект:
MyClass obj=new MyClass();
// Создание списка из 4-х объектов (начальный + еще 3 объекта):
obj.create(3);
// Проверка содержимого списка:
System.out.println("Значение поля number объектов:");
System.out.println("2-й после начального -> "+obj.getNumber(2));
System.out.println("4-й после начального -> "+obj.getNumber(4));
System.out.println("2-й после 1-го -> "+obj.next.getNumber(2));
}}
В программе создается класс MyClass, у которого всего два поля: целочисленное
поле number с нулевым значением по умолчанию и объектная переменная next
класса MyClass. В эту переменную записывается ссылка на следующий в спи-
ске объект. По умолчанию значение переменной присваивается ссылке this, что
означает ссылку на тот же объект, полем которого является переменная.
Для создания списка объектов предусмотрен метод create(). Метод не возвра-
щает результата и в качестве аргумента ему передается целое число, которое
определяет количество объектов, добавляемых в список. В методе объявляются
локальная целочисленная индексная переменная i, локальная объектная пере-
менная objA (текущий объект списка) класса MyClass с начальным значением
this — ссылкой на объект, из которого вызывается метод create(), а также объ-
ектная переменная objB (следующий элемент списка) того же класса MyClass.
Затем запускается цикл, в котором индексная переменная получает значения
от 1 до n (аргумент метода create()) с единичным шагом дискретности. Коман-
дой objB=new MyClass() в цикле создается новый объект класса MyClass, и ссылка
на этот объект присваивается в качестве значения переменной objB. Командой
objA.next=objB в поле next объекта objA записывается ссылка на объект objB. То
есть в поле next текущего объекта списка записывается ссылка на следующий
элемент списка. Далее командой objB.number=objA.number+1 полю number следую-
щего элемента списка присваивается значение, на единицу большее значения
поля number текущего элемента списка. Наконец, командой objA=objB переменной
objA присваивается ссылка на следующий элемент списка. На очередной итера-
ции новое значение получит и переменная objB. После завершения цикла пере-
менные objA и objB будут ссылаться на последний объект в списке. Полю number
этого объекта значение уже присвоено (при выполнении инструкции цикла).
Осталось только присвоить значение полю next этого объекта (по умолчанию
в этом поле содержится ссылка на объект-владелец поля). Новое значение полю
присваивается командой objA.next=this. В данном случае this — это ссылка на
объект, из которого вызывался метод create(), то есть ссылка на начальный объ-
ект списка. После этого список будет создан.
Метод getNumber() возвращает в качестве результата целочисленное значение
поля number объекта, который расположен в списке на указанное аргументом
138 Глава 4. Классы и объекты
Работа с матрицами
В листинге 4.11 приведен простой пример программы, в которой для работы
с квадратными матрицами создается специальный класс. В этом классе пред-
усмотрены методы для выполнения таких операций, как вычисление детерми-
нанта (определителя), транспонирования матрицы, вычисление следа (шпура)
матрицы, заполнение матрицы числами в различных режимах, вывод значений
матрицы на экран.
// Объектная переменная:
private int[][] Matrix;
// Определение размера и создание матрицы:
void setBase(int n){
this.n=n;
Matrix=new int[n][n];}
// Заполнение случайными числами:
void setRND(){
int i,j;
for(i=0;i<n;i++)
for(j=0;j<n;j++)
Matrix[i][j]=(int)(Math.random()*10);
}
// Заполнение одинаковыми числами:
void setVal(int a){
int i,j;
for(i=0;i<n;i++)
for(j=0;j<n;j++)
Matrix[i][j]=a;
}
// Заполнение последовательностью цифр:
void setNums(){
int i,j;
for(i=0;i<n;i++)
for(j=0;j<n;j++)
Matrix[i][j]=(i*n+j)%9+1;
}
// Единичная матрица:
void setE(){
int i;
setVal(0);
for(i=0;i<n;i++)
Matrix[i][i]=1;
}
// Отображение матрицы:
void show(){
int i,j;
for(i=0;i<n;i++){
for(j=0;j<n;j++){
System.out.print(Matrix[i][j]+(j==n-1?"\n":" "));}
}
}
// След матрицы:
int Sp(){
int i,s=0;
for(i=0;i<n;i++) s+=Matrix[i][i];
продолжение
140 Глава 4. Классы и объекты
Matrix[i][j]=Matrix[j][i];
Matrix[j][i]=s;}
}
}
class MatrixDemo{
public static void main(String[] args){
// Создание объекта:
SMatrix obj=new SMatrix();
// Определение размера матрицы и ее создание:
obj.setBase(3);
// Заполнение случайными числами:
obj.setRND();
// Единичная матрица:
//obj.setE();
// Заполнение единицами:
//obj.setVal(1);
// Заполнение последовательностью цифр:
//obj.setNums();
System.out.println("Исходная матрица:");
obj.show();
System.out.println("После транспонирования:");
// Транспонирование матрицы:
obj.trans();
obj.show();
// Вычисление следа матрицы:
System.out.println("След матрицы: "+obj.Sp());
// Вычисление определителя матрицы:
System.out.println("Определитель матрицы: "+obj.det());
}}
Основу программы составляет класс SMatrix. Класс каптирует в себе квадратную
матрицу, ссылка на которую (переменная массива Matrix) является закрытым
полем класса. Для удобства полагаем, что элементы матрицы — целые числа.
Еще одно закрытое целочисленное поле n определяет размер квадратной матри-
цы и, соответственно, размер двухмерного массива, через который эта матрица
реализована. Значения поля n и размер массива, на который ссылается перемен-
ная массива Matrix, должны, очевидно, изменяться синхронно. Поэтому данные
поля закрыты.
Несколько открытых методов класса позволяют заполнять матрицу числовыми
значениями. В частности, метод setRND() предназначен для заполнения матрицы
случайными целыми числами. Для генерирования случайного целого числа пред-
назначена функция Math.random(). Функцией в качестве результата возвращает-
ся псевдослучайное действительное неотрицательное число, не превышающее
единицу. Для получения на его основе целого числа служит команда (int)(Math.
random()*10) — полученное в результате вызова функции random() случайное
142 Глава 4. Классы и объекты
После транспонирования:
1 6 2 7 3
2 7 3 8 4
3 8 4 9 5
4 9 5 1 6
5 1 6 2 7
След матрицы: 20
Определитель матрицы: 0
Другие варианты читатель может исследовать самостоятельно.
d 2y
= Fy m .
dt 2
Здесь m — масса тела, а F x и F y — соответственно проекции на горизонталь-
ную и вертикальную координатные оси действующей на тело силы. В силу сде-
ланных предположений F x = −γV x и F y = − mg − γV y , где через g обозначено
dx
ускорение свободного падения, γ — коэффициент сопротивления, а V x =
dy dt
и Vy = являются проекциями скорости на координатные оси.
dt
Для решения задачи в числовом виде воспользуемся следующей итерационной
схемой. Предположим, в какой-то момент времени тело имеет координаты x
и y, а также скорость, проекции которой на координатные оси составляют V x
и V y. Чтобы рассчитать положение и скорость тела, которые у него будут через
время dt (этот параметр должен быть достаточно малым, чем меньше — тем
лучше), к текущей координате x добавляем величину V x dt, а к текущей коор-
динате y — величину V y dt. Поправка к новым значениями для компонент ско-
рости равняется F x dt m и F y dt m соответственно для V x и V y . Последовательно
выполняя такие итерации нужное количество раз (это количество определяется
как целая часть от выражения t dt , но обычно параметр dt задается на основе
времени t и количества итераций n как dt = t n), получим координаты тела
и его скорость в момент времени t .
Данная итерационная процедура реализована в программе, представленной
в листинге 4.12.
Примеры программ 147
double alpha=Math.round(Math.toDegrees(phi())*100)/100.0;
System.out.println("В момент времени t="+t+" секунд положение тела следующее.");
System.out.println("Высота: "+Math.round(y*100)/100.0+" метров над горизонтом.");
System.out.println("Расстояние от точки броска: "+Math.round(x*100)/100.0+"
метров.");
System.out.println("Скорость: "+Math.round(V()*100)/100.0+" метров в секунду.");
System.out.println("Угол к горизонту: "+alpha+" градусов.");}
}
// Внутренний класс для "силы":
class Force{
// Проекция на горизонтальную ось:
double x(Body obj){
return -gamma*obj.Vx;}
// Проекция на вертикальную ось:
double y(Body obj){
return -g*obj.m-gamma*obj.Vy;}
}}
class BodyFlightDemo{
public static void main(String[] args){
// Параметры (масса (в граммах), начальные координаты (в метрах),
// скорость (в м/с)), угол (в градусах) и время (в секундах)):
double[] params={100,0,0,150,30,5};
// Анонимный объект:
new BodyFlight(params);
}}
Для решения программными методами поставленной задачи создается класс
BodyFlight, в котором объявляются еще два внутренних класса: класс Body для
реализации объекта «тела» и класс Force для реализации объекта «силы».
Внутренний класс Body имеет пять полей типа double: масса тела m, координаты
x и y, а также проекции скорости на координатные оси Vx и Vy. Методом клас-
са V() в качестве значения возвращается модуль скорости тела (определяется
как V = V x2 + V y2 ). Методом phi() возвращается угол к горизонту, под которым
направлена траектория тела. Для вычисления результата предназначен встро-
енный метод atan2(), который возвращает угол (в радианах) точки, координа-
ты которой заданы аргументами метода (первый аргумент — координата по
вертикали, второй аргумент — координата по горизонтали). Метод set() пред-
назначен для того, чтобы задавать значения координат и проекций скорости
для тела (соответствующие значения указываются аргументами метода). Этот
метод, кроме прочего, вызывается в конструкторе класса. У конструктора пять
аргументов — при создании объекта для каждого из полей нужно указать зна-
чение.
Метод show() внутреннего класса Body предназначен для отображения таких
параметров, как координаты тела, его скорость и направление. Выводимые
150 Глава 4. Классы и объекты
значения имеют до двух цифр после запятой, для чего соответствующим обра-
зом округляются. Угол к тому же переводится в градусы.
Внутренний класс Force достаточно прост и содержит всего два метода x() и y()
для вычисления проекций действующей на тело силы. Объект, описывающий
это тело, передается аргументом методам.
Кроме внутренних классов, в классе BodyFlight описываются закрытые поля:
поле g (ускорение свободного падения), поле gamma (коэффициент сопротивле-
ния), поле n (количество итераций) описаны как final (значение полей изме-
нять нельзя) и static (статические). Закрытое поле t предназначено для записи
момента времени, для которого вычисляется положение тела. Закрытое поле
body — это объектная переменная класса Body. Переменная предназначена для
записи в нее ссылки на объект, положение которого вычисляется в главном ме-
тоде программы. Эта переменная используется в методе calculate() — закрытом
методе, который вызывается в конструкторе класса BodyFlight и предназначен
для непосредственного вычисления координат и компонентов скорости тела.
В методе реализована описанная ранее схема вычислений. В нем, кроме прочего,
создается объект внутреннего класса Force, который необходим для вычисления
изменения скорости тела.
Конструктору класса BodyFlight в качестве аргумента передается массив из пяти
элементов. Эти элементы последовательно определяют следующие параметры:
масса тела (в граммах), координаты (в метрах), модуль скорости (в метрах в се-
кунду), угол, под которым направлена скорость к горизонту (в градусах), момент
времени (в секундах), для которого вычисляется положение тела. Отметим, что
значение для массы из граммов должно быть переведено в килограммы, для
чего делится на 1000. Градусы при расчетах переводятся в радианы с помощью
функции Math.toRadians(), а для обратного преобразования (радиан в градусы)
служит функция Math.toDegrees(). Компоненты скорости тела по модулю скоро-
сти V и углу (к горизонту) α определяются соответственно как V x = V cos(α)
и V y = V sin(α) .
В результате выполнения программы получаем:
В момент времени t=5.0 секунд положение тела следующее.
Высота: 219.03 метра над горизонтом.
Расстояние от точки броска: 574.76 метра.
Скорость: 102.28 метра в секунду.
Угол к горизонту: 8.46 градусов.
Обращаем внимание, что в главном методе программы, кроме массива params
с исходными расчетными значениями, создается анонимный объект класса
BodyFlight. При этом все необходимые вычисления, включая вывод результатов
на экран, выполняются автоматически при вызове конструктора.
Резюме 151
Резюме
1. Любой объектно-ориентированный язык базируется на трех «китах»: инкап-
суляции, полиморфизме и наследовании. Под инкапсуляцией подразумевают
механизм, который позволяет объединить в одно целое данные и код для их
обработки. Базовой единицей инкапсуляции является класс. Конкретной
реализацией класса (экземпляром класса) является объект. Полиморфизм —
это механизм, который позволяет использовать единый интерфейс для вы-
полнения однотипных действий. Реализуется полиморфизм, кроме прочего,
через перегрузку и переопределение методов. Наследование позволит одним
объектам получать (наследовать) свойства других объектов.
2. Описание класса начинается с ключевого слова class, после чего следуют имя
класса и в фигурных скобках описание класса. В состав класса могут входить
поля (переменные) и методы (функции), которые называются членами класса.
При описании поля указываются его тип и имя. При описании метода ука-
зываются тип результата, имя метода, в круглых скобках список аргументов
и в фигурных скобках тело метода.
3. При описании членов класса могут использоваться дополнительные иден-
тификаторы, определяющие доступность членов и их тип. В частности, член
класса может быть статическим. В этом случае у всех объектов этого класса
соответствующее поле или метод един для всех объектов: и существующих,
и тех, что будут созданы. Статический член описывается в классе с идентифи-
катором static. Могут также использоваться идентификаторы, определяющие
доступность членов класса: public (открытые члены), private (закрытые чле-
ны) и protected (защищенные члены). По умолчанию члены класса считаются
открытыми — они доступны не только в самом классе, но и вне его.
4. Программа в Java может состоять (и обычно состоит) из нескольких классов.
Один из них содержит метод main(). При выполнении программы выполня-
ется метод main(). Метод не возвращает результат и объявляется с идентифи-
каторами public и static.
5. Создание объектов осуществляется с помощью оператора new. После операто-
ра указывается имя класса, на основе которого создается объект, с круглыми
скобками (в рассмотренных примерах пустыми). Так создается объект (для
него выделяется место в памяти), а в качестве результата возвращается ссылка
на созданный объект. Обычно эта ссылка присваивается в качестве значения
объектной переменной, которую отождествляют с объектом. Для объявления
объектной переменной указывают имя соответствующего класса и имя этой
переменной. На практике команды объявления объектной переменной и со
здания объекта (с присваиванием переменной ссылки на объект) объединяют
в одну команду. Если ссылка на созданный объект не присваивается никакой
объектной переменной, говорят об анонимном объекте.
6. В Java один класс может объявляться внутри другого класса. В этом случае
говорят о внутреннем классе. Особенность внутреннего класса состоит в том,
что он имеет доступ к полям и методам содержащего его класса (класса-
152 Глава 4. Классы и объекты
Перегрузка методов
типа int и с одним аргументом типа double. Несложно заметить, что вариант
метода с одним аргументом типа int в классе OverloadDemo2 не предусмотрен. Тем
не менее программа работает корректно:
Аргументы отсутствуют!
аргументы типа int: 10 и 20
аргумент типа double: 88.0
аргумент типа double: 12.5
Причем во втором случае целочисленное значение аргумента выводится в фор-
мате числа с плавающей точкой. Причина в том, что поскольку для метода test()
вариант с одним целочисленным аргументом не предусмотрен, имеет место ав-
томатическое приведение типа, то есть тип аргумента int расширяется до типа
double, после чего вызывается вариант метода с одним аргументом типа double.
Если в классе OverloadDemo2 описать вариант метода test() с одним аргументом
типа int, то будет вызываться этот вариант метода.
Конструкторы
При возвращении объекта как результата метода в качестве типа результата ука-
зывается имя класса объекта. Если результатом метода является объект, то
в теле метода должна быть инструкция return с указанным после нее объектом
соответствующего класса. Обычно предварительно создается локальный объект,
который и возвращается в качестве результата. В листинге 5.5 приведен при-
мер программного кода, в котором имеется конструктор копирования (создания
объекта на основе уже существующего объекта) и метод, возвращающий в каче-
стве результата объект.
Im+=obj.Im;}
}
class ObjsDemo{
public static void main(String[] args){
// Создание объектов:
MyObjs a=new MyObjs(1);
MyObjs b=new MyObjs(-3,5);
MyObjs c=new MyObjs(b);
// Вычисление "суммы" объектов:
c=a.getSum(b);
// Проверка результата:
c.show();
// Изменение объекта:
a.add(c);
// Проверка результата:
a.show();
}
}
Класс MyObjs имеет два поля Re и Im, метод set() с двумя аргументами для при-
сваивания значений полям, метод show() для отображения значений полей объ-
ектов, а также метод getSum(), с помощью которого вычисляется «сумма» двух
объектов. В результате выполнения метода создается объект, причем значения
его полей равны сумме соответствующих полей объекта, из которого вызыва-
ется метод, и объекта, переданного методу в качестве аргумента. В отличие от
этого метода, методом add() изменяется объект, из которого вызывается метод,
а результат методом add() не возвращается. Действие метода add() состоит в том,
что к полям объекта, из которого вызывается метод, прибавляются значения со-
ответствующих полей объекта-аргумента метода.
В методе getSum() командой MyObjs tmp=new MyObjs() создается локальный (до-
ступный только в пределах метода) объект tmp класса MyObjs. При создании объ-
екта использован конструктор без аргументов, поэтому при создании объект tmp
получает нулевые значения для полей, хотя это и не принципиально. Команда-
ми tmp.Re=Re+obj.Re и tmp.Im=Im+obj.Im полям tmp.Re и tmp.Im созданного объекта
присваиваются нужные значения — суммы соответствующих полей объекта, из
которого вызывается метод (поля Re и Im), и полей объекта obj, переданного
аргументом методу (поля obj.Re и obj.Im). После того как для локального объ-
екта tmp заданы все свойства, этот объект командой return tmp возвращается
в качестве результата.
Для понимания принципов работы метода следует учесть особенности возвра-
щения объекта в качестве результата. Так, если методом возвращается объект,
при выполнении метода выделяется место в памяти для записи результата. Этот
процесс (выделение места в памяти) не следует путать с созданием объекта.
После того как в методе выполнена инструкция возврата значения, локальный
объект, используемый как результат метода, копируется в место, выделенное
162 Глава 5. Методы и конструкторы
Примеры программ
Далее рассматриваются примеры, в которых применены некоторые полезные
при составлении эффективных программных кодов подходы, связанные с ис-
пользованием конструкторов и реализацией различных методов.
Интерполяционный полином
Важной задачей прикладного числового анализа является проблема интерполя-
ции и аппроксимации зависимостей, заданных в табулированном виде, то есть
в виде массивов узловых точек и значений некоторой, обычно неизвестной
функции в этих точках. Рассмотрим иллюстративный пример, позволяющий
получить некоторое представление о способах решения этой задачи методами
объектно-ориентированного программирования.
Примеры программ 167
1 n a n
b= ∑
n + 1 k =0
yk − ∑ xk .
n + 1 k =0
n
В методе setab() в инструкции цикла вычисляются суммы ∑ x k (переменная
n n n k =0
Sx), ∑ yk (переменная Sy), ∑ xk yk (переменная Sxy) и ∑ x k2 (переменная Sxx),
k =0 k =0 k =0
а затем на основе вычисленных значений определяются параметры регресси-
онной модели. Метод approx(), предназначенный для вычисления значения ре-
грессионной функции, достаточно прост и особых комментариев не требует. Не-
сколько сложнее вычисляется интерполяционный полином в методе interp().
При вычислении значения интерполяционного полинома вызывается закрытый
метод psi(). Это метод, предназначенный для расчета базисных функций интер-
поляционного полинома в схеме Лагранжа. Кратко опишем основные моменты
использованного подхода.
170 Глава 5. Методы и конструкторы
Геометрические фигуры
В листинге 5.9 приведен код программы, в которой реализован класс для работы
с некоторыми графическими объектами на плоскости. В частности, предлага-
ется класс для обработки таких геометрических фигур, как правильные много-
угольники. Класс содержит методы для их создания (определение координат
вершин), а также для вычисления их периметра и площади.
В классе Figures целочисленное закрытое поле n предназначено для записи ко-
личества вершин. Многоугольник определяется, фактически, набором точек на
плоскости. Для реализации объекта «точка» в классе Figures описывается вну-
тренний класс Point. У этого внутреннего класса есть символьное (тип char)
поле name для записи названия точки (латинская буква). Поля x и y типа double
предназначены для записи и хранения координат точки.
Конструктору внутреннего класса в качестве аргументов передаются символ
(буква) названия точки, а также две ее координаты. Метод dist() в качестве ре-
зультата возвращает расстояние от начала координат до точки, реализованной
через объект вызова. Расстояние вычисляется как корень квадратный из суммы
квадратов координат точки. Наконец, методом show() на экран выводится на-
звание точки с ее координатами в круглых скобках, разделенные запятой. При
выводе координат точки после запятой оставляется не более двух цифр. Для
округления используется метод Math.round().
Идея, положенная в основу алгоритма выполнения программы, следующая. На
основе начальной точки создается правильный многоугольник с указанным ко-
личеством вершин. Конструктор класса имеет три аргумента: количество вер-
шин многоугольника и координаты первой точки. Прочие точки находятся на
таком же расстоянии от начала координат, что и первая точка. Каждая следу-
ющая получается смещением точки против часовой стрелки на один и тот же
угол. Каждая точка — объект класса Point. Ссылки на эти объекты записываются
в закрытое поле points класса Figures. Поле points объявляется как переменная
массива, элементами которого являются переменные-ссылки на объекты класса
Point (соответствующая инструкция имеет вид Point[] points). Размер массива
определяется значением поля n. Поля n и points объявлены как закрытые для
предотвращения их несанкционированного или несинхронного изменения.
В классе также описан метод perimeter() для вычисления периметра и метод
square() для вычисления площади многоугольника. Метод dist(), описанный
172 Глава 5. Методы и конструкторы
в классе Figures, в качестве аргументов принимает два объекта класса Point, а ре-
зультатом метода является расстояние между соответствующими точками (корень
квадратный из суммы квадратов разностей соответствующих координат точек).
Все основные вычисления происходят при вызове конструктора. В первую оче-
редь там создается начальная точка (объект p класса Point). Для этого использу-
ется команда Point p=new Point('A',x,y). Первая точка имеет имя A, а ее коорди-
наты определяются вторым и третьим аргументами конструктора класса Figures.
Командой this.n=n полю n класса присваивается значение, переданное первым
аргументом конструктору. После этого командой points=new Point[n] создается
массив для записи ссылок на объекты точек. Угол phi0 на начальную точку и рас-
стояние r до нее вычисляются соответственно командами phi0=Math.atan2(y,x)
и r=p.dist(). Напомним, что инструкцией Math.atan2(y,x) возвращается угол на
точку с координатами x и y. Угол поворота определяется как phi=2*Math.PI/n.
Далее для расчета следующих точек запускается цикл. Одновременно с расче-
том имен и координат этих точек методом show() осуществляется вывод этих
данных на экран. Каждая новая вершина (ссылка на соответствующий объект)
записывается в качестве значения переменной p. Предварительно старая ссылка
заносится в поле-массив points. При вычислении имени новой точки к име-
ни старой точки (ссылка p.name) прибавляется единица. Результатом является
код следующего после p.name символа. Это значение преобразуется через ме-
ханизм явного приведения типов в значение типа char. В результате получаем
букву, следующую после буквы p.name. При вычислении координат вершины
использовано то свойство, что точка, находящаяся на расстоянии r от начала
координат в направлении угла α, имеет координаты x = r cos(α) и y = r sin(α).
В то же время для k-й точки (по порядку, а не по индексу массива) угол опреде-
ляется как α = ϕ 0 + (k − 1)ϕ , где ϕ 0 — угол направления на начальную точку,
2π
а ϕ= — угол поворота. Именно эти соотношения использованы при вычис-
n
лении вершин многоугольника.
После вычисления вершин многоугольника вычисляется и выводится на экран
значение для периметра и площади многоугольника.
// Начальная точка:
Point p=new Point('A',x,y);
// Индексная переменная:
int i;
// Значение для количества вершин:
this.n=n;
// Массив переменных-ссылок на объекты "точек":
points=new Point[n];
// Угол на начальную точку - вычисление:
phi0=Math.atan2(y,x);
// Угол приращения - вычисление:
phi=2*Math.PI/n;
// Расстояние до начальной точки от начала координат:
r=p.dist();
System.out.print("Правильный "+n+"-угольник с вершинами в точках ");
// Заполнение массива "точек" и вывод результата на экран:
for(i=0;i<n-1;i++){
p.show();
System.out.print(i==n-2?" и ":", ");
points[i]=p;
// "Вычисление" вершин:
p=new Point((char)(p.name+1),r*Math.cos(phi0+(i+1)*phi),r*Math.
sin(phi0+(i+1)*phi));
}
// "Последняя" вершина:
points[n-1]=p;
p.show();
System.out.println(".");
// Периметр фигуры:
System.out.println("Периметр:\t"+perimeter()+".");
// Площадь фигуры:
System.out.println("Площадь:\t"+square()+".");
}
// Расстояние между точками:
double dist(Point A,Point B){
return Math.sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y));}
// Метод для вычисления периметра:
double perimeter(){
double P=0;
int i;
for(i=0;i<n-1;i++)
P+=dist(points[i],points[i+1]);
P+=dist(points[n-1],points[0]);
return P;
}
продолжение
174 Глава 5. Методы и конструкторы
Матричная экспонента
В следующем примере представлена программа, с помощью которой вычисляет-
ся матричная экспонента. Известно, что экспоненциальная функция от действи-
тельного аргумента x вычисляется в виде ряда:
∞
xk
exp( x) = ∑ .
k =0 k !
for(j=0;j<n-1;j++){
System.out.print(Math.round(1000*matrix[i][j])/1000.0+"\t");}
System.out.print(Math.round(1000*matrix[i][n-1])/1000.0+"\n");
}}
// Метод для вычисления суммы матриц:
MatrixExp sum(MatrixExp B){
MatrixExp t=new MatrixExp(n,0);
int i,j;
for(i=0;i<n;i++){
for(j=0;j<n;j++){
t.matrix[i][j]=matrix[i][j]+B.matrix[i][j];}}
return t;}
// Метод для вычисления произведения матрицы на число:
MatrixExp prod(double x){
MatrixExp t=new MatrixExp(matrix);
int i,j;
for(i=0;i<n;i++){
for(j=0;j<n;j++){
t.matrix[i][j]*=x;}}
return t;}
// Метод для вычисления произведения матриц:
MatrixExp prod(MatrixExp B){
MatrixExp t=new MatrixExp(n,0);
int i,j,k;
for(i=0;i<n;i++){
for(j=0;j<n;j++){
for(k=0;k<n;k++){
t.matrix[i][j]+=matrix[i][k]*B.matrix[k][j];}
}}
return t;}
// Метод для вычисления матричной экспоненты:
MatrixExp mExp(){
MatrixExp t,q;
// Начальное значение - единичная матрица:
t=new MatrixExp(n);
// Начальная добавка:
q=new MatrixExp(matrix);
int i;
// Вычисление ряда для экспоненты:
for(i=1;i<=N;i++){
t=t.sum(q);
q=q.prod(this).prod(1.0/(i+1));}
return t;}
}
class MatrixExpDemo{
public static void main(String[] args){
продолжение
178 Глава 5. Методы и конструкторы
Метод sum() предназначен для вычисления суммы матриц. Аргументом ему пере-
дается объект класса MatrixExp. Его поле-матрица matrix складывается с полем-
матрицей matrix объекта, из которого вызывается метод. В теле метода созда-
ется локальный объект класса MatrixExp. Поле matrix этого локального объекта
вычисляется как поэлементная сумма, для чего используется вложенный цикл.
После этого локальный объект возвращается как результат метода.
Методу prod() в качестве аргумента можно передать как число, так и объект
класса MatrixExp. В первом случае на основе объекта вызова создается локаль-
ный объект, все элементы поля-матрицы matrix которого затем умножаются на
число, переданное аргументом методу. После этого объект возвращается как ре-
зультат метода.
При передаче методу prod() в качестве аргумента объекта класса MatrixExp вы-
числяется произведение матриц. Первая матрица произведения — поле matrix
объекта, из которого вызывается метод, вторая матрица — поле matrix объекта,
указанного аргументом метода. Произведение вычисляется по правилу вычис-
ления произведения для матриц (сумма произведений соответственных элемен-
тов строк первой и столбцов второй матрицы). Результатом метода является
объект класса MatrixExp с полем matrix, в которое записан результат произведе-
ния матриц. При создании локального объекта класса MatrixExp, который затем
возвращается в качестве результата (после выполнения всех необходимых вы-
числений), использован конструктор заполнения элементов поля-матрицы со
здаваемого объекта одинаковыми числами (в данном случае нулями).
Вычисление значений элементов матрицы-результата осуществляется с помо-
щью вложенного тройного цикла. Две внешние индексные переменные нуме-
руют элементы матрицы-результата, а еще одна внутренняя переменная служит
для вычисления суммы по элементам строк и столбцов матриц-операндов.
При вычислении матричной экспоненты в методе mExp() объявляются две объ-
ектные переменные t и q класса MatrixExp. Значения этим переменным присваи-
ваются командами t=new MatrixExp(n) и q=new MatrixExp(matrix) соответственно.
В первом случае создается единичная матрица, и ссылка на нее присваивается
в качестве значения объектной переменной t. Это — начальное значение для вы-
числения матричной экспоненты. Второй командой создается копия исходной
матрицы (поле matrix), и ссылка на результат записывается в переменную q.
Это — «добавка», то есть следующее после текущего слагаемое в ряде для ма-
тричной экспоненты. На первом итерационном шаге эта добавка (ее поле-массив
matrix) должна совпадать с исходной матрицей. В общем случае, на k-м итера-
Ak
ционном шаге добавка q k определяется как q k = , где через A обозначе-
k!
A
на исходная матрица. Принимая во внимание, что q k +1 q k = , легко при-
k +1
ходим к выводу, что на каждом итерационном шаге для вычисления добавки
следующего итерационного шага текущее значение-массив объекта, на который
ссылается переменная q, нужно умножить (по правилу умножения матриц) на
180 Глава 5. Методы и конструкторы
1
исходную матрицу, и результат умножить на число , где через k обозначе-
k +1
но текущее значение индексной переменной. На следующем итерационном шаге
значение-массив объекта, на который ссылается переменная q, прибавляется (по
правилу сложения матриц) к массиву объекта, на который ссылается перемен-
ная t, и ссылка на полученный в результате таких действий объект присваива-
ется в качестве нового значения переменной t. Последняя операция реализуется
с помощью команды t=t.sum(q) (первая команда в цикле в методе mExp()). Что
касается процедуры изменения поля-массива объекта, на который ссылается
переменная q, то для этих целей предназначена команда q=q.prod(this).prod(1.0/
(i+1)). Интерес представляет правая часть этого выражения. Она формально
состоит из двух частей. Инструкцией q.prod(this) вычисляется произведение
матриц (той, что записана в объекте q, и той, что записана в объекте, из которого
вызывается метод mExp()), и в качестве результата возвращается объект с соответ-
ствующим полем-массивом. Ссылка на этот объект никуда не записывается, по-
этому объект анонимный. Из этого анонимного объекта вызывается метод prod()
с числовым аргументом (инструкция q.prod(this).prod(1.0/(i+1))). В результате
на основе анонимного объекта создается еще один объект, элементы матрицы ко-
торого получаются умножением соответствующих элементов матрицы аноним-
ного объекта на число, указанное аргументом метода prod(). Ссылка на результат
присваивается в качестве значения объектной переменной q. Обращаем внима-
ние, что при передаче числового аргумента методу prod() единица в числителе
вводилась с десятичной точкой дабы избежать целочисленного деления.
После выполнения всех необходимых итераций в качестве значения метода
mExp() возвращается объект, на который ссылается переменная t.
В главном методе программы в классе MatrixExpDemo командой MatrixExp A=new
MatrixExp(3,-1,1) создается новый объект класса MatrixExp с полем-матрицей
ранга 3, заполненной случайными числами в диапазоне от –1 до 1. Ссылка на
объект записывается в объектную переменную A. Выводится матрица на экран
с помощью команды A.show(). Командой A.mExp().show() вычисляется матричная
экспонента, и результат выводится на экран. В данном случае также использует-
ся анонимный объект — результат выполнения инструкции A.mExp(). Поскольку
метод mExp() возвращает результат, являющийся объектом класса MatrixExp с вы-
численной матричной экспонентой в качестве поля-массива, то из этого объекта
(имеется виду объект A.mExp()) можно вызвать метод show(), что и происходит.
Результат выполнения программы может иметь следующий вид:
Матрица A:
0.844 0.797 0.095
0.891 -0.407 -0.528
-0.549 0.168 0.63
Матрица exp(A):
2.931 1.18 -0.111
1.532 1.068 -0.621
-1.131 -0.121 1.825
Примеры программ 181
Операции с векторами
Следующая достаточно простая программа служит иллюстрацией к созданию
класса для реализации векторов в трехмерном пространстве и выполнения основ-
ных операций с ними. Код программы приведен в листинге 5.11.
// Конструктор класса:
Vector(double[] params){
set(params);}
// Конструктор класса:
Vector(double x,double y,double z){
set(x,y,z);}
// Конструктор класса:
Vector(){
set();}
}
class VectorDemo{
public static void main(String[] args){
Vector a=new Vector(1,0,0);
Vector b=new Vector(new double[]{0,1,0});
Vector c;
System.out.println("Векторное произведение:");
(c=a.vprod(b)).show('\n');
System.out.println("Смешанное произведение: "+a.mprod(b,c));
System.out.println("Линейная комбинация векторов:");
a.prod(3).plus(b.div(2)).minus(c).show('\n');
a.set(4,0,-3);
b.set(0,10,0);
System.out.println("Угол между векторами (в градусах): "+a.angDeg(b));
System.out.println("Площадь параллелограмма: "+a.square(b));}
}
Для работы с векторами предлагается класс Vector. У класса есть закрытое поле-
ссылка на массив из трех элементов vect. Поле объявлено как закрытое, хотя на-
стоятельной необходимости в данном случае в этом нет. Перегруженный метод
set() предназначен для присваивания значения элементам массива vect. Методу
в качестве аргумента могут передаваться три числа типа double (значения трех
элементов массива vect) и один аргумент — ссылка на массив из трех элементов,
или не передаваться вовсе (в этом случае создается нуль-вектор). В свою оче-
редь, версия метода set() без аргументов реализована на основе вызова версии
метода set() с тремя нулевыми аргументами.
В соответствии с вариантами вызова метода set() в классе создаются и вариан-
ты перегружаемого конструктора. Конструктору могут передаваться те же аргу-
менты, что и методу set(), причем реализованы разные версии конструктора на
основе вызова соответствующих версий метода set().
Методу show(), предназначенному для вывода компонентов вектора на экран,
можно передать символьный аргумент (тип char), или же метод вызывается без
аргументов. Во втором случае выполняется вывод в треугольных скобках значе-
ний элементов вектора (с точностью до двух цифр после запятой) без перехода
к новой строке. Если понадобится, чтобы после вывода значений компонентов
вектора следующее сообщение выводилось в новой строке, можно вызвать ме-
тод show() с аргументом — символом перехода к новой строке '\n'.
184 Глава 5. Методы и конструкторы
Операции с полиномами
В некотором смысле перекликается с рассмотренным примером следующая
программа. В ней для работы с выражениями полиномиального типа создается
класс Polynom, в котором описаны методы сложения, вычитания, умножения по-
линомов, умножения и деления полинома на число и вычисления производной
для полинома. Рассмотрим программный код, представленный в листинге 5.12.
// Сумма полиномов:
Polynom plus(Polynom Q){
Polynom t;
int i;
if(n>=Q.n){
t=new Polynom(a);
for(i=0;i<Q.n;i++)
t.a[i]+=Q.a[i];}
else{
t=new Polynom(Q.a);
for(i=0;i<n;i++)
t.a[i]+=a[i];
}
return t;}
// Разность полиномов:
Polynom minus(Polynom Q){
return plus(Q.prod(-1));}
Polynom div(double z){
return prod(1/z);}
// Произведение полинома на число:
Polynom prod(double z){
Polynom t=new Polynom(a);
for(int i=0;i<n;i++)
a[i]*=z;
return t;}
// Произведение полинома на полином:
Polynom prod(Polynom Q){
int N=n+Q.n-1;
Polynom t=new Polynom(N);
for(int i=0;i<n;i++){
for(int j=0;j<Q.n;j++){
t.a[i+j]+=a[i]*Q.a[j];}
}
return t;}
// Конструкторы класса:
Polynom(double[] a){
set(a);
}
Polynom(int n,double z){
set(n,z);}
Polynom(int n){
set(n);}
}
class PolynomDemo{
public static void main(String[] args){
продолжение
188 Глава 5. Методы и конструкторы
то:
n m
R( x) ≡ P ( x)Q( x) = ∑ a i x i ∑ b j x j = ∑ a i b j x i + j.
i =0 j =0 0≤ i ≤ n,
0≤ j ≤ m
Произведение полиномов:
Степень x: 0 1 2 3 4 5 6
Коэффициент: -6.0 4.0 38.0 -24.0 -14.0 0.0 12.0
Желающие могут проверить, что все коэффициенты и значения вычислены
корректно. Отметим также, что вывод результатов в данном случае реализован
с помощью специального метода show() класса Polynom. На практике существует
более простой, надежный и эффективный способ обеспечить приемлемый спо-
соб вывода данных объекта. Состоит он в переопределении метода toString().
Подробнее речь о работе с текстом и, в частности, о работе с методом toString()
идет в главе 8.
Бинарное дерево
Рассмотрим программу, в которой на основе конструкторов класса создается
бинарное дерево объектов — каждый объект имеет по две ссылки на объекты
того же класса. Пример учебный, поэтому ситуация упрощена до предела. Каж-
дый объект, кроме прочего, имеет три поля. Символьное (типа char) поле Level
определяет уровень объекта: например, в вершине иерархии находится объект
уровня A, который ссылается на два объекта уровня B, которые, в свою очередь,
ссылаются в общей сложности на четыре объекта уровня C и т. д. Объекты нуме-
руются, для чего используется целочисленное поле Number. Нумерация выполня-
ется в пределах одного уровня. Например, на верхнем уровне A всего один объ-
ект с номером 1. На втором уровне B два объекта с номерами 1 и 2. На третьем
уровне C четыре объекта с номерами от 1 до 4 включительно и т. д. Описанная
структура объектов представлена на рис. 5.2.
Кроме метки уровня и номера объекта на уровне, каждый объект имеет еще
и свой «идентификационный код». Этот код генерируется случайным образом
при создании объекта и состоит по умолчанию из восьми цифр (количество
цифр в коде определяется закрытым статическим целочисленным полем IDnum).
Примеры программ 193
Резюме
1. В Java методы классов могут перегружаться. В этом случае создается не-
сколько вариантов одного метода. Они все имеют одинаковое название, но
отличаются сигнатурой — типом результата, именем и (или) списком аргу-
ментов. Какой именно вариант метода необходимо вызывать при выполнении
программы, определяется в зависимости от типа и количества переданных
методу аргументов.
2. Конструктор класса — это метод, который вызывается автоматически при
создании объекта класса. Имя конструктора совпадает с именем класса.
Конструктор не возвращает результат, и идентификатор типа результата для
него не указывается. Конструктору можно передавать аргументы и конструк-
тор можно перегружать. Аргументы, которые передаются конструктору при
создании объекта, указывается в круглых скобках после имени класса в ин-
струкции вида new имя_класса(аргументы).
3. Объекты могут передаваться методам в качестве аргументов, а также возвра-
щаться методами в качестве результата. В этом случае формально метод объ-
является так же, как и для базовых типов, только в качестве идентификатора
типа указывается имя соответствующего класса.
4. Существует два способа передачи аргументов методам: по значению и по
ссылке. При передаче аргументов по значению создается копия переменной,
указанной аргументом, и все вычисления в методе осуществляются с этой
копией. При передаче аргумента по ссылке операции выполняются непо-
средственно с аргументом. В Java переменные базовых типов передаются по
значению, а объекты — по ссылке.
Глава 6. Наследование
и переопределение методов
Не забывайте, что всему нашему делу
положила начало мышь.
У. Дисней
Создание подкласса
Для иллюстрации того, что происходит при наследовании, когда суперкласс со-
держит закрытые члены, рассмотрим пример в листинге 6.2.
Конструкторы и наследование
Поле code (после присваивания значения полю) служит в конструкторе для вы-
вода сообщения о создании объекта с соответствующим номером. Номер объек-
та (поле code) используется также в методе show(), чтобы легче было проследить,
для какого именно объекта выводится информация о значении поля number.
Подкласс ClassB создается на основе суперкласса ClassA. В подклассе ClassB на-
следуется статическое поле count и поле number. Закрытое поле code не наследу-
ется. Кроме этих наследуемых полей, непосредственно в классе ClassB описано
символьное поле symbol. Конструктор класса принимает два аргумента: первый
типа int для поля number и второй типа char для поля symbol.
Код конструктора класса ClassB состоит всего из двух команд: команды вызова
конструктора суперкласса super(n) и команды присваивания значения символь-
ному полю symbol=s (n и s — аргументы конструктора). Со второй командой все
просто и понятно. Интерес представляет команда вызова конструктора супер-
класса. Во-первых, этим конструктором наследуемому полю number присваива-
ется значение. Во-вторых, значение наследуемого статического поля count уве-
личивается на единицу. Это означает, что ведется общий учет всех объектов,
как суперкласса, так и подкласса. В-третьих, хотя поле code не наследуется, под
него выделяется место в памяти и туда заносится порядковый номер созданного
объекта. На экран выводится сообщение о создании нового объекта, а номер
объекта считывается из «несуществующего» поля code.
Метод show() в классе ClassB переопределяется. Сигнатура описанного в классе
ClassB метода show() совпадает с сигнатурой метода show(), описанного в классе
ClassA. Если в классе ClassA методом show() отображается информация о номере
объекта и значении его поля number, то в классе ClassB метод show() выводит
еще и значение поля symbol. При этом в переопределенном методе show() вы-
зывается также прежняя (исходная) версия метода из класса ClassA. Для этого
используется инструкция вида super.show(). Этот исходный вариант метода,
кроме прочего, считывает из ненаследуемого (но реально существующего) поля
code порядковый номер объекта и отображает его в выводимом на экран со-
общении.
Метод set() в классе ClassB перегружается. Хотя в классе ClassA есть метод
с таким же названием, сигнатуры методов в суперклассе и подклассе разные.
В суперклассе у метода set() один числовой аргумент, а в подклассе у этого ме-
тода два аргумента: числовой и символьный. Поэтому в классе ClassB имеется
два варианта метода set() — с одним и двумя аргументами. Первый наследу-
ется из суперкласса ClassA, а второй определен непосредственно в подклассе
ClassB.
В главном методе программы командами ClassA objA=new ClassA(10) и ClassB
objB=new ClassB(-20,'a') создаются два объекта: объект objA суперкласса и объ-
ект objB подкласса. В результате выполнения этих команд на экране появля-
ются сообщения Объект №1 создан! и Объект №2 создан! — сообщения выводятся
конструкторами. Проверяются значения полей созданных объектов командами
objA.show() и objB.show(). Поскольку метод show() перегружен, то в первом случае
212 Глава 6. Наследование и переопределение методов
Многоуровневое наследование
class MultiCall{
public static void main(String args[]){
C obj=new C(1,2,3);}
}
Ситуация достаточно простая: класс A является суперклассом для подкласса B.
Класс B, в свою очередь, является суперклассом для подкласса C. Таким обра-
зом, получается своеобразная иерархия классов. В классе A всего одно числовое
поле a и конструктор с одним аргументом. Аргумент определяет значение поля
создаваемого объекта. Кроме того, при этом выводится сообщение о значении
поля объекта.
В классе B наследуется поле a из класса A и появляется еще одно поле b. Соот-
ветственно, конструктор имеет два аргумента. Первый передается конструктору
суперкласса (класс A), а второй определяет значение нового поля b. Также выво-
дится сообщение о значении этого поля, однако прежде сообщение о значении
поля a выводится конструктором суперкласса.
Два поля a и b наследуются в классе C. Там же описано числовое поле c. Первые
два аргумента конструктора передаются конструктору суперкласса (класса B),
а третий присваивается в качестве значения полю c. В конструкторе класса C
имеется также команда вывода на экран значения этого поля. Значения полей
a и b выводятся при выполнении конструктора суперкласса.
В главном методе программы командой C obj=new C(1,2,3) создается объект
класса C. В результате на экране появляются сообщения:
Поле a: 1
Поле b: 2
Поле c: 3
Путем многоуровневого наследования можно создавать достаточно сложные
иерархические структуры классов. Особенно механизм многоуровневого насле-
дования становится эффективным при одновременном использовании механиз-
мов перегрузки и переопределения методов. Пример простой, но показательной
программы приведен в листинге 6.8.
Метод класса C
Класс С
Из объекта класса A вызывается версия метода без аргументов. Из объекта клас-
са B метод вызывается без аргументов (версия метода из класса A) и с текстовым
аргументом (версия метода, описанная в классе B). Вызываемая из объекта клас-
са C версия метода без аргумента описана в классе C, а версия метода с тексто-
вым аргументом наследуется из класса B.
Абстрактные классы
Примеры программ
Рассмотрим некоторые примеры, в которых имеет место наследование классов
и переопределение методов.
Комплексная экспонента
Далее в листинге 6.12 приведен код программы, в которой создается суперкласс
для реализации комплексных чисел и выполнения базовых операций с ними:
сложения комплексных чисел, умножения комплексных чисел и произведения
комплексного и действительного чисел. На основе суперкласса создается под-
класс, в котором описан метод для вычисления экспоненты от комплексного
аргумента.
tmp.Re=Re*obj.Re-Im*obj.Im;
tmp.Im=Im*obj.Re+Re*obj.Im;
return tmp;}
// Метод перегружен для вычисления произведения
// комплексного и действительного чисел:
Compl prod(double x){
Compl tmp=new Compl();
tmp.Re=Re*x;
tmp.Im=Im*x;
return tmp;}
// Метод для отображения полей объекта:
void show(){
System.out.println("Действительная часть Re="+Re);
System.out.println("Мнимая часть Im="+Im);}
// Конструктор без аргумента:
Compl(){
Re=0;
Im=0;}
// Конструктор с одним аргументом:
Compl(double x){
Re=x;
Im=0;}
// Конструктор с двумя аргументами:
Compl(double x,double y){
Re=x;
Im=y;}
// Конструктор создания копии:
Compl(Compl obj){
Re=obj.Re;
Im=obj.Im;}
}
// Подкласс:
class ComplNums extends Compl{
// Количество слагаемых ряда:
private int n;
// Метод для вычисления комплексной экспоненты:
ComplNums CExp(){
// Начальное значение - объект суперкласса:
Compl tmp=new Compl(1);
// Начальная добавка - объект суперкласса:
Compl q=new Compl(this);
// Индексная переменная:
int i;
// Вычисление ряда:
for(i=1;i<=n;i++){
tmp=tmp.sum(q);
продолжение
222 Глава 6. Наследование и переопределение методов
В суперклассе Compl описано два поля Re и Im — оба типа double. Кроме этого,
класс имеет метод sum() для вычисления суммы двух комплексных чисел, реа-
лизованных в виде объектов класса Compl. В классе также есть перегруженный
метод prod() для вычисления произведения двух комплексных чисел, а также
комплексного числа на действительное число. Конструкторы класса Compl по-
зволяют создавать объекты без передачи аргументов, а также с передачей одно-
го и двух аргументов, кроме того, у класса имеется конструктор копирования.
В последнем случае конструктору в качестве аргумента передается объект того
же класса — на основе этого объекта создается копия.
Примеры программ 223
}
return tmp;}
// Отображение параметров полинома и значения в точке:
void show(double x){
System.out.println("Аргумент полинома: "+x);
System.out.println("Значение полинома: "+value(x));
show();}
PolyDerive(PolyBase obj){
a=new double[obj.a.length];
for(int i=0;i<a.length;i++)
a[i]=obj.a[i];}
}
// Подкласс для разложения в ряд Тейлора произведения:
class Taylor extends PolyDerive{
void show(){
System.out.println("Ряд Тейлора!");
super.show();
}
Taylor(PolyBase P,PolyBase Q){
super(P);
PolyBase tmp=prod(Q);
for(int i=0;i<a.length;i++)
a[i]=tmp.a[i];}
}
class PolyExtendsDemo{
public static void main(String[] args){
// Исходные полиномы:
PolyBase P=new PolyBase();
PolyBase Q=new PolyBase();
PolyBase R;
P.a=new double[]{1,-2,4,1,-3};
Q.a=new double[]{2,-1,3,0,4};
// Произведение полиномов:
R=new PolyDerive(P).prod(Q);
R.show();
new PolyDerive(P).show(-1);
// Ряд Тейлора:
new Taylor(P,Q).show();
}}
В суперклассе PolyBase, предназначенном для реализации полиномов, имеется
поле a — переменная массива типа double. В этот массив будут заноситься коэф-
фициенты полинома (напомним, что полиномом называется сумма степенных
2 n
слагаемых вида a 0 + a1 x + a 2 x + ... + a n x ). Кроме того, в классе описан ме-
тод value() для вычисления значения полинома в точке (аргумент метода). Ме-
тод power() предназначен для вычисления степени полинома. Вообще степень
226 Глава 6. Наследование и переопределение методов
Ряд Тейлора!
Коэффициенты полинома:
2.0 -5.0 13.0 -8.0 9.0
Степень полинома: 4.
В частности, объявляются три объектные переменные (P, Q и R) класса PolyBase.
Для двух (P и Q) полям a присваиваются в явном виде значения, а в третью
(переменную R) записывается ссылка на результат произведения двух полино-
мов. При этом инструкцией new PolyDerive(P) на основе первого объекта P класса
PolyBase создается анонимный объект класса PolyDerive, из которого вызывается
метод prod() для вычисления произведения полиномов. Аналогично проверя-
ется функциональность перегруженного в подклассе PolyDerive метода show().
Для вычисления ряда Тейлора с одновременным выводом результата на консоль
использована команда new Taylor(P,Q).show(). В данном случае также создается
анонимный объект.
Резюме
1. В Java на основе одних классов можно создавать другие. Такой механизм на-
зывается наследованием. При наследовании поля и методы исходного класса,
который называется суперклассом, наследуются классом, создаваемым на его
основе. Этот второй класс называется подклассом.
2. При создании подкласса после его имени через ключевое слово extends указы-
вается имя суперкласса, на основе которого создается подкласс. Наследование
228 Глава 6. Наследование и переопределение методов
Пакеты в Java
Символ плюс (+) означает, что член доступен, а символ минус (-) — что нет.
Нетривиальность ситуации усугубляется еще и тем, что подклассы и супер-
классы могут размещаться в разных пакетах. Существует несколько правил,
которые достаточно полно отражают ситуацию с доступностью различных чле-
нов класса.
Наличие идентификатора public у члена класса означает, что он доступен
везде: как в классе, так и за его пределами и даже в классах других пакетов.
Наличие идентификатора private означает, что член доступен только в преде-
лах класса, где он объявлен.
Если у члена нет идентификатора доступа, он доступен в пределах пакета.
Члены касса, объявленные с идентификатором protected, доступны в пакете
и в подклассах вне пакета.
232 Глава 7. Пакеты и интерфейсы
Интерфейсы
Интерфейсные ссылки
У суперкласса ClassA объявлено поле int number и определен метод show(), который
выводит значение поля на экран. При наследовании в подклассе ClassB этот метод
переопределяется так, что выводит значения двух полей: наследуемого из супер-
класса поля number и поля int value, описанного непосредственно в подклассе.
В классе ClassB методы setOne() и setTwo() реализованы так, что первый метод
присваивает значение полю number, второй — полю value.
В главном методе программы создаются две интерфейсные переменные: пере-
менная ref1 типа One и переменная ref2 типа Two. Кроме того, создается объект
obj класса ClassB. В качестве значений интерфейсным переменным присваива-
ются ссылки на объект obj. Это возможно, поскольку класс ClassB реализует
интерфейсы One и Two. Однако в силу того же обстоятельства через перемен-
ную ref1 можно получить доступ только к методу setOne(), а через переменную
ref2 — только к методу setTwo(). Командами ref1.setOne(10) и ref2.setTwo(-50)
полям объекта obj присваиваются значения, а командой obj.show() значения по-
лей выводятся на экран.
Расширение интерфейсов
Резюме
1. В Java существуют специальные контейнеры для классов — пакеты. При
определении пакета в файле с описанием класса, включаемого в пакет, пер-
вой командой следует инструкция package, после которой указывается имя
пакета.
2. Взаимное расположение классов по пакетам влияет на доступность членов
этих классов.
3. Интерфейс напоминает класс, но содержит только сигнатуры методов (без
описания), а также поля-константы (значения полей не могут изменяться).
Резюме 241
затем на его основе создается текстовая строка strA, после чего создается еще
одна текстовая строка strB с таким же значением (слово Java), как и строка strA.
Обращаем внимание, что в данном случае объектные переменные strA и strB
класса String ссылаются на разные объекты, но текстовые значения в этих объ-
ектах записаны одинаковые.
По-иному обстояли бы дела, если одной объектной переменной, например srtA,
в качестве значения была присвоена другая объектная переменная (команда
вида strA=strB). В последнем случае обе переменные ссылались бы на один и тот
же объект. В рассмотренном примере объекты разные.
Еще один способ создания текстовой строки проиллюстрирован в листинге 8.2.
Здесь текстовая строка создается на основе числового массива. Каждое число в мас-
сиве элементов типа byte интерпретируется как код символа в кодировке ASCII.
Метод toString()
if(Re!=0||(Re==0&&Im==0)) RePart+=Re;
if((Im>0)&&(Re!=0)) sign+="+";
if(Im!=0) ImPart+=Im+"i";
result=RePart+sign+ImPart;
return result;}
}
class toStringDemo{
public static void main(String[] args){
for(int i=1;i<=3;i++){
for(int j=1;j<=5;j+=2){
ComplNums z=new ComplNums(i-2,j-3);
// Автоматический вызов метода toString():
System.out.println(z);
}
}
}
}
В программе создается некое подобие класса для реализации комплексных чи-
сел. У класса ComplNums два поля Re и Im типа double. Это действительная и мнимая
части комплексного числа. Конструктор класса принимает два аргумента — зна-
чения полей Re и Im. Кроме этого, в классе переопределен метод toString(). Ме-
тод, описанный как public, возвращает в качестве значения объект класса String
и не имеет аргументов. На метод toString() возложена обязанность сформиро-
вать текстовое представление комплексного числа с заданными действительной
и мнимой частями. Правила представления комплексного числа следующие.
Если действительная часть равна нулю, а мнимая отлична от нуля, то дей-
ствительная часть не отображается.
Если и мнимая, и действительная части числа равны нулю, отображается
действительная (нулевая) часть.
Если действительная часть отлична от нуля, а мнимая равна нулю, мнимая
часть не отображается.
Если действительная часть не отображается, то не отображается и знак плюс
перед мнимой частью.
Программный код метода toString() с помощью нескольких условных инструк-
ций реализует эти правила. В частности, результат метода предварительно за-
писывается в объектную переменную result класса String. Переменная result,
в свою очередь, представляется как объединение трех текстовых строк: RePart
(текстовое представление действительной части комплексного числа), ImPart
(текстовое представление мнимой части комплексного числа с учетом мнимой
единицы i) и sign (знак между действительной и мнимой частями комплексного
числа). Все четыре переменные инициализируются пустой строкой.
Командой if(Re!=0||(Re==0&&Im==0)) RePart+=Re изменяется, если необходимо, тек-
стовое представление действительной части. Это происходит, если действительная
часть отлична от нуля или если и действительная, и мнимая части равны нулю.
248 Глава 8. Работа с текстом
Сравнение строк
Метод Назначение
Методом substring() возвращается в качестве результата тексто-
вая подстрока (объект класса String) строки, из которой вы-
substring() зывается метод. Аргументами метода указывают индекс начала
подстроки в строке и индекс первого не входящего в подстроку
символа строки. Можно указывать только первый аргумент
Методом concat() выполняется объединение строк: текстовая
строка, указанная аргументом метода, добавляется в конец
concat() текстовой строки, из которой вызывается метод. Получаемый
в результате объединения текстовый объект класса String возвра-
щается в качестве результата метода
У метода replace() два аргумента символьного (char) типа.
replace() В качестве результата методом возвращается текстовая строка,
которая получается заменой в строке вызова первого символа-
аргумента вторым
Изменение текстовых строк 255
Метод Назначение
Результатом метода trim() является текстовый объект, который
trim() получается из строки вызова удалением начальных и конечных
пробелов. Метод не имеет аргументов
Методом toLowerCase() в качестве результата возвращается
toLowerCase() текстовая строка (объект класса String), которая получается из
строки вызова переводом всех букв в нижний регистр (все буквы
строчные). Метод аргументов не имеет
Методом toUpperCase() в качестве результата возвращается
toUpperCase() текстовая строка (объект класса String), которая получается из
строки вызова переводом всех букв в верхний регистр (все буквы
прописные). Метод аргументов не имеет
Класс StringBuffer
Текстовые строки могут быть реализованы не только как объекты класса String,
но и как объекты класса StringBuffer. Принципиальное отличие этих объектов
состоит в том, что объекты класса StringBuffer можно изменять. Другими слова-
ми, если текст реализован в виде объекта класса StringBuffer, в этот текст мож-
но вносить изменения, причем без создания нового объекта. В частности, при
работе с объектами класса StringBuffer можно добавлять подстроки в средину
и конец строки. Делается это за счет выделения дополнительной памяти при
создании объекта класса StringBuffer.
Класс StringBuffer 257
Метод Описание
length() Метод возвращает текущую длину текстовой строки
Методом возвращается выделенный для данной текстовой
capacity() переменной объем памяти (в символах, то есть количество
символов, которые можно записать в текстовую строку)
Метод Описание
Методом из строки вызова удаляется подстрока. Первым
delete() аргументом метода указывается индекс начала удаляемой под-
строки, вторым — индекс первого после удаляемой подстроки
символа
Резюме
1. Для работы с текстом в Java предусмотрены классы String и StringBuffer.
2. Принципиальная разница между строками, реализованными в виде объектов
классов String и StringBuffer, состоит в том, что в первом случае создан-
ная строка изменена быть не может, во втором — может. Под изменением
строки в данном случае подразумевается изменение объекта, через который
Резюме 261
реализована строка. При реализации строки объектом класса String для из-
менения строки создается новый объект, и ссылка на него присваивается
соответствующей объектной переменной. Поскольку при реализации строк
объектами класса StringBuffer предусмотрено автоматическое выделение
дополнительного буфера для записи текста, в объекты этого класса можно
вносить изменения.
3. Для работы со строками (объектами классов String и StringBuffer) в Java
имеются специальные встроенные функции, которые позволяют выполнять
все базовые операции со строками.
4. При преобразовании объектов в тип String (например, если объект передается
аргументом методу println()) автоматически вызывается метод toString().
Путем переопределения этого метода для класса можно создать простой
и эффективный механизм вывода на консоль информации об объектах класса.
5. Аргументы командной строки передаются в виде текстового массива в метод
main(). Элементы массива, являющегося аргументом метода main(), — это
текстовые представления параметров командной строки.
Глава 9. Обработка исключительных
ситуаций
Это безобразие так оставлять нельзя!
Из к/ф «Карнавальная ночь»
Программы пишут для того, чтобы они работали, работали быстро и, самое
главное, правильно. К сожалению, программы не всегда работают правильно.
Причем эта проблема не сводится к уровню подготовки программиста. Другими
словами, бывают ситуации, причем нередко, когда принципиально невозможно
или практически затруднительно обеспечить безошибочную работу программы.
В таких случаях желательно хотя бы свести к минимуму негативные послед-
ствия от возникшей ошибки.
Самая большая неприятность при возникновении ошибочной ситуации состоит
в том, что обычно это приводит к экстренному завершению программы. Во мно-
гих языках программирования, в том числе и в Java, предусмотрены механизмы,
позволяющие «сохранить лицо» даже в достаточно сложных ситуациях. Об этих
механизмах и идет речь в данной главе.
Исключительные ситуации
finally{
// код, который выполняется обязательно
}
Если при исполнении программного кода в блоке try{} ошибок не возникает,
после выполнения этого блока выполняется блок finally (если он имеется), за-
тем управление передается следующей после конструкции try-catch-finally ко-
манде.
При возникновении ошибки в процессе выполнения кода в блоке try выполне-
ние кода в этом блоке останавливается и начинается поиск подходящего блока
catch. Если подходящий блок найден, выполняется его программный код, после
чего выполняется код блока finally (при наличии такого). На этом все — далее
выполняется код, следующий после блока try-catch-finally.
Может случиться, что в блоке try возникла ошибка, но подходящего блока
catch для ее обработки нет. В этом случае исключение выбрасывается из ме-
тода и должно быть обработано внешним к методу программным кодом. Со-
гласно правилам языка Java, исключения, которые не обрабатываются в методе
и выбрасываются из метода, указываются в сигнатуре метода после ключевого
слова throws. То есть указываются классы выбрасываемых из метода исключе-
ний. Правда, далеко не все классы выбрасываемых исключений нужно указы-
вать — только так называемые неконтролируемые исключения. Мы рассмотрим
их позже.
Если возникает ошибка, обработка которой в программе не предусмотрена, ис-
пользуется обработчик исключительной ситуации по умолчанию. Самое траги-
ческое последствие вызова обработчика по умолчанию состоит в том, что про-
грамма завершает работу.
Есть еще одно ключевое слово, которое достаточно часто используется при об-
работке исключительных ситуаций, а точнее, при генерировании исключитель-
ной ситуации. Это ключевое слово throw.
Классы исключений
Как отмечалось в начале главы, для каждого типа исключений можно предусмо-
треть свой блок catch для обработки. Блоки размещаются один за другим и им
передаются разные аргументы (объекты исключений разных классов) в соот-
ветствии с типом обрабатываемой исключительной ситуации. В листинге 9.4
приведен пример программы, в которой помимо ошибки деления на ноль об-
рабатывается также и ошибка неверной индексации массива.
finally{System.out.println("**************");}}
System.out.println("Цикл for завершен!");}
}
Как и в одном из предыдущих примеров, здесь используется генератор слу-
чайных чисел, для чего командой import java.util.Random импортируется класс
Random, а с помощью команды Random r=new Random() создается объект r этого
класса для генерирования случайных чисел. Кроме того, создается целочислен-
ный массив MyArray из двух элементов (значения 0 и 2), также описываются две
целочисленные переменные a и b.
В цикле командой a=r.nextInt(3) присваивается значение переменной a — слу-
чайное целое число в диапазоне от 0 до 2 включительно. Переменная a, другими
словами, может принимать значения 0, 1 и 2. Далее командой b=10/MyArray[a]
присваивается значение переменной b. При выполнении этой команды могут
возникать неприятности двух видов. Во-первых, если значение переменой a рав-
но 0, то выполняется деление на ноль, поскольку элемент массива MyArray[0] ра-
вен нулю. Во-вторых, если значение переменой a равно 2, то имеет место ошиб-
ка выхода за границы массива, поскольку элемента MyArray[2] не существует.
Если же значение переменной a равно 1, то значение переменной b вычисляется
как 5 и выводится на экран командой System.out.println(b).
Для обработки ошибки деления на ноль используется блок catch с аргументом
класса ArithmeticException. В этом случае выводится сообщение о том, что прои-
зошла попытка деления на ноль.
Исключительная ситуация, связанная с неправильной индексацией элементов
массива, описывается исключением класса ArrayIndexOutOfBoundsException. Аргу-
мент этого класса передается во второй блок catch. Обработка этой ошибки
сводится к тому, что выводится сообщение о выходе за границы массива.
Наконец, блок finally содержит команду вывода разделительной «звездной ли-
нии», которая отображается независимо от того, произошла какая-либо ошибка
или нет. Результат выполнения программы мог бы выглядеть следующим об-
разом:
Выход за границы массива!
**************
Деление на ноль!
**************
Выход за границы массива!
**************
5
**************
Деление на ноль!
**************
5
**************
Выход за границы массива!
270 Глава 9. Обработка исключительных ситуаций
**************
5
**************
Выход за границы массива!
**************
Цикл for завершен!
В конце работы программы выводится сообщение о том, что работа цикла за-
вершена.
Один блок try может размещаться внутри другого блока try. В этом случае, если
во внутреннем блоке try возникает ошибка и этот блок try не содержит блока
catch для ее обработки, исключение выбрасывается во внешний блок try и на-
чинается последовательный просмотр его блоков catch на предмет обработки
возникшей ошибки.
Может сложиться и более нетривиальная ситуация, например, когда метод, ко-
торый вызывается в блоке try, сам содержит блок try. Если в блоке try метода
возникает ошибка, не обрабатываемая методом, ее перехватывает внешний блок
try, в котором вызывается метод.
Вообще же общий принцип обработки ситуаций при сложной схеме включения
блоков try состоит в том, что при входе в очередной блок try контексты обраба-
тываемых этим блоком исключений записываются в стек. При возникновении
ошибки этот стек начинает «раскручиваться» — контексты исключений просма-
триваются в обратном порядке (то есть последний занесенный в стек контекст
исключения просматривается первым).
В листинге 9.5 приведен пример использования вложенных блоков try.
try{
if(a==1) a=a/(a-1); // деление на ноль
else c[a]=200; // выход за границы массива
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("Выход за границы массива: "+e);}
}catch(ArithmeticException e){
System.out.println("Деление на ноль: "+e);}
System.out.println("*******************************");}
}}
Как и ранее, в программе генерируются случайные числа (с помощью объекта r
класса Random), объявляются две целочисленные переменные a и b, а также цело-
численный массив c, состоящий всего из двух элементов (со значениями –1 и 1).
В цикле внешнего блока try последовательное выполнение команд a=r.nextInt(3)
и b=100/a может закончиться генерированием исключения, поскольку среди воз-
можных значений переменной a есть и нулевое, что, в свою очередь, означает
ошибку деления на ноль. На этот случай предусмотрен блок catch внешнего
блока try. В случае ошибки выполняется команда:
System.out.println("Деление на ноль: "+e)
Здесь объект e класса ArithmeticException является аргументом блока catch.
Если в указанном месте программы ошибка деления на ноль не возникает, зна-
чение переменной b выводится на экран (эта переменная может принимать все-
го два значения: 100 при значении переменной a равном 1 и 50 при значении
переменной a равном 2), после чего выполняется серия команд, заключенных во
внутренний блок try. Сразу отметим, что этот блок обрабатывает только исклю-
чение, связанное с выходом за границы массива (объект класса ArrayIndexOutOfB
oundsException). В случае возникновения соответствующей ошибки выполняется
команда:
System.out.println("Выход за границы массива: "+e)
Что касается самого программного кода во внутреннем блоке try, то он может
вызывать исключения двух типов. В частности, там с помощью условной ин-
струкции проверяется условие равенства значения переменной a единице. Если
условие соблюдается, то при выполнении команды a=a/(a-1) происходит ошиб-
ка деления на ноль. В противном случае (то есть если значение переменной a
отлично от единицы) выполняется команда c[a]=200. Обращаем внимание, что
внутренний блок try выполняется, только если значение переменной a равно 1
или 2, поскольку если значение этой переменной равно 0, еще раньше возникнет
ошибка деления на ноль, которая перехватывается блоком catch внешнего бло-
ка try. Поэтому если во внутреннем блоке try значение переменной a отлично
от единицы, это автоматически означает, что значение переменной a равно 2.
В результате при выполнении команды c[a]=200 возникает ошибка выхода за
границы массива, поскольку в массиве c всего два элемента, а элемента c[2] там
просто нет.
272 Глава 9. Обработка исключительных ситуаций
int a,b;
for(int i=1;i<10;i++){
try{
a=r.nextInt(3); // значения 0, 1 или 2
b=100/a; // возможно деление на ноль
System.out.println("b="+b);
nesttry(a);
}catch(ArithmeticException e){
System.out.println("Деление на ноль: "+e);}
System.out.println("*******************************");}
}}
Фактически, это та же программа, что и в предыдущем примере, только код
внутреннего блока try реализован в виде статического метода nesttry(), содер-
жащего блок try. Там, где раньше был внутренний блок try, теперь вызывается
метод nesttry(). Если возникает ошибка выхода за границы массива, она обраба-
тывается в самом методе, а ошибка деления на ноль обрабатывается во внешнем
блоке try. Результат выполнения этой программы аналогичен предыдущему.
Ранее отмечалось, что если метод выбрасывает или может выбросить исключе-
ние, которое в методе не обрабатывается, этот факт нужно отразить при опи-
сании метода. В сигнатуре метода после ключевого слова throws перечисляются
классы исключений, которые может выбрасывать метод. Причина такой преду-
предительности состоит в том, что внешним методам нужно сообщить, к каким
неприятностям следует быть готовым при вызове данного метода. Ранее мы
не использовали ключевое слово throws, хотя некоторые методы и выбрасыва-
ли исключения. Дело в том, что перечисляются далеко не все выбрасываемые
276 Глава 9. Обработка исключительных ситуаций
продолжение
278 Глава 9. Обработка исключительных ситуаций
Исключение Описание
Еще раз отметим, что даже если метод не обрабатывает и выбрасывает контро-
лируемое исключение, отражать этот факт в сигнатуре метода не нужно. Не-
контролируемые исключения указываются в сигнатуре метода, если они в этом
методе не обрабатываются.
Если аргумент, переданный методу MyLog(), лежит вне пределов диапазона [0,1] ,
методом возвращается значение Math.log(x*(x-1)) (в этой команде имеет место
обращение к статическому методу вычисления натурального логарифма log(),
описанному в классе Math). Если же аргумент метода MyLog() попадает в диапа-
зон [0,1] , приведенное выражение вычислено быть не может, поскольку у на-
турального логарифма аргумент отрицательный. В этом случае методом MyLog()
генерируется и выбрасывается исключение пользовательского типа MyException.
Аргументами конструктору при этом передаются границы диапазона [0,1]
и описание ошибки (неверный аргумент).
В главном методе программы выполняется попытка вычислить значение мето-
дом MyLog() и вывести его на экран. При этом отслеживается возможность по-
явления исключения класса MyException. В случае если соответствующая ошибка
возникает, выполняется ее обработка, которая состоит в том, что на экран выво-
дится описание объекта исключения. Этот объект передается методу println()
в блоке catch, а наблюдаемый при этом результат отражает способ переопреде-
ления метода toString() в классе MyException.
Резюме
1. В Java при возникновении ошибочных ситуаций автоматически создаются
объекты, которые их описывают, — исключения. Подобный объект (исклю-
чение) передается методу, вызвавшему ошибку, для обработки. Метод может
обработать объект (исключение) или передать его дальше.
2. Исключения также можно генерировать специально — в некоторых случаях
это имеет смысл делать. В таком случае используется инструкция throw, после
которой указывается объект генерируемого исключения.
3. Для обработки исключений служит блок try-catch-finally. В блок try помеща-
ется отслеживаемый программный код, блоки catch содержат код обработки
исключений (их может быть несколько), а необязательный блок finally со-
держит код, который должен выполняться в любом случае.
4. В Java для описания исключений есть иерархия классов, на вершине которой
находится класс Throwable. Встроенные исключения делятся на контролируе-
Резюме 281
Нужно, чтобы она тебя брала. Нужно, чтобы она тебя вела.
Но в то же время и не уводила!
Из к/ф «Карнавальная ночь»
Метод Описание
а поток относится к группе с именем main. Имя потока — это его уникальный
текстовый идентификатор. Приоритет — целое число. Само значение приори-
тета особой важности не имеет, важно только, у какого потока оно больше.
Приоритет определяет, какому потоку отдается предпочтение при выполнении
программы, когда один поток прерывается другим. Поток с низким приорите-
том может быть остановлен потоком с более высоким приоритетом. Все потоки
разбиваются на группы. Приоритеты потоков сравниваются в пределах групп.
Командой t.setName("Самый главный поток") меняется имя главного потока. Но-
вое имя потока "Самый главный поток" указывается аргументом метода setName(),
который вызывается из объекта главного потока. Поэтому после выполнения
команды System.out.println("После изменения имени: "+t) на экране появляется
сообщение:
После изменения имени: Thread[Самый главный поток,5,main]
Далее в рамках цикла на экран в столбик выводятся цифры от 5 до 1. При этом
использована команда Thread.sleep(1000) для приостановки потока в каждом ци-
кле. В результате выполнения программы получаем:
Активный поток: Thread[main,5,main]
После изменения имени: Thread[Самый главный поток,5,main]
5
4
3
2
1
Причем цифровой ряд выводится на экран с заметной задержкой (порядка
одной секунды).
Поскольку метод sleep() может выбрасывать исключение InterruptedException
(прерывание потока) и это исключение неконтролируемое, то в программе преду-
смотрена его обработка. В противном случае пришлось бы отразить в сигнатуре ме-
тода main() тот факт, что он выбрасывает исключение класса InterruptedException.
Создание потока
Как уже отмечалось, для создания потока (кроме главного) следует либо рас-
ширить класс Thread, либо реализовать интерфейс Runnable. Сначала рассмотрим
создание потока путем реализации интерфейса Runnable.
Создать поток можно на базе любого класса, который реализует интерфейс
Runnable. При реализации интерфейса Runnable достаточно определить всего один
286 Глава 10. Многопоточное программирование
метод run(). Программный код этого метода — это тот код, который выполня-
ется в рамках создаваемого потока. Говорят, что метод run() определяет точку
входа в поток. Метод run() имеет следующую сигнатуру:
public void run()
Для начала выполнения потока вызывают метод start().
Общая последовательность действий при создании нового потока путем реали-
зации интерфейса Runnable следующая.
1. Определяется класс, реализующий интерфейс Runnable. В этом классе опреде-
ляется метод run().
2. В этом классе создается объект класса Thread. Конструктору класса передается
два аргумента: объект класса, реализующего интерфейс Runnable, и текстовая
строка — название потока.
3. Для запуска потока из объекта класса Thread вызывается метод start().
Другими словами, для того чтобы определить программный код, выполняемый
в новом потоке, необходимо расширить интерфейс Runnable, причем указанный
код потока — это, фактически, код метода run(), определяемого в классе, расши-
ряющем интерфейс Runnable. Поток в Java — это объект класса Thread. Поэтому
для создания потока необходимо создать объект этого класса. В то же время при
создании потока необходимо указать код этого потока (то есть код соответствую-
щего метода run()). Код потока определяется в классе, реализующем интерфейс
Runnable. Объект этого класса передается аргументом конструктору класса Thread
при создании объекта нового потока. Поскольку создание потока не означает
его запуск, поток запускается с помощью метода start(), вызываемого из объ-
екта потока (объект класса Thread).
Часто процесс создания нового потока реализуется по следующей схеме.
1. При расширении интерфейса Runnable в соответствующем классе (для удоб-
ства назовем его внешним) не только определяется метод run(), но и описы-
вается поле — объект класса Thread.
2. Создание объекта класса Thread (объекта потока), ссылка на который присваи-
вается полю Thread, выполняется в конструкторе внешнего класса. При созда-
нии этого объекта вызывается конструктор класса Thread, первым аргументом
которому передается ссылка this. Таким образом, одновременно с созданием
объекта внешнего класса создается и объект потока, причем объект потока
создается на основе объекта внешнего класса.
3. В конструкторе внешнего класса после команды создания объекта потока
(объекта класса Thread) из этого потока вызывается метод start(). Это при-
водит к запуску потока.
4. Для создания и запуска потока в главном методе программы создается объ-
ект описанного ранее класса, расширяющего интерфейс Runnable. Поскольку
для запуска потока достаточно самого факта создания объекта, нередко этот
создаваемый объект является анонимным, то есть ссылка на него ни в какие
объектные переменные не записывается.
Создание потока 287
Синхронизация потоков
операций — это обычная ситуация, когда доступ к счету имеют несколько субъ-
ектов финансово-экономической деятельности. Процесс изменения состояния
счета можно отождествить с потоком. Таким образом, может выполняться сразу
несколько потоков.
Непосредственно процесс изменения состояния счета состоит из двух этапов.
Сначала сумма, находящаяся на счету, считывается. Затем со считанным зна-
чением выполняется нужная операция, после чего новое значение вносится как
новое состояние счета. Если в процесс изменения состояния счета между считы-
ванием и записью суммы счета вклинится другой поток, последствия могут быть
катастрофическими. Например, пусть есть счет в размере 10 000 рублей. Одним
потоком сумма на счету увеличивается на 5000 рублей, а другим — уменьша-
ется на 3000 рублей. Несложно понять, что новое значение счета должно быть
равным 12 000 рублей. А теперь проанализируем такую ситуацию. Первым про-
цессом считана сумма в 10 000 рублей. После этого, но до записи первым по-
током нового значения, второй поток также считывает сумму на счету. Затем
первый поток записывает новое значение счета, то есть 15 000 рублей. После
этой оптимистичной процедуры второй поток также записывает свое значение,
но это 7000 рублей, поскольку 10 000 – 3000 = 7000. Понятно, что для банка это
хорошо, но никак не для обладателя счета. Другой пример: продажа железно-
дорожных билетов из разных касс. В этом случае из базы данных считывается
информация о наличествующих свободных местах, и на одно из них выписы-
вается билет (или не выписывается). Соответствующее место помечается как
занятое (то, на которое продан билет). Понятно, что если подобные операции
выполняются сразу несколькими потоками, возможны неприятности, поскольку
на одни и те же места могут продаваться по несколько билетов, если при вы-
писке билета другой поток успеет считать из базы данных старую информацию,
в которой не отражены вносимые при покупке билета изменения. Поэтому при
необходимости потоки синхронизируют, что делает невозможным ситуации, по-
добные описанным.
Существует два способа создания синхронизированного кода:
создание синхронизированных методов;
создание синхронизированных блоков.
В обоих случаях используется ключевое слово synchronized. Если создается син-
хронизированный метод, ключевое слово synchronized указывается в его сигна-
туре. При вызове синхронизированного метода потоком другие потоки на этом
методе блокируются — они не смогут его вызвать, пока работу с методом не
завершит первый вызвавший его поток.
Можно синхронизировать объект в блоке команд. Для этого блок выделяется
фигурными скобками, перед которыми указывается ключевое слово synchronized,
а в скобках после этого слова — синхронизируемый объект. Пример программы
с синхронизированным методом приведен в листинге 10.5.
Синхронизация потоков 295
Резюме
1. В Java поддерживается многопоточное программирование, когда несколько
частей программы выполняются одновременно.
2. Поточная модель реализуется через иерархию классов, основу которой со-
ставляют класс Thread и интерфейс Runnable.
3. Для создания потока необходимо либо расширить класс Thread, либо реали-
зовать интерфейс Runnable.
4. Запуск Java-программы заключается в выполнении главного потока. В глав-
ном потоке порождаются все дочерние потоки. Главный поток отождествля-
ется с программой.
5. Для управления потоками используются специальные встроенные методы
(в основном методы класса Thread). Метод run() содержит код потока, а метод
start() служит для запуска потока.
6. В некоторых случаях потоки необходимо синхронизировать. Обычно это тре-
буется для правильной последовательности операций с ресурсом, если потоки
имеют доступ к одному ресурсу. Синхронизация выполняется с использова-
нием ключевого слова synchronized. Пока с ресурсом работает синхронизиро-
ванный метод, доступ других методов к этому ресурсу блокируется.
Глава 11. Система ввода-вывода
А с обратной стороны я вас попрошу сделать дверь.
Чтобы можно было войти и, когда надо, выйти.
Из к/ф «Человек с бульвара Капуцинов»
1
Не путать с программными потоками (thread), рассматриваемыми в главе 10.
300 Глава 11. Система ввода-вывода
Для того чтобы можно было воспользоваться утилитами библиотеки Swing, ко-
мандой import javax.swing.* в заголовке файла программы выполняем импорт
соответствующего пакета.
Как и ранее, в главном методе программы объявляется текстовое поле name для
записи имени пользователя и числовое поле age для записи возраста пользова-
теля. Далее выводится приглашение пользователю указать свое имя, однако для
считывания имени используется команда
name=JOptionPane.showInputDialog("Укажите Ваше имя")
В результате открывается диалоговое окно, содержащее текстовое поле ввода
(рис. 11.1).
Работа с файлами
Сидоров
Игорь
Степанович
46
526-00-13
Иванов
Семен
Николаевич
61
522-16-48
В частности, после фамилии сотрудника построчно указываются его имя, от-
чество, возраст и телефон.
Программой выводится запрос (диалоговое окно с полем ввода) для ввода имени
файла, в котором производится поиск. После этого во втором диалоговом окне
необходимо указать фамилию сотрудника для поиска в файле. Если совпадение
по фамилии найдено, для данного сотрудника выводится весь «послужной спи-
сок»: фамилия, имя, отчество, возраст и телефон. Если сотрудника с указанной
фамилией нет, на консоль выводится соответствующее сообщение.
Инструкциями import java.io.* и import javax.swing.* подключаются пакеты
java.io (для использования классов системы ввода-вывода) и javax.swing (для
использования утилит библиотеки Swing и, в частности, диалогового окна с по-
лем ввода).
В главном методе программы объявляются три текстовых поля класса String:
поле fileName для записи имени файла, в котором осуществляется поиск, поле
name для записи фамилии сотрудника и поле s для записи строк, считываемых
из файла.
Работа с файлами 315
Если при запуске программы в первое окно ввести имя файла personal.txt
(рис. 11.3) и фамилию Сидоров (рис. 11.4), результат выполнения программы
будет следующим:
Фамилия : Сидоров
Имя : Игорь
Отчество : Степанович
Возраст : 46
Тел. : 526-00-13
Если фамилию сотрудника указать неправильно (то есть такую, которой нет
в файле personal.txt), появится сообщение
Такого сотрудника нет!
Если неверно указано имя файла, будет получено сообщение
Ошибка доступа к файлу: java.io.FileNotFoundException: F:\Java_2\Files\df
(Не удается найти указанный файл)
При этом до ввода фамилии дело не доходит — работа программы завершается.
Хотя при желании можно сделать так, чтобы этого не происходило.
Резюме
1. В Java ввод и вывод данных реализуется через байтовые и символьные по-
токи ввода-вывода.
2. Иерархия байтовых потоков основана на абстрактных классах InputStream
и OutputStream (в них определены методы read() и write() для чтения-записи
данных).
3. На вершине иерархии классов символьных потоков находятся абстрактные
классы Reader и Writer (с методами read() и write()).
Резюме 317
Окно имеет название Новое окно, и все, что можно полезного сделать с этим
окном (не считая, разумеется, операций свертывания-развертывания, перемеще-
ния по экрану и изменения размеров перетаскиванием границ) — это закрыть
его щелчком на системной кнопке в правом верхнем углу строки заголовка окна.
Собственно, для обеспечения этой минимальной функциональности (имеется
в виду возможность закрыть в нормальном режиме окно) и нужен класс обра-
ботчика события закрытия окна.
Для определения главного и единственного окна программы предназначен класс
JustAFrame, который создается наследованием класса Frame библиотеки AWT.
Все описание класса состоит из конструктора, у которого два целочисленных
аргумента — они определяют размеры создаваемого окна. Первой командой
super("Новое окно") в конструкторе вызывается конструктор суперкласса с тек-
стовым аргументом, который определяет название создаваемого окна — оно ото-
бражается в строке заголовка. Командой setSize(a,b) с помощью унаследован-
ного из класса Frame метода setSize() задаются (в пикселях) размеры окна по
горизонтали и вертикали.
Создание простого окна 321
Несложно заметить, что внешне окна на рис. 12.1 и 12.2 отличаются (имеется
в виду стиль окон). Легкие компоненты, то есть те, что созданы на основе би-
блиотеки Swing, обычно больше соответствуют стилю окон используемой опе-
рационной системы.
Обработка событий
Приложение с кнопкой
// Создание панели:
MyPanel panel=new MyPanel();
setSize(300,200); // Размер окна
// Закрытие окна:
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocation(a,b); // Положение окна
add(panel); // Добавление панели
setVisible(true); // Отображение окна
}}
// Класс панели:
class MyPanel extends JPanel{
// Конструктор:
MyPanel(){
// Создание кнопки:
JButton button=new JButton("Создать новое окно");
add(button); // Добавление кнопки на панель
button.addActionListener(listener);} // Регистрация обработчика
// Обработчик для кнопки - объект анонимного класса:
ActionListener listener=new ActionListener(){
public void actionPerformed(ActionEvent event){
Random rnd=new Random();
// Создание окна со случайными координатами размещения на экране:
MyFrame frame=new MyFrame(rnd.nextInt(800),rnd.nextInt(500));}};
}
class FrameAndButton{
public static void main(String args[]){
// Создание первого окна:
MyFrame frame=new MyFrame(100,100);}
}
Действие программы состоит в следующем: при запуске программы открывает-
ся окно с кнопкой и названием Окно с кнопкой: 1, показанное на рис. 12.4.
Кнопка имеет название Создать новое окно. При щелчке на кнопке открывается
еще одно окно — практически копия первого. Отличается только номер в на-
звании. Расположение нового окна на экране выбирается случайным образом.
328 Глава 12. Создание программ с графическим интерфейсом
Далее при щелчке на любой из кнопок в первом или втором окне открывается
такое же третье окно (с порядковым номером три), причем его положение на
экране также выбирается случайно и т. д. Если закрыть хотя бы одно окно
щелчком на системной кнопке в строке заголовка окна, закрываются все окна.
Результат выполнения программы показан на рис. 12.5.
На рис. 12.7 показано, как мог бы выглядеть экран в процессе работы прило-
жения.
продолжение
336 Глава 12. Создание программ с графическим интерфейсом
продолжение
338 Глава 12. Создание программ с графическим интерфейсом
Класс Описание
цвет кривой для графика функции. Для этого командой String name=P.CbG.
getSelectedCheckbox().getLabel() объявляется текстовая переменная name и в ка-
честве значения этой переменной присваивается текст установленного пользо-
вателем переключателя. Объект этого переключателя в группе CbG возвращается
методом getSelectedCheckbox(). Из этого объекта вызывается метод getLabel(),
которым в качестве значения возвращается текст переключателя. Затем с помо-
щью вложенных условных инструкций проверяется считанное значение, и полю
clr присваивается соответствующее значение (Color.RED, Color.BLUE или Color.
BLACK).
Также в классе Plotter описан метод f() с одним аргументом типа double. Этот
метод определяет функциональную зависимость, отображаемую на графике.
Метод класса remember() с аргументом — объектом класса BPanel в качестве зна-
чения возвращает объект класса, созданный на основе панели, переданной в ка-
честве аргумента методу. Этот метод используется в тех случаях, когда необхо-
димо запомнить состояние элементов управления панели, чтобы на его основе
можно было нарисовать картинку с графиком функции.
Отображение графика функции и сопутствующих ему атрибутов реализуется
с помощью метода plot() класса Plotter. В этом методе аргументом является
объект Fig класса Graphics. Это графический контекст — объект, через который
реализуется графическое представление компонента. Обычно для создания гра-
фического контекста компонента (в данном случае панели) используется метод
getGraphics() этого компонента.
Командами H=getHeight() и W=getWidth() определяются размеры панели, в ко-
торой будет отображаться график. Командами h=H-2*s и w=W-2*s определяются
фактические размеры области отображения графика (при этом переменная s
определяет ширину поля вокруг графика функции).
Командой Fig.clearRect(0,0,W,H) выполняется очистка области панели. Это не-
обходимо делать для того, чтобы при выводе графика старое изображение уби-
ралось. Метод вызывается из объекта графического контекста компонента, а ар-
гументами ему передаются координаты левой верхней точки области очистки
и ее ширина и высота.
Сначала отображаются координатные оси. Для этого командой Fig.setColor
(Color.BLACK) устанавливается черный цвет линий, а командами Fig.drawLine
(s,s,s,h+s) и Fig.drawLine(s,s+h,s+w,s+h) непосредственно отображаются коор-
динатные оси. Линии (точнее, отрезки прямых) выводятся с помощью метода
drawLine(), аргументами которому передаются координаты начальной и конеч-
ной точек отрезка. Засечки и текстовые обозначения координатных осей ото-
бражаются в рамках цикла. Вывод текста в графическом формате осуществля-
ется методом drawString(). Аргументом метода указывается отображаемый текст
и координаты для вывода этого текста. Для преобразования действительных
чисел в формат текстовой строки используется метод toString() класса Double.
Переменная nums определяет количество линий сетки.
Создание графика функции 353
Если установлен флажок вывода сетки (значение переменной status равно true),
отображается сетка. Для этого командой Fig.setColor(gclr) задается цвет линий
сетки, а затем в цикле прорисовываются линии сетки.
Для отображения кривой задается цвет линии графика (командой Fig.
setColor(clr)). График строится по базовым точкам. Расстояние по горизонталь-
ной оси (в пикселях) между базовыми точками определяется переменной step.
Эти точки соединяются линиями, а также выделяются квадратами. В последнем
случае вызывается метод drawRect(), аргументами которому передаются коорди-
наты левой верхней точки отображаемого прямоугольника и его размеры (ши-
рина и высота).
На этом описание внутреннего класса Plotter завершается.
Конструктору класса-контейнера PPanel в качестве аргументов передаются ко-
ординаты верхней левой точки панели во фрейме, ее размеры (ширина и вы-
сота), а также объект P класса BPanel (то есть панель с элементами управле-
ния). В конструкторе командой G=new Plotter(P) создается новый объект класса
Plotter и записывается в поле G. Белый цвет фона устанавливается командой
setBackground(Color.WHITE). Границы панели определяются с помощью метода
setBounds(), которому аргументом передаются первые четыре аргумента конструк-
тора.
Также в классе PPanel переопределяется метод paint(). Этот метод автоматически
вызывается при перерисовке компонентов, например при разворачивании свер-
нутого окна. Если метод не переопределить, то в конечном варианте программы
сворачивание или разворачивание окна будет приводить к исчезновению графи-
ка функции. Аргументом методу передается графический контекст компонента.
В данном случае метод переопределен так, что при его вызове выполнятся ко-
манда G.plot(g) (здесь g — аргумент метода paint(), то есть графический кон-
текст перерисовываемого компонента), в результате выводится график функции.
Класс фрейма PlotFrame создается на основе класса Frame. Конструктору класса
передаются два числа W и H — размеры окна. Собственно, весь класс состоит фак-
тически из конструктора. В частности, командой setTitle("График функции") в кон-
структоре задается название окна (отображается в строке заголовка окна). По-
ложение окна на экране и его размеры задаются командой setBounds(100,50,W,H).
Серый цвет фона устанавливается с помощью команды setBackground(Color.GRAY),
а менеджер размещения компонентов отключается командой setLayout(null).
Командой Font f=new Font("Arial",Font.BOLD,11) создается шрифт (объект f клас-
са Font), и этот шрифт устанавливается как шрифт фрейма с помощью команды
setFont(f). Также в главное окно добавляются (а сначала создаются) три панели.
Панель с кнопками создается командой BPanel BPnl=new BPanel(6,25,W/4,H-30).
Добавляется в главное окно панель командой add(BPnl). Панель для отображе-
ния графика создается командой PPanel PPnl=new PPanel(W/4+10,25,3*W/4-15,H-
120,BPnl), а добавляется в окно командой add(PPnl). Панель для вывода справки
создается командой HPanel HPnl=new HPanel(W/4+10,H-90,3*W/4-15,85), а добавляет-
ся в окно командой add(HPnl).
354 Глава 12. Создание программ с графическим интерфейсом
Калькулятор
TF.setEditable(false);
// Добавление поля на панель:
add(TF);
// Создание обработчика щелчка на кнопке:
BtnPressed=new BtnAction(TF);
// Список названий кнопок:
String[] BtnTxt={"1","2","3","+","4","5","6","-
","7","8","9","/","0",".","=","*"};
// Создание кнопок и добавление их на панель:
for(int i=0;i<BtnTxt.length;i++){
addBtn(sx+(w+sx)*(i%4),(2*sy+h)+(sy+h)*(i/4),w,h,BtnTxt[i],BtnPressed);}
// Создание кнопки сброса параметров:
JButton BtnC=new JButton("C");
// Размер и положение кнопки:
BtnC.setBounds(4*sx+3*w,sy,w,h);
// Добавление обработчика для кнопки:
BtnC.addActionListener(BtnPressed);
// Режим отсутствия выделения названия кнопки при активации:
BtnC.setFocusPainted(false);
// Красный цвет для названия кнопки:
BtnC.setForeground(Color.RED);
// Добавление кнопки на панель:
add(BtnC);}
// Метод для создания и добавления кнопок
// (аргументы - положение и размер кнопки, название и обработчик щелчка):
void addBtn(int i,int j,int w,int h,String txt,ActionListener AcList){
// Создание кнопки:
JButton b=new JButton(txt);
// Размер и положение кнопки:
b.setBounds(i,j,w,h);
// Режим отсутствия выделения названия кнопки при активации:
b.setFocusPainted(false);
// Добавление обработчика для кнопки:
b.addActionListener(AcList);
// Добавление кнопки на панель:
add(b);}
}
// Класс обработчика щелчка на кнопке:
class BtnAction implements ActionListener{
// Текстовое поле для вывода информации:
public JTextField TF;
// Индикатор состояния ввода числа:
private boolean start;
// Индикатор состояния ввода десятичной точки:
private boolean point;
// Текстовое представление последнего введенного оператора:
продолжение
358 Глава 12. Создание программ с графическим интерфейсом
TF.setText("0.");
point=false;
start=false;
return;}
else{// Ввод цифры в начале ввода числа
TF.setText(str);
start=false;
return;}
}
else{// Продолжение ввода числа
if(str.equals(".")){// Попытка ввести точку
str=point?str:"";
point=false;}
// Добавление цифры к числу:
// Незначащий первый ноль:
if(TF.getText().equals("0")&!str.equals(".")) TF.setText(str);
else TF.setText(TF.getText()+str);}
}}
// Класс с главным методом программы:
class MyCalculator{
public static void main(String[] args){
// Создание окна:
new CalculatorFrame();
}}
На рис. 12.11 представлено графическое окно программы.
else stl=Font.PLAIN;
// Размер шрифта:
int size=Integer.parseInt(getParameter("размер"));
// Текст для отображения:
text="Шрифт "+font+" "+style+" размера "+size;
// Применение атрибутов к шрифту:
setFont(new Font(font,stl,size));}
// Метод отрисовки апплета:
public void paint(Graphics g){
g.drawString(text,30,getHeight()-30);}
}
Помимо обычного по сравнению со встречавшимся ранее HTML-кодом в ли-
стинге 12.12 появляется группа тегов вида:
<param name="значение1" value="значение2">
В данном случае значение1 является названием параметра, который передается
апплету, а значение2 — значением этого параметра. Например, инструкция <param
name="цвет шрифта" value="желтый"> означает, что апплету передается параметр,
который называется цвет шрифта, а значение этого параметра равно желтый. Все
значения параметров апплета считываются в апплете методом getParameter()
в текстовом формате. Аргументом методу передается текстовое название счи-
тываемого параметра. Например, чтобы считать значение параметра цвет шрифта,
метод getParameter() в коде апплета вызывается в формате getParameter("цвет
шрифта"), а результатом выполнения такой команды является текстовое значение
"желтый".
Что касается кода апплета (см. листинг 12.13), то класс апплета ShowText состо-
ит всего из трех методов (getColor(), init() и paint()) и одного текстового поля
text. Текстовое поле text предназначено для записи текстовой фразы, отобра-
жаемой в области апплета. Поскольку этот текст формируется в методе init(),
а отображается переопределяемым методом paint() (то есть оба метода долж-
ны иметь доступ к соответствующей текстовой переменной), текст реализуется
в виде поля класса апплета.
Кроме формирования текстового поля text, в методе init() производится
считывание параметров апплета. Командой String color=getParameter("цвет")
объявляется текстовая переменная color и в качестве значения ей присваи-
вается значение параметра цвет, указанное в HTML-документе. После это-
го командой setBackground(getColor(color)) применяется цвет для фона. Здесь
использован метод getColor(), с помощью которого выполняется преобра-
зование текстового значения переменной color в объект класса Color (метод
getColor() описывается далее). Этот же метод getColor() используется в коман-
де setForeground(getColor(color)), которой задается цвет отображения текста.
Предварительно командой color=getParameter("цвет шрифта") меняется значение
переменной color — теперь это цвет, указанный в качестве значения параметра
374 Глава 12. Создание программ с графическим интерфейсом
Резюме
1. Существуют две библиотеки, которые используются для создания прило-
жений с графическим интерфейсом: AWT и Swing. Вторая из них является
дополнением к библиотеке AWT.
2. Обычно приложение содержит главное окно или фрейм. Фрейм реализуется
как объект класса, наследующего класс Frame (библиотека AWT) или класс
JFrame (библиотека Swing). Существуют специальные классы и для прочих
элементов графического интерфейса, таких, например, как кнопки или пере-
ключатели.
3. Взаимодействие компонентов графического интерфейса реализуется посред-
ством обработки событий. В Java используется модель обработки событий
с делегированием: в компоненте, который может вызвать то или иное собы-
тие, регистрируется обработчик этого события. Для обработчиков событий
создаются специальные классы путем расширения соответствующего интер-
фейса.
4. Помимо приложений, которые выполняются под управлением операционной
системы, существует особый вид Java-программ — апплеты, выполняющиеся
под управлением браузера и обычно загружаемые через Интернет. Апплет
создается расширением класса Applet или JApplet. У апплетов есть некоторые
особенности: например, в них отсутствует метод main(). По сравнению с обыч-
ными приложениями апплеты имеют ограниченные возможности. Однако
именно благодаря этому они более безопасны, особенно при загрузке через
Интернет.
Заключение
В книге представлена лишь малая часть из того, что можно было бы написать
о Java. При написании книги всегда стоит вопрос о том, что в книгу включать,
а что — нет. По большому счету, успех книги зависит от того, насколько удачен
ответ на этот вопрос.
В данном случае решалась двойная задача. Во-первых, хотелось описать базовые
конструкции и основы синтаксиса языка Java. Во-вторых, среди множества тем
и подходов были выбраны те, что наиболее интересны и перспективны и при
этом вписываются в концепцию книги. Тем не менее список тем, не вошедших
в книгу, может быть предметом еще для нескольких книг. Поэтому в конце кни-
ги приведен список литературы, который может быть полезен тем, кто желает
продолжить свое самообразование и приобщение к миру Java. Список неболь-
шой, но показательный. Даже если в перечисленных изданиях освещены те же
или почти те же вопросы, что и в данной книге, всегда полезно узнать, что по
поводу одних и тех же проблем думают разные люди. В любом случае, хочется
пожелать читателю успехов и выразить благодарность за интерес к книге.
Литература
1. Ноутон П., Шилдт Г. Java 2: Наиболее полное руководство. СПб.: БХВ, 2006.
1072 с.
2. Хабибулин И. Самоучитель Java. СПб.: БХВ, 2008. 720 с.
3. Хорстманн К.С., Корнелл Г. Java 2. Библиотека профессионала. Т. 1, Основы.
М.: Вильямс, 2009. 816 с.
4. Хорстманн К.С., Корнелл Г. Java 2. Библиотека профессионала. Т. 2, Тонкости
программирования. М.: Вильямс, 2010. 992 с.
5. Монахов В. Язык программирования Java и среда NetBeans. СПб.: БХВ, 2009.
720 с.
6. Шилдт Г. Java: руководство для начинающих. М.: Вильямс, 2008. 720 с.
7. Шилдт Г. Java: методики программирования Шилдта. М.: Вильямс, 2008.
512 с.
8. Фишер Т. Р. Java. Карманный справочник. М.: Вильямс, 2008. 224 с.
9. Шилдт Г. Библиотека SWING для Java: руководство для начинающих. М.:
Вильямс, 2007. 704 с.
10. Шилдт Г. Полный справочник по Java SE 6. М.: Вильямс, 2010. 1040 с.
11. Эккель Б. Философия Java. СПб.: Питер., 2009. 640 с.
Приложение
Программное обеспечение
В этом приложении приведена краткая справка по программному обеспечению,
которое может оказаться полезным (а некоторые утилиты и просто необходимы-
ми) для успешной работы с Java. При этом в приложении приведен минималь-
ный объем информации относительно загрузки и использования программного
обеспечения — в объеме, необходимом для того, чтобы читатель смог успешно
откомпилировать и запустить приведенные в основной части книги программ-
ные коды. Читателю, интересующемуся вопросами работы со специальным про-
граммным обеспечением для Java, можем порекомендовать обратиться к специ-
альной или справочной литературе по этому вопросу.
Работа с NetBeans
Окно среды разработки NeBeans версии 6.8 представлено на рис. П.4.
Для создания проекта в меню File выбирают команду New Project, как показано
на рис. П.5.
Работа с Eclipse
Во многом методы работы в среде Eclipse напоминают методы работы в NetBeans,
поэтому в данном случае кратко остановимся на способе создания и запуска Java-
приложения с помощью редактора и утилит среды Eclipse. На рис. П.21 представ-
лено окно среды Eclipse, в котором выбрана команда FileNewJava Project.
Подписано в печать 27.08.10. Формат 70х100/16. Усл. п. л. 32,25. Тираж 2000. Заказ 0000.
ООО «Лидер», 194044, Санкт-Петербург, Б. Сампсониевский пр., 29а.
Налоговая льгота — общероссийский классификатор продукции ОК 005-93, том 2; 95 3005 — литература учебная.
Отпечатано по технологии CtP в ОАО «Печатный двор» им. А. М. Горького.
197110, Санкт-Петербург, Чкаловский пр., 15.
397
ПРЕДСТАВИТЕЛЬСТВА ИЗДАТЕЛЬСКОГО ДОМА «ПИТЕР»
предлагают эксклюзивный ассортимент компьютерной, медицинской,
психологической, экономической и популярной литературы
РОССИЯ
Санкт/Петербург м. «Выборгская», Б. Сампсониевский пр., д. 29а
тел./факс: (812) 7037373, 7037372; email: [email protected]
Москва м. «Электрозаводская», Семеновская наб., д. 2/1, корп. 1, 6й этаж
тел./факс: (495) 23438-15, 97434-50; e-mail: [email protected]
Воронеж Ленинский пр., д. 169; тел./факс: (4732) 396170
email: piterctr@сomch.ru
Екатеринбург ул. Бебеля, д. 11а; тел./факс: (343) 3789841, 3789842
еmail: [email protected]
Нижний Новгород ул. Совхозная, д. 13; тел.: (8312) 412731
email: [email protected]
Новосибирск ул. Станционная, д. 36; тел.: (383) 363-01-14
факс: (383) 3501979; email: [email protected]
Ростов/на/Дону ул. Ульяновская, д. 26; тел.: (863) 2699122, 2699130
еmail: piter[email protected]
Самара ул. Молодогвардейская, д. 33а; офис 223; тел.: (846) 2778979
e-mail: [email protected]
УКРАИНА
Харьков ул. Суздальские ряды, д. 12, офис 10; тел.: (1038057) 751-10-02
758-41-45; факс: (1038057) 712-27-05; е-mail: [email protected]
Киев Московский пр., д. 6, корп. 1, офис 33; тел.: (1038044) 4903569
факс: (1038044) 4903568; еmail: [email protected]
БЕЛАРУСЬ
Минск ул. Притыцкого, д. 34, офис 2; тел./факс: (1037517) 201-48-79, 201-48-81
еmail: [email protected]
СТАНЬТЕ УЧАСТНИКОМ
ПАРТНЕРСКОЙ ПРОГРАММЫ ИЗДАТЕЛЬСТВА «ПИТЕР»!
Зарегистрируйтесь на нашем сайте в качестве партнера
по адресу www.piter.com/ePartners
Получите свой персональный уникальный номер партнера