- •Основы программирования на языке java в среде eclipse
- •Ответственный за выпуск: в.И. Павловський, зав. Кафедрою информационных и компьютерных систем, канд. Техн. Наук, доцент.
- •2.1 Задание на лабораторную работу 19
- •7.3 Порядок выполнения работы 103
- •7.4 Содержимое отчета 103
- •7.5 Контрольные вопросы 104
- •Введение
- •1Лабораторная работа №1 Изучение среды разработки Eclipse
- •1.1 Задание на лабораторную работу
- •1.2 Краткие теоретические сведения
- •1.2.1Создание проекта
- •1.2.2Создание нового класса Java
- •1.3 Порядок выполнения работы
- •1.4 Содержимое отчета
- •Краткие теоретические сведения.
- •1.5 Контрольные вопросы
- •2Лабораторная работа №2 Основы языка Java
- •2.1 Задание на лабораторную работу
- •2.2 Краткие теоретические сведения
- •2.2.1Создание объектов
- •2.2.2Примитивные типы
- •2.2.3Числа высокой точности
- •2.2.4Уничтожение объектов
- •2.2.5Видимость имен
- •2.2.6Использование других компонентов
- •2.2.7Ключевое слово static
- •2.2.8Массивы
- •2.2.9Обработка ошибок с помощью исключений
- •2.3 Порядок выполнения работы
- •2.4 Содержимое отчета
- •2.5 Контрольные вопросы и задания
- •3Лабораторная работа №3 Объектная модель языка Java
- •3.1 Задание на лабораторную работу
- •3.2 Краткие теоретические сведения
- •3.3 Порядок выполнения работы
- •Краткие теоретические сведения.
- •4.2 Краткие теоретические сведения
- •4.2.1Классы дерева и узла
- •4.2.2Вопросы сокрытия реализации
- •4.2.3Пользовательский интерфейс
- •4.3 Порядок выполнения работы
- •4.4 Содержимое отчета
- •Краткие теоретические сведения.
- •4.5 Контрольные вопросы
- •5Лабораторная работа №5 Изучение основ объектно-ориентированного программирования на языке Java. Часть 2
- •5.1 Задание на лабораторную работу
- •5.2 Краткие теоретические сведения
- •5.2.1Классы деревьев и интерфейсы
- •5.2.2Рекурсивный проход по деревьям разных классов
- •5.2.3Расширение ранее созданных классов специфическими операциями пользователей
- •5.2.4Вопросы оптимизации кода
- •5.3 Порядок выполнения работы
- •5.4 Содержимое отчета
- •Краткие теоретические сведения.
- •5.5 Контрольные вопросы
- •6Лабораторная работа №6 Изучение коллекций Java и системы ввода-вывода
- •6.1 Задание на лабораторную работу
- •6.2 Краткие теоретические сведения
- •6.2.1Представление и реализация дерева на основе коллекций
- •В описании узла дерева необходимо создать и инициализировать объект класса список или набор сыновей, например
- •Количество сыновей узла дерева определяется следующим образом:
- •Элементу набора с индексом I выполняется путем получения массива из набора
- •6.2.2Представление и реализация дерева на основе ассоциативных массивов (карт отображений)
- •В описании узла дерева необходимо создать и инициализировать объект ассоциативный массив сыновей, например
- •Количество сыновей узла дерева определяется следующим образом:
- •6.2.3Доступ к коллекции или ассоциативному массиву через итератор
- •6.2.4Использование обобщений Java 5
- •В описании узла дерева необходимо создать и инициализировать коллекцию настраиваемый список, например
- •6.2.5Сериализация и десериализация дерева в файл
- •6.2.6Ввод и вывод в потоки со сжатием данных
- •6.3 Порядок выполнения работы
- •Краткие теоретические сведения.
- •7.2 Краткие теоретические сведения
- •7.2.1Многопоточность
- •7.2.2Процессы, потоки и приоритеты
- •7.2.3Приоритеты потоков в приложениях Java
- •7.2.4Реализация многопоточности в Java
- •7.2.5Функциональность класса Thread
- •7.2.6Реализация интерфейса Runnable
- •7.2.7Синхронизация потоков
- •7.2.8Синхронизация методов
- •7.2.9Блокировка потока
- •7.2.10Синхронизация доступа к совместно используемым данным.
- •7.2.11Избыточная синхронизация
- •7.2.12Вызов метода wait
- •7.2.13Документирование уровней безопасности
- •7.2.14Работа с графикой Графика 2d
- •Пространства координат
- •Режим рисования
- •Создание цвета
- •Основные методы рисования
- •Рисование фигур средствами Java2d
- •Класс BasicStroke
- •Класс GeneralPath
- •Классы GradientPaint и TexturePaint
- •7.3 Порядок выполнения работы
- •7.4 Содержимое отчета
- •Краткие теоретические сведения;
- •7.5 Контрольные вопросы
- •Многопоточность;
- •РекомендУемая литература
5.2.2Рекурсивный проход по деревьям разных классов
Способ обращения к деревьям не должен зависеть от вида дерева, а реализация этого способа для каждого дерева должна быть своя собственная. Для решения поставленной задачи необходимо:
Описание в интерфейсном классе узла ITree рекурсивных методов, например void forwardTraverse( );
Реализация этих методов в классах LinkedTree и ArrayTree, например
public void forwardTraverse( ){…}
Вызов такого метода, с параметром в виде ссылки на узел типа LinkedNode или ArrayNode, например forwardTraverse(pNode);
Проблема описания и реализации интерфейсных методов forwardTraverse( ) или backwardTraverse( ), которые в качестве параметра получают ссылку на узел дерева, состоит в том, что узлы LinkedNode и ArrayNode имеют разные типы. Поэтому общий для них интерфейсный метод, требующий в качестве параметра ссылку на тот или иной узел дерева, в качестве параметра должен иметь параметр типа интерфейс INode, например
void forwardTraverse(INode pNode) |
Далее в классе LinkedTree или ArrayTree необходимо описать реализацию этих методов, например
public void forwardTraverse(INode pNode) {…..} |
После этого в требуемом месте программы можно вызвать такой метод, передав ему ссылку на корень дерева, например:
ITree pTree = new LinkedTree(); … pTree.forwardTraverse(pTree.GetRoot()); |
Таким образом, становится понятной необходимость в интерфейсах и их реализации в конкретных предметных классах.
Поскольку обычно производится проход по дереву начиная с корневого узла, имеет смысл снабдить интерфейс ITree дополнительными методами-оболочками, которые не принимают ссылку на корень дерева и используют внутри метод getRoot() для получения такой ссылки. Например:
public void forwardTraverse() { forwardTraverse(getRoot()) } |
5.2.3Расширение ранее созданных классов специфическими операциями пользователей
Проблема состоит в том, что в Java, в отличии от C++, нет понятия указатель на функцию, с помощью которого можно настроить общий метод на специфические действия.
Поэтому в Java операция, настраивающая другую операцию, должна быть представлена в виде объекта. Для создания такого объекта можно воспользоваться несколькими подходами. Все они на начальном этапе предполагают:
Создание дополнительного программного интерфейса, например TreeOperation, содержащего специфические пользовательские методы, принимающие узел дерева в качестве параметра, например,
void processNode (INode pNode);
Пополнить интерфейс ITree новыми объявлениями методов forwardTraverse и backwardTraverse, например, для метода forwardTraverse
void forwardTraverse(INode pNode, TreeOperation pTreeOperation) |
Такой метод будет, в частности, в качестве параметра принимать экземпляр класса, реализующего интерфейс TreeOperation, и в нужный момент будет вызывать требуемый метод данного интерфейса, тем самым, эмулируя вызов функции по указателю, известный вам по языку C++.
В каждом из классов реализации LinkedTree и ArrayTree выполнить реализацию нового метода прохода по дереву, например
public void forwardTraverse(INode pNode, TreeOperation pTreeOperation){ pTreeOperation.processNode(pNode); … } |
Далее рассмотрим наиболее распространенные подходы.
I. Первый – универсальный, подход предполагает выполнение следующих двух дополнительных шагов:
Создать класс реализации интерфейса, например PrintTreeOperation, и выполнить в нем реализацию методов интерфейса TreeOperation, например:
public void processNode(INode pNode){
System.out.println(pNode.getData())
}
После этого в требуемом месте программы можно вызвать метод forwardTraverse( ), передав ему ссылку на объект класса PrintTreeOperation, например
ITree pTree = new LinkedTree() |
далее так:
TreeOperation pTreeOperation = new PrintTreeOperation()//ссылка //на объект класса реализации PrintTreeOperation pTree.forwardTraverse(pTreeOperation) //вызов |
или компактнее:
pTree.forwardTraverse(new PrintTreeOperation()) //вызов |
Достоинство такого подхода в том, что класс реализации операций пользователя является внешним и позволяет описывать методы любой сложности. Такой подход к реализации выполняемых действий целесообразно применять, если реализация сложная и требует частого использования несколькими различными классами. Если же действия необходимо выполнять только при проходе по дереву, и они являются достаточно простыми – есть смысл рассмотреть следующие подходы.
Следующие два подхода II и III, носят преимущественно учебно-иллюстративный характер и логически подводят к IV и V подходам, которые так же могут рассматриваться как достаточно универсальные и широко используемые.
II. Второй подход предполагает создание дополнительного внутреннего класса, реализующего пользовательскую операцию. Для этого необходимо выполнение следующих трех дополнительных шагов:
В классах LinkedTree и ArrayTree создать внутренние классы, реализующие интерфейс операции, например:
1 public class LinkedTree {
2 private class PrintTreeOperation implements TreeOperation{
3 public void processNode(INode pNode){
4 //собственная реализация интерфейса операции
5 }
6 } // PrintTreeOperation
7 …
8 } // LinkedTree
В интерфейсе ITree и соответственно в классах LinkedTree и ArrayTree создать соответствующие методы, возвращающие ссылку на объект внутреннего класса PrintTreeOperation, например
public TreeOperation getPrintOperation() {
return new PrintTreeOperation();
}
После этого в требуемом месте программы можно вызвать метод forwardTraverse(….), передав ему ссылку на объект класса PrintTreeOperation, например:
ITree pTree = new LinkedTree() … pTree.forwardTraverse(pTree.getPrintOperation()); |
Достоинство такого подхода в том, что класс реализации операции пользователя располагается непосредственно в классе реализации дерева, что позволяет при реализации обращаться к закрытым полям дерева. Однако использование внутреннего класса не всегда удобно, так как предполагает изменение реализации класса дерева, его интерфейса при необходимости добавления новых операций. Таким образом, данный подход целесообразно применять лишь для реализации общедоступных операций, которые могут часто понадобиться клиентам дерева.
III. Третий подход подобен второму, но предполагает создание присущего Java безымянного (анонимного) внутреннего класса, реализующего пользовательскую операцию. Для этого необходимо выполнение следующих двух дополнительных шагов. В классах LinkedTree и ArrayTree создать безымянные внутренние классы, реализующие интерфейс операции TreeOperation.
1 public class LinkedTree { 2 public TreeOperation getPrintOperation(){ 3 new TreeOperation(){ //это задание безымянного класса 4 public void processNode(INode pNode){ 5 //собственная реализация интерфейса операции 6 } 7 } 8 … 9} |
В этом случае метод getPrintOperation() генерирует ссылку на объект безымянного класса.
После этого в требуемом месте программы можно вызвать метод forvardTraverse(….), передав ему результат выполнения метода getPrintOperation(), например:
ITree pTree = new LinkedTree() … pTree.forwardTraverse(pTree.getPrintOperation()) |
Достоинства и недостатки такого подхода те же что и в предыдущем случае, разница состоит лишь в более компактном способе объявления класса реализации.
IV. Четвертый подход основан на использовании безымянного внутреннего класса, который описывается непосредственно при вызове метода прохода по дереву. Такое решение подходит для случая, когда метод пользователя состоит из одного – двух простых операторов или вызовов других методов, иначе код становится тяжело читаемым.
В результате без дополнительных шагов в требуемом месте программы можно вызвать метод forwardTraverse(….), задав определение безымянного класса:
ITree pTree = new LinkedTree(); … pTree.forwardTraverse(new TreeOperation() { public void processNode (INode pNode){ //один – два оператора, например System.out.println(pNode.getData()); } };) // использование символа ";" здесь обязательно |
Этот подход является иллюстрацией того, ради чего в Java в первую очередь и был введен механизм безымянных внутренних классов. На этом принципе построено описание слушателей (Listener) событий в Java. Такой подход позволяет превратить проход по дереву в любую операцию, которая объявляется прямо в месте использования. Например, для выполнения слияния всех данных дерева в одну последовательную строку можно произвести следующий вызов:
1 private String joinResult = ""; 2 pTree.forwardTraverse(new TreeOperation(){ 3 public void processNode(INode pNode){ 4 joinResult += pNode.getData() 5 } 6 };) |
V. Пятый подход является развитием второго и третьего, и основан на использовании определяемых пользователем констант-объектов для безымянных внутренних классов. Фактически, отличие состоит лишь в способе получения ссылки на реализацию пользовательской операции. Достоинством данного подхода является возможность использования предопределенных часто используемых операций (например, вывода данных узла на экран), без необходимости использования экземпляра дерева.
Для этого необходимо выполнение двух дополнительных шагов. В класс реализации дерева необходимо добавить константу, например PRINT_TREE_OPER, указывающую на одну из реализаций операции обработки узла, а именно вывод данных из узла на экран, например:
1 public static final TreeOperation PRINT_TREE_OPER = 2 new TreeOperation(){//это задание безымянного класса 3 public void processNode(InNode pNode){ 4 //любое количество операторов 5 System.out.println(pNode.getData()); 6 } 7 … //Возможные другие функции 8 }; |
После этого в требуемом месте программы можно вызвать метод forwardTraverse(….), передав ему константу, PRINT_TREE_OPER, указывающую на одну из реализаций операции обработки узла, например:
pTree.forwardTraverse(LinkedTree.PRINT_TREE_OPER); |
