Коллекции в java

[an error occurred while processing this directive] [an error occurred while processing this directive]

Конспект лекций по Java. Занятие 10

[an error occurred while processing this directive](none) [an error occurred while processing this directive](none)[an error occurred while processing this directive] ::
[an error occurred while processing this directive](none)
[an error occurred while processing this directive]([an error occurred while processing this directive] В.Фесюнов [an error occurred while processing this directive]) [an error occurred while processing this directive](none)

Статические вложенные классы

На прошлом занятии мы познакомились с вложенными классами Java и, в частности, с анонимными вложенными классами. Однако, мы рассмотрели не все, что относится к данной теме. Еще один «подвид» вложенных классов — это статические вложенные классы. Т.е. у inner-класса может быть описатель static.

Пример

class Outer2 { . . . static class Inner2 { . . . } . . . }

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

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

Расширенный пример

class Outer3 { String name; . . . static class Inner3 { . . . public void f(Outer3 obj) { System.out.println(obj.name); // Здесь без obj нельзя } } . . . public static Inner3 createInner() { return new Inner3(); } . . . }

В данном примере в классе Outer3 описан статический inner-класс Inner3. В этом классе метод f(…) печатает поле name охватывающего класса, но для этого в f(…) передается параметр типа Outer3. В самом классе Outer3 есть статический метод createInner(), который порождает объект класса Inner3.

Приведем также примеры создания объекта класса Inner3 извне класса Outer3.

Outer3.Inner3 obj1 = new Outer3.Inner3(); // явное порождение Outer3.Inner3 obj2 = Outer3.createInner(); // порождение через метод createInner()

Во всем остальном статические вложенные классы такие же, как и нестатические.

Коллекции объектов Java.

Подробное знакомство со стандартной библиотекой Java мы начнем с коллекций объектов . Они реализованы различными классами пакета java.util. Мы рассмотрим не весь этот пакет, а только классы, связанные с коллекциями, что, однако, составляет значительную его часть.

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

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

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

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

Начнем изучение коллекций в Java с примера.

Пример

Одним из широко используемых классов коллекций является ArrayList. Пример использования этой коллекции приводится ниже.

import java.util.*; import java.io.*; public class ArrayListTest { ArrayList lst = new ArrayList(); Random generator = new Random(); void addRandom() { lst.add(new Integer(generator.nextInt())); } public String toString() { return lst.toString(); } public static void main(String args[]) { ArrayListTest tst = new ArrayListTest(); for(int i = 0; i < 100; i++ ) tst.addRandom(); System.out.println(«Сто случайных чисел: «+tst.toString()); } }

Рассмотрим данный пример подробнее. Здесь, кроме класса ArrayList, использованы еще ряд классов бибилиотеки Java.

  • Random — класс из java.util. Расширяет возможности класса Math по генерации случайных чисел (см. документацию).
  • Integer — так называемый wrapper-класс (класс-обертка) для целых (int). Он использован потому, что в коллекцию нельзя занести данные элементарных типов, а только объекты классов.

Класс ArrayListTest имеет два поля — поле lst класса ArrayList и поле generator класса Random, используемое для генерации случайных чисел. Метод addRandom() генерирует и заносит в коллекцию очередное случайное число. Метод toString() просто обращается к методу toString() класса ArrayList, который обеспечивает формирование представления списка в виде строки.

Метод main(…) создает объект класса ArrayListTest и организует цикл порождени 100 случайных чисел с занесением их в коллекцию, вызыва метод addRandom(). После этого он печатает результат.

Оттранслируем и запустим данную программу.

Этот пример не демонстрирует особых преимуществ коллекций, а лишь технику их использования. Из него видно, что добавить элемент в коллекцию можно методом add(…) класса ArrayList и при этом мы нигде не указываем размер коллекции.

Рассмотрим коллекции более систематично.

В Java коллекции объектов разбиты на три больших категории: List (список), Set (множество) и Map (отображение).

Посмотрим документацию. В java.util определены интерфейсы:

Collection | +——- List | +——- Set Map

Эти интерфейсы составляют основу для построения классов коллекций Java.

