Сериализация объектов

Факультет «Информатика и системы управления»

Методические указания к лабораторной работе

по курсу «Распределенные системы обработки информации»

"Сериализация объектов"

Москва 2004 г.

Цель работы

    Познакомиться с сериализацией объектов и её практическим применением.

    Изучить метод клонирования объектов при помощи сериализации.

    Освоить классы ObjectInputStream и ObjectOutputStream пакета java.io.

    Научится обрабатывать действия мыши и клавиатуры.

    Изучить классы Menu, MenuBar, MenuItem, Dialog, FileDialog пакета java.awt.

    Научиться использовать таблицы.

    Применить полученные знания на практике

Задание для домашней подготовки

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

Задание к лабораторной работе

Создать оконное приложение, реализующее графический интерфейс доступа к массиву объектов классов Employee и Manager (см. приложение). Работа пользователя должна осуществляться при помощи меню, обязательно содержащего следующие элементы.

– Меню «Файл» (Внутри – пункты меню «Новый каталог», «Сохранить данные», «Загрузить данные», «Передать данные»).

– Меню «Добавить (Внутри – пункты меню «Новый работник», «Новый менеджер», «Клонировать сотрудника»).

Добавление информации о новых сотрудниках должно происходить при помощи диалоговых окон.

На главной форме должна присутствовать таблица с информацией о сотрудниках (Ф.И.О., оклад, дата поступления).

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

Меню «ФАЙЛ»

«Новый каталог» – вся информация в таблице стирается. На экране показываются только заголовки таблицы.

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

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

«Выход» – вызов системного метода System.exit(0).

Меню «ДОБАВИТЬ»

«Добавить работника» – показывается диалоговое окно, в котором пользователь может ввести данные для нового работника. Эти данные заносятся в объект базы данных, после чего обновляется информация в таблице.

«Добавить менеджера» – аналогично предыдущему пункту, но с учётом особенностей класса Manager. Класс диалогового окна добавления менеджера может быть наследован от аналогичного класса окна Employee.

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

Рисунок 1: Вариант исполнения задания

Содержание отчета

Отчет должен содержать:

    Постановку задачи, решаемой отлаженной программой.

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

    Листинг программы с необходимыми комментариями.

Контрольные вопросы

          Что такое сериализация / десериализация?

          Какое диалоговое окно называется модальным?

          Почему не все объекты можно сериализовать?

          Как узнать, можно ли сериализовать объект класса, встроенного в Java?

          Почему возможность сериализации сохраняется при наследовании?

          Сколько полей содержит интерфейс Serializable?

          Что подразумевается под термином «клонирование объектов».

          Назовите «родственные связи» между классами Menu, MenuItem, MenuBar, MenuShortCut и MenuComponent.

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

          Как заставить приложение записывать на экране в текстовой области символы ‘*’ вместо символов, вводимых с клавиатуры?

          Что такое «модель таблицы»?

          Где хранятся данные для таблицы?

          Какие данные могут содержать ячейки таблицы JTable?

          Как сделать возможным выбор нескольких элементов таблицы.

Литература

Официальные источники:

    Кен Арнольд, Джеймс Гослинг, Дэвид Холмс. Язык программирования Java™.

    Официальный сайт Java – http://java.sun.com/ (есть раздел на русском языке с учебником).

    Java™ 2 SDK, Standard Edition Documentation – http://java.sun.com/products/jdk/1.5/index.html.

    Джеймс Гослинг, Билл Джой, Гай Стил. Спецификация языка Java (The Java Language Specification – http://www.javasoft.com/docs/books/jls/). Перевод на русский язык – http://www.uni-vologda.ac.ru/java/jls/index.html

    Официальный сайт проекта Eclipse – http://www.eclipse.org/.

Другое:

    Дмитрий Рамодин. Начинаем программировать на языке Java.

    Николай Смирнов. Java 2: Учебное пособие.

    Картузов А.В. Программирование на языке Java.

    Вязовик Н.А. Программирование на Java.

    Алексей Литвинюк. Введение в интегрированную среду разработки Eclipse – http://lib.juga.ru/article/articleview/174/1/69/.

Приложение 1. Создание меню

В контейнер типа Frame заложена возможность установки стандартной строки меню (menu bar), располагаемой ниже строки заголовка, как показано на рисунке 1. Эта строка – объект класса MenuBar.

Все, что нужно сделать для установки строки меню в контейнере Frame – это создать объект класса MenuBar и обратиться к методу setMenuBar():

Frame f = new Frame («Пример меню»);

MenuBar mb = new MenuBar();

f.setMenuBar(mb);

Если имя mb не понадобится, можно совместить два последних обращения к методам:

f.setMenuBar (new MenuBar());

Разумеется, строка меню еще пуста и пункты меню не созданы.

Каждый элемент строки меню – выпадающее меню (drop-down menu) – это объект класса Menu. Создать эти объекты и занести их в строку меню ничуть не сложнее, чем создать строку меню:

Menu mFile = new Menu («Файл»);

mb.add(mFile);

Menu mEdit = new Menu («Правка»);

mb.add(mEdit);

Menu mView = new Menu («Вид»);

mb.add(mView);

Menu mHelp = new Menu («Справка»);

mb.setHelpMenu(mHelp);

и т.д. Элементы располагаются слева направо в порядке обращений к методам add(), как показано на рисунке 1. Во многих графических системах принято меню Справка (Help) прижимать к правому краю строки меню. Это достигается обращением к методу setHelpMenu(), но фактическое положение меню Справка определяется графической оболочкой.

Рисунок 2. Система меню

Затем определяем каждое выпадающее меню, создавая его пункты. Каждый пункт меню – это объект класса MenuItem. Схема его создания и добавления к меню точно такая же, как и самого меню:

MenuItem create = new MenuItem («Создать»);

mFile.add(create);

MenuItem open = new MenuItem («Открыть…»);

mFile.add(open);

и т.д. Пункты меню будут расположены сверху вниз в порядке обращения к методам add().

Часто пункты меню объединяются в группы. Одна группа от другой отделяется горизонтальной чертой. На рисунке 1 черта проведена между командами Открыть и Отправить. Эта черта создается методом addSeparator() класса Menu или определяется как пункт меню с надписью специального вида – дефисом:

mFile.add (new MenuItem(» –»));

Интересно, что класс Menu расширяет класс MenuItem, а не наоборот. Это означает, что меню само является пунктом меню, и позволяет задавать меню в качестве пункта другого меню, тем самым организуя вложенные подменю:

Menu send = new Menu («Отправить»);

mFile.add(send);

Здесь меню send добавляется в меню mFile как один из его пунктов. Подменю send заполняется пунктами меню как обычное меню.

Часто команды меню создаются для выбора из них каких-то возможностей, подобно компонентам checkbox. Такие пункты можно выделить щелчком кнопки мыши или отменить выделение повторным щелчком. Эти команды – объекты класса CheckboxMenuItem:

CheckboxMenuItem disk = new CheckboxMenuItem («Диск A:», true);

send.add(disk);

send.add (new CheckboxMenuItem («Архив»));

и т.д.

Все, что получилось в результате перечисленных действий, показано на рисунке 1.

Многие графические оболочки, но не MS Windows, позволяют создавать отсоединяемые (tear-off) меню, которые можно перемещать по экрану. Это указывается в конструкторе

Menu (String label, boolean tearOff)

Если tearoff == true и графическая оболочка умеет создавать отсоединяемое меню, то оно будет создано. В противном случае этот аргумент просто игнорируется.

Наконец, надо назначить действия командам меню. Команды меню типа MenuItem порождают события типа ActionEvent, поэтому нужно присоединить к ним объект класса-слушателя как к обычным компонентам, записав что-то вроде

create.addActionListener (new SomeActionEventHandler())

open.addActionListener (new AnotherActionEventHandler())

Пункты типа CheckboxMenuItem порождают события типа ItemEvent, поэтому надо обращаться к объекту-слушателю этого события:

disk.addItemListener (new SomeItemEventHandler())

Очень часто действия, записанные в командах меню, вызываются не только щелчком кнопки мыши, но и «горячими» клавишами-акселераторами (shortcut), действующими чаще всего при нажатой клавише <Ctrl>. На экране в пунктах меню, которым назначены «горячие» клавиши, появляются подсказки вида Ctrl+N, Ctrl+O, как на рисунке 1. «Горячая» клавиша определяется объектом класса MenuShortcut и указывается в его конструкторе константой класса KeyEvent, например:

MenuShortcut keyCreate = new MenuShortcut (KeyEvent.VK_N);

После этого «горячей» будет комбинация клавиш <Ctrl>+<N>. Затем полученный объект указывается в конструкторе класса MenuItem:

MenuItem create = new MenuItem («Создать», keyCreate);

Нажатие <Ctrl>+<N> будет вызывать окно создания. Эти действия, разумеется, можно совместить, например,

MenuItem open = new Menultern («Открыть…»,

new – MenuShortcut (KeyEvent.VK_O));

Можно добавить еще нажатие клавиши <Shift>. Действие пункта меню будет вызываться нажатием комбинации клавиш <Shift>+<Ctrl>+<X>, если воспользоваться вторым конструктором:

MenuShortcut (int key, boolean useShift)

С аргументом useShift == true.

Пример создания меню

import java.awt.*;

import java.awt.event.*;

public class MenuScribble extends Frame {

public MenuScribble (String s) {

super(s);

MenuBar mb = new MenuBar();

setMenuBar(mb);

Menu f = new Menu («Файл»);

Menu h = new Menu («Справка»);

mb.add(f);

mb.add(h);

MenuItem open = new MenuItem («Открыть…»,

new MenuShortcut (KeyEvent.VK_0));

MenuItem save = new MenuItem («Сохранить»,

new MenuShortcut (KeyEvent.VK_S));

MenuItem saveAs = new Menultera («Сохранить как…»);

MenuItem exit = new MenuItem («Выход»,

new MenuShortcut (KeyEvent.VK_Q));

f.add(open);

f.add(save);

f.add(saveAs);

f.addSeparator();

f.add(exit);

open.addActionListener (new ActionListener() {

public void actionPerformed (ActionEvent e) {

FileDialog fd = new FileDialog (new Frame(),

«Загрузить», FileDialog.LOAD);

fd.setVisible(true);

}

});

saveAs.addActionListener (new ActionListener() {

public void actionPerformed (ActionEvent e) {

FileDialog fd = new FileDialog (new Frame(),

«Сохранить», FileDialog.SAVE);

fd.setVisible(true);

}

});

exit.addActionListener (new ActionListener() {

public void actionPerformed (ActionEvent e) {System.exit(0);}

});

MenuItem about = new MenuItem («О программе»);

h.add(about);

about.addActionListener (

 // …обработчик

);

addWindowListener (new WinClose());

setVisible(true);

} // конец конструктора

 // вложенный класс – обработчик события закрытия окна

class WinClose extends WindowAdapter {

public void windowClosing (WindowEvent e) {System.exit(0);}

}

public static void main (String[] args) {

new MenuScribble («Программа с меню»);

}

}

Приложение 2. Контейнер Dialog

Контейнер Dialog – это окно обычно фиксированного размера, предназначенное для ответа на сообщения приложения. Оно автоматически регистрируется в оконном менеджере графической оболочки, следовательно, его можно перемещать по экрану, менять его размеры. Но окно типа Dialog, как и его суперкласс – окно типа Window, – обязательно имеет владельца owner, который указывается в конструкторе. Окно типа Dialog может быть модальным (modal), в котором надо обязательно выполнить все предписанные действия, иначе из окна нельзя будет выйти.

В классе семь конструкторов. Из них:

    Dialog (Dialog owner) – создает немодальное диалоговое окно с пустой строкой заголовка;

    Dialog (Dialog owner, String title) – создает немодальное диалоговое окно со строкой заголовка title;

    Dialog (Dialog owner, String title, boolean modal) – создает диалоговое окно, которое будет модальным, если modal == true.

Четыре других конструктора аналогичны, но создают диалоговые окна, принадлежащие окну типа Frame:

Dialog (Frame owner)

Dialog (Frame owner, String title)

Dialog (Frame owner, boolean modal)

Dialog (Frame owner, String title, boolean modal)

Среди методов класса интересны методы: isModal(), проверяющий состояние модальности, и setModal (boolean modal), меняющий это состояние.

События

Кроме Событий класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent, при изменении размеров окна, его перемещении или удалении с экрана, а также показа на экране происходит событие WindowEvent.

В примере 1 создается модальное окно доступа, в которое вводится имя и пароль. Пока не будет сделан правильный ввод, другие действия невозможны. На рисунке 2 показан вид этого окна.

Пример 1. Модальное окно доступа

import java.awt.*;

import java.awt.event.*;

class LoginWin extends Dialog {

LoginWin(Frame f, String s) {

super (f, s, true);

setLayout(null);

setFont (new Font («Serif», Font.PLAIN, 14));

Label l1 = new Label («Ваше имя:», Label.RIGHT);

l1.setBounds (20, 30, 70, 25); add(l1);

Label l2 = new Label («Пароль:», Label.RIGHT);

l2.setBounds (20, 60, 70, 25); add(l2);

TextField tf1 = new TextField(30);

tf1.setBounds (100, 30, 160, 25); add(tf1);

TextField tf2 = new TextField(30);

tf2.setBounds (100, 60, 160, 25); add(tf2);

tf2.setEchoChar ('*');

Button b1 = new Button («Применить»);

b1.setBounds (50, 100, 100, 30); add(b1);

Button b2 = new Button («Отменить»);

b2.setBounds (160, 100, 100, 30); add(b2);

setBounds (50, 50, 300, 150);

}

}

class DialogTest extends Frame {

DialogTest(String s) {

super(s);

setLayout(null);

setSize (200, 100);

setVisible(true);

Dialog d = new LoginWin (this, «Окно входа»);

d.setvisible(true);

}

public static void main (String[] args) {

Frame f = new DialogTest (» Окно-владелец»);

f.addWindowListener (new WindowAdapter() {

public void windowClosing (WindowEvent ev) {

System.exit(0);

}

});

}

}

Рисунок 2. Модальное окно доступа

Приложение 3. Контейнер FileDialog.

Контейнер FileDialog – это модальное окно с владельцем типа Frame, содержащее стандартное окно выбора файла операционной системы для открытия (константа LOAD) или сохранения (константа SAVE). Окна операционной системы создаются и помещаются в объект класса FileDialog автоматически.

В классе три конструктора:

    FileDialog (Frame owner) – создает окно с пустым заголовком для открытия файла;

    FileDialog (Frame owner, String title) – создает окно открытия файла с заголовком title;

    FileDialog (Frame owner, String title, int mode) – создает окно открытия или сохранения документа; аргумент mode имеет два значения: FileDialog.LOAD и FileDialog.SAVE.

Методы класса getDirectory() и getFile() возвращают только выбранный каталог и имя файла в виде строки String. Загрузку или сохранение файла затем нужно производить методами классов ввода / вывода.

Можно установить начальный каталог для поиска файла и имя файла методами setDirectory (String dir) и setFile (String fileName).

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

Метод setFilenameFilter (FilenameFilter filter) устанавливает шаблон filter для имени выбираемого файла. В окне будут видны только имена файлов, подходящие под шаблон. Этот метод не реализован в SUN JDK на платформе MS Windows.

События

Кроме событий класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent, при изменении размеров окна, его перемещении или удалении с экрана, а также показа на экране происходит событие WindowEvent.

Приложение 4. Создание таблиц

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

Компонент JTable не хранит информацию о содержимом в себе, а использует т.н. модель таблицы. Для создания модели предусмотрен специальный класс AbstractTableModel, в котором необходимо переопределить следующие методы:

public int getRowCount(); – возвращает количество строк в таблице.

public int getColumnCount(); – возвращает количество столбцов.

public object getValueAt (int row, int column); – возвращает содержимое ячейки в ряду row и столбце column.

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

return new Integer(100);

Для задания названий столбцам таблицы необходимо переопределить метод:

public int getColumnName (int c);

Параметр с указывает на номер столбца. Внутри должна быть конструкция switch-case (или if-else), возвращающая на каждый номер с названием колонки.

public String getColumnName (int c) {

if (c == 0)

return «Ф.И.О.»;

else if (c == 1)

return «Должность»;

else if (c==2)

return «Зарплата»;

else if (c == 3)

return «Год»;

else if (c == 4)

return «Месяц»;

else if (c == 5)

return «День»;

else

return null;

}

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

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

JTable empTable = new JTable(myTableModel);

Модель таблицы может быть указана и позже с помощью метода setModel.

И, наконец, для создания полос прокрутки на элементе воспользуемся стандартным методом – поместим таблицу на панель типа JScrollPane:

JScrollPane pane= new JScrollPane(empTable);

Осталось разместить готовую панель на форме. Воспользуемся следующим кодом:

this.getContentPane().add(pane);

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

1. Ограничить пользователя выбором одной строки. Для этого необходимо задать режим выбора строк таблицы.

table.getSelectionModel().setSelectionMode(mode)

Параметр mode принимает три значения:

0 – выбирается одна строка.

1 – несколько смежных строк.

2 – несколько произвольных строк.

2. Для получения номера выбранной строки воспользоваться методом getSelectedRow().

Примечание: Для режима выбора нескольких строк существует метод getSelectedRows(), возвращающий массив int[] номеров выделенных строк.

Приложение 5. Сериализация объектов

Методы классов ObjectInputStream и ObjectOutputStream позволяют прочитать из входного байтового потока или записать в выходной байтовый поток данные сложных типов – объекты, массивы, строки – подобно тому, как методы классов DataInputStream и DataOutputStream читают и записывают данные простых типов.

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

Процесс записи объекта в выходной поток получил название сериализации (serialization), а чтения объекта из входного потока и восстановления его в оперативной памяти – десериализации (deserialization).

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

Поэтому сериализации можно подвергнуть не каждый объект, а только тот, который реализует интерфейс Serializable. Этот интерфейс не содержит ни полей, ни методов. Реализовать в нем нечего. По сути дела запись

class A implements Serializable{…}

это только пометка, разрешающая сериализацию класса А.

В Java процесс сериализации максимально автоматизирован. Достаточно создать объект класса ObjectOutputStream, связав его с выходным потоком, и выводить в этот поток объекты методом writeObject():

MyClass me = new MyClass («abc», -12, 5.67e 5);

int[] arr = {10, 20, 30};

ObjectOutputStream oos = new ObjectOutputStream (

new FileOutputStream («myobjects.ser»));

oos.writeObject(me);

oos.writeObject(arr);

oos.writeObject («Some string»);

oos.writeObject (new Date());

oos.flush();

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

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

Все классы объектов, входящих в такое сериализуемое множество, а также все их внутренние классы, должны реализовать интерфейс Serializable, в противном случае будет выброшено исключение класса NotSerializableException и процесс сериализации прервется. Многие классы J2SDK реализуют этот интерфейс. Учтите также, что все потомки таких классов наследуют реализацию (может быть сериализацию?). Например, класс java.awt. Component реализует интерфейс Serializable, значит, все графические компоненты можно сериализовать. Не реализуют этот интерфейс обычно классы, тесно связанные с выполнением программ, например, java.awt. Toolkit. Состояние экземпляров таких классов нет смысла сохранять или передавать по сети. Не реализуют интерфейс Serializable и классы, содержащие внутренние сведения Java «для служебного пользования».

Десериализация происходит так же просто, как и сериализация:

ObjectlnputStream ois = new ObjectInputStream (

new FilelnputStream («myobjects.ser»));

MyClass mcl = (MyClass) ois.readObject();

int[] a = (int[]) ois.readObject();

String s = (String) ois.readObject();

Date d = (Date) ois.readObject();

Приложение 6. Клонирование при помощи сериализации

Под клонированием объекта подразумевают получение его точной копии, независимой от оригинала. Таким образом, изменение параметров объекта-клона не влияет на оригинал, верно и обратное: изменение оригинала не влияет на клон.

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

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

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

– клонирование сериализацией работает несколько медленнее обчного метода клонирования.

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

Для реализации метода рекомендуется использовать следующую схему.

– Создаётся класс SerialCloneable, имплементирующий интерфейсы Cloneable и Serializable.

– Класс SerialCloneable имеет единственный метод clone(), в котором происходит сериализация объекта. Метод clone() возвращает объект из входного потока.

– Класс, который должен содержать поддержку клонирования, наследуется от класса SerialCloneable.

Приложение 7. Пример кода классов Employee и Manager

public class Manager extends Employee {

private Employee secretary;

public Manager (String n, double s, int year, int month, int day) {

super (n, s, year, month, day);

secretary = null;

}

public void setSecretary (Employee s) {

secretary = s;

}

}

public class Employee implements Serializable {

private String name;

private double salary;

private Date hireDay;

public Employee(){};

public Employee (String n, double s, int year, int month, int day) {

name = n;

salary = s;

GregorianCalendar calendar = new GregorianCalendar (year, month 1, day);

hireDay = calendar.getTime();

}

public String getName() {

return name;

}

public double getSalary() {

return salary;

}

public Date getHireDay() {

return hireDay;

}

public void raiseSalary (double byPercent) {

double raise=salary*byPercent/100;

salary+=raise;

}

}