  • List — это список объектов. Объекты можно добавлять в список (метод add()), заменять в списке (метод set()), удалять из списка (метод remove()), извлекать (метод get()). Существует также возможность организации прохода по списку при помощи итератора.
  • Set — множество объектов. Те же возможности, что и у List, но объект может входить в множество только один раз. Т.е. двойное добавление одного и того же объекта в множество не изменяет само множество.
  • Map — отображение или ассоциативный массив. В Map мы добавляем не отдельные объекты, а пары объектов ( ключ, значение ). Соответственно есть операции поиска значения по ключу . Добавление пары с уже существующим в Map ключем приводит к замене, а не к добавлению. Из отображения (Map) можно получить множество (Set) ключей и список (List) значений.

Коллекции — это наборы произвольных объектов

Общим свойством коллекций является то, что они работают с Object (базовый класс для всех классов Java). Это означает, что мы можем добавлять в коллекцию любые объекты Java, т.к. Object является базовым классом для всех классов Java. При извлечении из коллекции мы тоже получаем Object. Зачастую это приводит к необходимости преобразования полученного из коллекции объекта к нужному классу (downcasting).

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

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

Поэтому во многих случаях лучше построить на основе класса поддержки коллекции свой класс, который обеспечит нужный контроль типов во время трансляции. Например, пусть нам нужен список объектов класса MyType и мы решили использовать для его построения стандартный класс ArrayList (см. документацию). Тогда можно создать вспомогательный класс MyList такого вида:

public class MyList { private ArrayList v = new ArrayList(); public void add(MyType obj) { v.add(obj); } public MyType get(int index) { return (MyType)v.get(index); } public int size() { return v.size(); } }

Класс MyList хорош тем, что в такой список нельзя добавить ничего, кроме объектов типа MyType, а извлеченные из списка объекты сразу приводятся к типу MyType внутри метода get().

Отступление . Этот пример интересен еще одним. Он демонстрирует случай, когда вместо наследования следует применять агрегацию. Действительно, если бы мы наследовали от ArrayList, то остались бы доступными методы ArrayList, оперирующие с Object, а не с MyType и мы бы не получили желаемого эффекта.

Итераторы

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

Как и сами коллекции, итераторы базируются на интерфейсе. Это интерфейс Iterator , определенный в пакете java.util (см.документацию). Т.е. любой итератор, как бы он не был устроен, имеет следующие три метода:

  • boolean hasNext() — проверяет есть ли еще элементы в коллекции
  • Object next() — выдает очередной элемент коллекции
  • void remove() — удаляет последний выбранный элемент из коллекции.

Кроме Iterator есть еще ListIterator — это расширенный вариант итератора с доп. возможностями и Enumerator — это устаревший вариант, оставленный для совместимости с предыдущими версиями.

В свою очередь интерфейс Collection имеет метод

Iterator iterator();

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

Рассмотрим теперь интерфейс List. В дополнение к методу iterator() он имеет метод

ListIterator listIterator();

Соответственно все коллекции-списки реализуют List-итераторы.

Вернемся к примеру с коллекцией из 100 случайных чисел в начале занятия.

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

Для этого нам достаточно переписать метод toString().

public String toString() { String res = «»; Iterator iter = lst.iterator(); for(int i = 0; iter.hasNext(); i++) { if( i%6 == 0 ) res += «\n»; res += » » + iter.next().toString(); // !!! } return res; }

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

Разберем, как реализован метод toString(). Метод iterator() из ArrayList возвращает объект, ссылку на который мы запоминаем в переменной iter. Этот объект не интерфейса Iterator (нельзя построить объект интерфейса). Это объект некоторого класса, определенного внутри ArrayList, удовлетворяющего интерфейсу Iterator. Этот класс имеет свое имя, поля, возможно, какие-то private-методы. Но нас это не интересует. Мы просто приводим его к типу Iterator и используем как итератор.

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

Рассмотрим строку:

res += » » + iter.next().toString();

Извлеченный из коллекции методом next() объект мы не приводим к типу Integer, а сразу применяем метод toString().

Здесь используется то свойство, что все классы имеют метод toString() (т.к. он есть в Object).

Метод remove()

В интерфейсе Iterator определен метод remove(). Он позволяет удалить из коллекции последний извлеченный из нее объект. В то же время, в интерфейсе Collection (а это значит — во всех классах-коллекциях) есть методы remove(Object o) и removeAll().

Здесь нет двойственности возможностей. Дело в том, что удаление из коллекции объектов и вообще модификация коллекции параллельно с проходом по коллекции через итератор запрещено (выдается ConcurrentModificationException). Единственное, что разрешено — это удалить объект методом remove() из Iterator (причем, только один — последний извлеченный).

Интерфейс ListIterator расширяет эти возможности (см. метод set(Object o) интерфейса ListIterator).

Классы реализации коллекций

Рассмотрим конкретные реализации коллекций, т.е. классы, которые обеспечивают описанную выше функциональность. Мы будем рассматривать их в том же разрезе типов коллекций — List, Set, Map.

Коллекции-списки (List)

Реализованы в следующий трех вариантах ArrayList, LinkedList, Vector.

  • Класс Vector — это устаревший вариант, оставлен для совместимости с предыдущими версиями.

Класс ArrayList — используется чаще всего. Имеет два конструктора:

public ArrayList() // Конструктор пустого списка public ArrayList(Collection c) // Строит список из любой коллекции

А также следующие методы:

public int size() Возвращает количество элементов списка public boolean isEmpty() Проверяет, что список пуст public boolean contains(Object elem) Проверяет, принадлежит ли заданный объект списку public int indexOf(Object elem) Ищет первое вхождение заданного объекта в список и возвращает его индекс. Возвращает -1, если объект не принадлежит списку. public int lastIndexOf(Object elem) То же самое, но ищет последнее вхождение. public Object clone() Создает «поверхностную» копию списка. public Object[] toArray() Преобразует список в массив. public Object get(int index) Возвращает элемент коллекции с заданным номером. public Object set(int index, Object element) Заменяет элемент с заданным номером. public boolean add(Object o) Добавляет заданный объект в конец списка. public void add(int index, Object element) Вставляет элемент в указанную позицию списка. public Object remove(int index) Удаляет заданный элемент списка. public void clear() Полностью очищает список. public boolean addAll(Collection c) Добавляет к списку (в конец) все элементы заданной коллекции. public boolean addAll(int index, Collection c) Вставляет в список с указанной позиции все элементы коллекции. protected void removeRange(int fromIndex, int toIndex) Удаляет из коллекции объекты в заданном интервале индексов (исключая toIndex).

Класс LinkedList .

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

Знакомство с другими типами коллекций мы продолжим на следующем занятии.

Практическая работа

Вернемся к домашнему заданию 9-го занятия. Правильная его реализация выглядит примерно так.

// Dialog9Home.java // Пример визульного приложения на Java. Копирование из полей ввода import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Dialog9Home extends JFrame { JTextField fldi = new JTextField(20); JTextField fldo = new JTextField(20); Dialog9Home() { super(«Home Work (Lesson 9)»); try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch(Exception e) { } setSize(400, 150); Container c = getContentPane(); JLabel lblin = new JLabel(«Поле ввода «); JLabel lblout= new JLabel(«Поле вывода «); JButton btn = new JButton(«Скопировать»); JPanel pnlout = new JPanel(); JPanel pnlin = new JPanel(); pnlin.add(lblin); pnlin.add(fldi); c.add(pnlin, BorderLayout.NORTH); pnlout.add(lblout); pnlout.add(fldo); c.add(pnlout, BorderLayout.CENTER); JPanel pnlBtn = new JPanel(); pnlBtn.add(btn); c.add(pnlBtn, BorderLayout.SOUTH); // Listener для поля ввода fldi.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { fldo.setText(fldi.getText()); fldi.setText(«»); } }); // Listener для кнопки копирования. btn.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { fldo.setText(fldi.getText()); fldi.setText(«»); fldi.requestFocus(); } }); WindowListener wndCloser = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; addWindowListener(wndCloser); setVisible(true); } public static void main(String[] args) { new Dialog9Home(); } }

1. Для размещения визуальных компонент здесь использованы 3 панели — pnlin, pnlout и pnlBtn. Именно это обеспечивает нормальный внешний вид приложения.

2. Вся функциональность данного приложения сосредоточена в двух «слушателях» (listener) — одного для поля ввода, другого для кнопки. Копирование текста выполняется так

fldo.setText(fldi.getText());

После этого выполняется очистка поля ввода:

fldi.setText(«»);

Задание.

Изменить данное приложение следующим образом.

  • 1. При копировании поля ввода нужно кроме собственно копирования организовать занесение строки из поля ввода во внутренний список (список организовать, используя ArrayList или LinkedList).
  • 2. Добавить вниз экрана еще одну кнопку с надписью «Печатать».

    По нажатию на эту кнопку весь сохраненный список должен быть выведен на печать (в поток System.out).

[an error occurred while processing this directive]
[an error occurred while processing this directive](none)


На главнуюПоиск

Java

Java EE 6

Что такое Java EE?

Java Platform, Enterprise Edition (Java EE) – это платформа для создания приложений класса предприятия с использованием языка Java. Java EE добавляет свои библиотеки и системные сервисы к платформе Java SE для поддержки масштабирования, безопасности, интеграции и других направлений, необходимых для приложений уровня предприятия.

Новое в Java EE 6

Java EE 6 поддерживает концепцию профилей (profiles), т.е. конфигурирование платформы Java EE для проектирования приложений определенного класса. Такие профили включают набор технологий (а не все технологии Java EE), необходимых для создания приложений данного класса.

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

Облегчена разработка приложений.

Наверх

Приложение Java EE 6

Платформа Java EE 6 позволяет разрабатывать многозвенные приложения. Различные части таких приложений расположены на разных машинах.

В общем случае многозвенное приложение состоит из:

  • звена клиента, расположенного на машине клиента;
  • веб-звена, размещенного в Java EE сервере;
  • бизнес-узла, размещенного в Java EE сервере;
  • базы данных, расположенной, возможно, на отдельной машине:

Наверх

Компоненты приложения Java EE

Приложение Java EE содержит компоненты. Компонентами приложения Java EE являются:

  • приложения и апплеты на клиенте;
  • сервлеты, JSP, JSF – это веб-компоненты;
  • EJB компоненты – это бизнес-компоненты.

Наверх

Клиенты Java EE

Клиенты Java EE – это веб-клиенты и Java-приложения.

Веб-клиенты часто называют «тонкими» клиентами. Это динамически формируемые веб-страницы, которые клиент видит через браузер. Формируются они в веб-слое сервера Java EE. Тонкий клиент обычно не обращается к базе данных и не обслуживает бизнес-логику. Тонкий клиеннт — это интерфейс, более сложные действия выполняются другими частями многозвенного приложения.

Клиентом может быть и Java-приложение. Если возможностей браузера не хватант для работы клиента, то можно использовать обычное Java-приложение. Оно может прямо обращаться к слою предприятия (к EJB) или напрямую обращаться к базе данных.

Наверх

JavaBeans

Компоненты, построенные по технологии JavaBeans применяются и на клиенте, и на Java EE сервере. Они обрабатывают потоки данных от клиента к серверному слою и базам данных и потоки в обратном направлении.

Наверх

Веб-компоненты

Веб-компоненты – это сервлеты, страницы JSP и JSF. Сервлеты – это Java-классы, которые обрабатывают запрос из браузера клиента и формируют ответ в виде, как правило, HTML страницы. Страницы JSP также принимают запрос и формиуют ответ как и сервлеты.

Наверх

Бизнес-компоненты

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

Наверх

Информационный слой EIS

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

Наверх

Контейнеры Java EE

Контейнер обслуживает специфическую функциональность, которая позволяет писать веб-компоненты, бины, приложения без необходимости учёта этой функциональности, т.е. можно сосредоточиться только на бизнес-задачах. Клиентский компонент (веб-компонент, бин, приложение) должен быть оформлен как Java EE модуль и помещён в контейнер. Контейнер есть интерфейс между клиентскими компонентами и функциональностью на нижнем уровне.

Наверх

Контейнеры

Java EE сервер содержит в себе и обеспечивает работу веб-контейнера и EJB-контейнера.

Веб-контейнер управляет работой веб-страниц, сервлетов и некоторых EJB-компонентов.

EJB-контейнер управляет работой EJB-компонентов.

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

Апплеты работают в контейнере апплетов на клиентской машине.

На схеме показаны контейнеры Java EE:

Наверх

Сборка и установка приложений Java EE

Приложения Java EE упаковываются стандартным образом и устанавливаются в соответствующие контейнеры, работающие под управлением сервера Java EE. Приложение Java EE состоит из функциональных элементов (сервлетов, бинов и т.д.) и, дополнительно, содержит описатель установки (deployment descriptor). Последний описывает содержание приложения.

После установки в контейнеры сервера Java EE приложение готово к работе.

Наверх

Как упаковываются приложения?

Java EE приложения поставляются либо в виде jar-файлов, либо как веб-архивы (WAR), либо как Enterprise Archive (EAR). Все эти упаковки являются jar-файлами, но веб-архивы имеют расширение .war, а EAR-архивы имеют расширение .ear. То, что приложение состалено из модулей jar, war, ear, позволяет использовать какие-то из этих модулей повторно в других приложениях.

На картинке представлена структура приложения EAR:

Описатель установки (deployment descriptor) – это XML документ с расширением .xml, он описывает параметры установки приложения и его составных частей. Описатель является декларацией и может быть изменён без изменения исходных кодов приложения. Сервер Java EE использует информацию из описателя и действует соответственно.

Описатели установки представлены двумя типами: описатель типа Java EE (applicationName.xml) и описатель времени исполнения (sun-applicationName.xml).

Наверх

Модуль Java EE

Модуль в Java EE состоит из компонентов одного контейнерного типа и, возможно, описателя установки компонента этого типа. Модуль Java EE может быть установлен автономно, а не только в составе приложения.

Имеется 4-е типа Java EE модулей:

  1. EJB-модули, содержащие производственные бины и дескрипторы установки. Расширение для них – .jar.
  2. Веб-модули, содежащие сервлеты, Java-классы, картинки, HTML-файлы и др., описатель установки. Расширение для веб-модулей — .war.
  3. Модуль приложения клиента. Он состоит из Java-классов и описателя установки. Этот модуль упаковывается в jar-архив с расширением .jar.
  4. Модуль ресурсов взаимодействия, в нём Java-интерфейсы, классы, библиотеки, документация, описатель установки. Всё это нужно для связи с производственным информационным слоем. Упаковывается в jar-файл с расширением .rar.

Наверх

Sun GlassFish Enterprise Server v3

Sun GlassFish Enterprise Server v3 – это реализация платформы Java EE. Дополнительно данный сервер добавляет к платформе Java EE ряд инструментов для разработчиков.

Наверх

Веб-приложения

Веб-компоненты – это динамические расширения веб-серверов. К веб-компонентам относятся сервлеты, веб-страницы, веб-сервисы, страницы JSP.

Как идёт взаимодействие клиетна и веб-приложения? Клиент направляет запрос веб-серверу. Веб-сервер преобразует запрос в объект HTTPServletRequest и направляет его веб-компоненту, который формирует ответ в виде HTTPServletResponse. Веб-сервер получает этот объект, преобразует его в HTTP-ответ и направляет клиенту:

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

Веб-приложение состоит из веб-компонентов, ресурсов, вспомогательных классов и библиотек.

Наверх

Веб-модули

Веб-компоненты и статические веб-файлы называются веб-ресурсы.

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

Рассмотрим структуру веб-модуля. Директория верхнего уровня называется корнем документа. В корне хранятся веб-страницы, классы, архивы, статические ресурсы.В корне содержится поддиректория WEB-INF, в которой имеется описатель установки web.xml и др. Если веб-модуль состоит только из HTTP-страниц и статических файлов, то нет необходимости в web.xml файле.

Можно создавать поддиректории и в корневой папке, и в папке WEB-INF.

Веб-модуль может быть установлен в контейнер, не будучи упаковыванным, или будучи упакованным в jar-файл, который в данном случае называется веб-архив (WAR) и который имеет расширение .war.

Если размешать WAR-архив на сервере предприятия, то этот архив должен содержать и описатель установки времени выполнения. Такой xml-файл называется sun-web.xml и располагается в директории WEB-INF.

Разберемся с вопросом задания порядка на некотором множестве объектов.

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

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

Для задания порядка используются следующие интерфейсы: Comparable и Comparator. Они дают два различных способа задания порядка. Мы рассмотрим оба этих интерфейса, определим их назначение, разберем, как ими пользоваться и чем они отличаются друг от друга.

Интерфейс Comparable предназначен для определения так называемого естественного порядка ( natural ordering ). Этот интерфейс определен в пакете java.lang. Обратившись к документации мы увидим, что данный интерфейс содержит всего один метод.

public int compareTo(Object o)

Сравнивает объект с другим объектом.

Разберем пример. Пусть мы хотим задать порядок на множестве объектов класса A

public class A implements Comparable { String name; . . . }

Причем, мы хотим сравнивать объекты по полю name в алфавитном порядке. Тогда мы задаем implements Comparable и определяем в классе метод

public int compareTo(Object obj) { return name.compareTo(((A)obj).name); }

В результате класс A должен выглядеть так

public class A implements Comparable { String name; public int compareTo(Object obj) { return name.compareTo(((A)obj).name); } . . . }

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

Метод compareTo(…) будет вызываться тогда, когда требуется сравнить два объекта. Он будет вызываться для некоторого объекта и в качестве параметра ему будет передан другой объект, который требуется сравнить с данным.

В нашем примере реализация метода compareTo(…) опирается на метод compareTo(…) класса String (поле name — класса String). Если мы обратимся к документации по методу compareTo(…) класса String, то увидим, что он сравнивает посимвольно две строки, пока не обнаружит два первых неодинаковых символа. В качестве результата он возвращает 0, если строки совпадают и разность кодов двух первых отличных друг от друга символов, если нет.

Замечание

Следует также обратить внимание на то, что параметр obj сначала приводится к классу A (downcasting), из него выбирается поле name и передается в качестве параметра методу compareTo(…) класса String. Если в качестве параметра будет передан объект не класса A, а какого-то другого класса, то возникнет ClassCastException. Если нам требуется сравнивать объекты нескольких различных классов (такая необходимость возникает довольно редко), то это нужно учесть в методе compareTo(…) .

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

public TreeSet(Comparator c)

который мы рассмотрим позже. Если добавляемый объект не удовлетворяет интерфейсу, то возникнет exception.

Класс TreeSet организует сравнение элементов, используя compareTo(…) и расположит их в соответствии с заданным этим методом порядком.

Как было указано, Java позволяет сравнивать объекты различных классов. Рассмотрим для примера, как нужно переопределить метод compareTo(), чтобы он учитывал возможность сравнения как объектов класса A как друг с другом, так и с объектами некоторого класса B. Для этого метод compareTo(…) должен выглядеть примерно так

public int compareTo(Object obj) { if ( obj instanceof A ) return name.compareTo(((A)obj).name); return … здесь сравнение с объектом B }

В свою очередь, в классе B, нужно указать, что он удовлетворяет интерфейсу Comparable и реализовать в нем метод compareTo(), который учитывает возможность сравнения B с A.

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

В описанном случае вместо интерфейса Comparable следует использовать интерфейс Comparator.

Интерфейс Comparator .

Определен в пакете java.util. Работа с использованием Comparator отличается от того, что мы рассмотрели по интерфейсу Comparable. Здесь строится отдельный вспомогательный класс (будем называть его компаратор), используемый для сравнения объектов.

Обратимся к документации. Интерфейс Comparator имеет два метода

public int compare(Object o1, Object o2) и public boolean equals(Object obj)

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

Собственно сравнение объектов выполняет первый метод. Рассмотрим интерфейс Comparator на примере.

Пример

public class Employer { // служащий int tabnom; // табельный номер String name; // ФИО static NameComparator nameComparator = new NameComparator(); static TabComparator tabComparator = new TabComparator(); public static Comparator getNameComparator() { return nameComparator; } public static Comparator getTabComparator() { return tabComparator; } static class NameComparator implements Comparator { public int compare(Object o1, Object o2) { return ((Employer)o1).name.compareTo(((Employer)o2).name); } // метод equals() реализовывать не обязательно } static class TabComparator implements Comparator { public int compare(Object o1, Object o2) { return ((Employer)o1).tabnom — ((Employer)o2).tabnom; } } . . . }

В классе Employer заданы два статических вложенных класса NameComparator и TabComparator, а также созданы объекты этих классов. Для получения конкретного компаратора созданы методы getNameComparator() и getTabComparator().

Имея такой класс, мы можем сравнивать объекты класса при помощи одного или другого компаратора. Например, рассмотрим, как построить TreeSet, упорядоченый по ФИО служащего.

TreeSet reestr = new TreeSet(Employer.getNameComparator());

Далее мы можем добавлять в reestr объекты класса Employer.

Они будут автоматически упорядочиваться по ФИО.

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

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

Рисунок 1.

Здесь видны два поля ввода — для фамилии и списка телефонов, кнопка «Печатать» и поле для индикации количества записей в нашей книжке. Конечно, в таком приложении должны быть еще средства сохранения/восстановления информации, поиска и проч. Но это пока за рамками наших возможностей. Для этого нужно просто больше знать о Java.

Наше приложение должно работать так. При вводе информации во второе поле (Телефоны) информация (фамилия и телефон) будет запоминаться в некотором внутреннем хранилище программы, (мы будем для этого использовать TreeSet). При этом поле-индикатор количества записей изменяется, а поля ввода очищаются. При нажатии на кнопку «Печатать» весь накопленный нами список записей выводится на печать (в поток System.out) в некотором удобочитаемом формате.

Обратим внимание на внешний вид приложения.

  • На экране явно видны две панели, причем с границами (border). Мы должны рассмотреть, как формировать границы панелей.
  • Верхняя панель поделена на три горизонтальные части, в каждой из которых расположены различные визуальные компоненты (метки, поля и одна кнопка). Для формирования такого внешнего вида нужно познакомиться с менеджерами компоновки (Layout Manager). В частности, для нашего приложения понадобится менеджер GridLayout.

Разберем эти вопросы, прежде чем приступим к реализации.

Границы панелей.

В библиотеке Swing есть много средств для создания различного рода границ. Мы рассмотрим сейчас только то, что нужно непосредственно для нашего приложения. Граница, представленная на рисунке, носит название Etched Border. Ее можно создать так.

JPanel pnl = new JPanel(); pnl.setBorder(BorderFactory.createEtchedBorder());

Здесь pnl — панель, для которой мы задаем границу типа Etched Border. Для установки границ используется метод setBorder(…) класса JPanel. Сами границы представляются объектами класса Border и могут быть порождены методами класса BorderFactory (пакет javax.swing). Посмотрим подробнее это в документации.

Менеджеры компоновки.

На занятии 7 мы уже познакомились с одним менеджером компоновки — BorderLayout. Как уже отмечалось, этот менеджер является менеджером по умолчанию для рабочей части (Content Pane) окна JFrame. Домашнее задание прошлого занятия связано с неявным использованием другого менеджера компоновки — FlowLayout. Для правильного решения домашнего задания нужно было создать панели (JPanel) для полей ввода и вывода. А панель JPanel по умолчанию использует FlowLayout как менеджер компоновки.

Это пригодится нам и в данном задании, поэтому рассмотрим FlowLayout несколько подробнее.

Данный менеджер размещает внутренние подкомпоненты слева направо в строке, выделяя им, по возможности, их предпочтительный размер (preferred size). Если очередная компонента не помещается в текущей строке, то она переносится на следующую строку, и так далее.

Как видим, это довольно простой менеджер компоновки. Он не обеспечивает нужной гибкости в разбиении окна приложения. Но он обладает одним очень важным свойством (как мы убедимся позже) — он «не растягивает» компоненты, как некоторые другие менеджеры. Поэтому один из возможных принципов построения нужной структуры окна приложения состоит в том, что мы разбиваем окно при помощи других менеджеров компоновки (не FlowLayout) на элементарные части или боки. В эти блоки мы вставляем не непосредственно элементарные визуальные компоненты (метки, поля, кнопки и т.п.) а панели JPanel, а уже в панели — элементарные компоненты. Т.е. FlowLayout следует применять на самом нижнем уровне вложенности вспомогательных панелей.

В нашем приложении нам понадобится другой менеджер компоновки — GridLayout . Этот менеджер позволяет разбить область на ячейки одинакового размера в виде сетки. Он не является менеджером по умолчанию и может быть установлен только явно. Поэтому сначала разберем, как установить менеджер компоновки. Обратимся к документации по JPanel и обратим внимание на конструкторы и метод setLayout(…) , который унаследован классом JPanel от класса Container (пакет java.awt).

public JPanel(LayoutManager layout)

Создает панель с указанным менеджером компоновки

public void setLayout(LayoutManager mgr)

Устанавливает менеджер компоновки для данного контейнера

Это два основных способа установки менеджера компоновки для панели. Т.е. мы можем установить менеджер компоновки либо сразу при создании панели (в конструкторе), либо позже, используя setLayout(…).

Теперь рассмотрим GridLayout. Два его основных конструктора это

public GridLayout(int rows, int cols)

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

public GridLayout(int rows, int cols, int hgap, int vgap)

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

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

В качестве отправной точки рассмотрим следующий вариант класса PhoneNotes — основного класса нашего приложения

// PhoneNotes.java: Записная книжка import java.awt.*; import java.awt.event.*; import javax.swing.*; public class PhoneNotes extends JFrame { JTextField fldFio = new JTextField(25); JTextField fldPhone = new JTextField(25); JTextField fldCnt = new JTextField(4); public PhoneNotes() { super(«Записная книжка»); try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch(Exception e) { } setSize(400, 250); Container c = getContentPane(); // Центральная панель JPanel centerPanel = new JPanel(new GridLayout(3, 2, 5, 5)); centerPanel.setBorder(BorderFactory.createEtchedBorder()); JLabel aLabel = new JLabel(«Фамилия «); centerPanel.add(aLabel); centerPanel.add(fldFio); aLabel = new JLabel(«Телефон «); centerPanel.add(aLabel); centerPanel.add(fldPhone); JButton btn = new JButton(«Печатать»); centerPanel.add(btn); c.add(centerPanel, BorderLayout.CENTER); // Нижняя панель JPanel statusPanel = new JPanel(); statusPanel.setBorder(BorderFactory.createEtchedBorder()); aLabel = new JLabel(«Количество записей «); statusPanel.add(aLabel); fldCnt.setEnabled(false); statusPanel.add(fldCnt); c.add(statusPanel, BorderLayout.SOUTH); // Listener’ы полей и кнопок fldPhone.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { // ???

} }); btn.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { // ??? } }); WindowListener wndCloser = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; addWindowListener(wndCloser); setVisible(true); } public static void main(String[] args) { new PhoneNotes(); } }

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

Задание

  • 1. Улучшим внешний вид приложения. Изменим сетку с 3*2 на 3*1 и в каждую ячейку сетки добавим панель, а уже в эти панели будем добавлять метки, поля, кнопки.
  • 2. Рассмотрим класс (бизнес класс) для хранения одной записи записной книжки.

    Это класс Person. // Person.java import java.io.*; public class Person implements Serializable { private String name; private String phone; Person(String name, String phone) { this.name = name; this.phone = phone; } Person() { this(null, null); } String getName() { return name; } String getPhone() { return phone; } } Этот класс предназначен для хранения информации по одной персоне.

  • 3. В слушателе (listener) поля fldPhone реализуем создание объекта этого класса на основе информации из полей fldFio и fldPhone. Кроме того, реализуем очистку этих полей после создания данного объекта.
  • 4. Добавим в класс PhoneNotes поле для коллекции записей (поле класса TreeSet) и реализуем занесение информации (объектов Person) в эту коллекцию.
  • 5. В классе Person реализуем интерфейс Comparable, в частности, определим метод compareTo(…) для сравнения двух персон .
  • 6. В классе Person определим также метод toString() для формирования строки из объекта. Используем этот метод для организации печати списка по кнопке «Печатать».

[an error occurred while processing this directive]
[an error occurred while processing this directive](none)


Добавить комментарий

Закрыть меню