public void setColor(int newColor) { color = newColor; }
public int getColor() { return color; }
}
public class StoreCADState {
public static void main(String[] args) throws Exception {
List<Class<? extends Shape>> shapeTypes =
new ArrayList<Class<? extends Shape>>();
// Add references to the class objects:
shapeTypes.add(Circle.class);
shapeTypes.add(Square.class);
shapeTypes.add(Line.class);
List<Shape> shapes = new ArrayList<Shape>();
// Make some shapes:
for(int i = 0; i < 10; i++)
shapes.add(Shape.randomFactory());
// Set all the static colors to GREEN:
for(int i = 0; i < 10; i++)
((Shape)shapes.get(i)).setColor(Shape.GREEN);
// Save the state vector:
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("CADState.out"));
out.writeObject(shapeTypes);
Line.serializeStaticState(out);
out.writeObject(shapes);
// Display the shapes:
System.out.println(shapes);
}
}
<spoiler text="Output:">
[class Circlecolor[3] xPos[58] yPos[55] dim[93]
, class Squarecolor[3] xPos[61] yPos[61] dim[29]
, class Linecolor[3] xPos[68] yPos[0] dim[22]
, class Circlecolor[3] xPos[7] yPos[88] dim[28]
, class Squarecolor[3] xPos[51] yPos[89] dim[9]
, class Linecolor[3] xPos[78] yPos[98] dim[61]
, class Circlecolor[3] xPos[20] yPos[58] dim[16]
, class Squarecolor[3] xPos[40] yPos[11] dim[22]
, class Linecolor[3] xPos[4] yPos[83] dim[6]
, class Circlecolor[3] xPos[75] yPos[10] dim[42]
]
</spoiler> Класс Shape реализует интерфейс Serializable, поэтому все унаследованные от него классы по определению поддерживают сериализацию и восстановление. В каждой фигуре Shape содержатся некоторые данные, и в каждом унаследованном от Shape классе имеется статическое (static) поле, которое определяет цвет фигуры. (Если бы мы поместили статическое поле в базовый класс, то получили бы одно поле для всех фигур, поскольку статические поля в производных классах не копируются.) Для задания цвета некоторого типа фигур можно переопределить методы базового класса (статические методы не используют динамическое связывание). Метод randomFactory() создает при каждом вызове новую фигуру, используя для этого случайные значения Shape. Классы Circle и Square — простые подклассы Shape, различающиеся только способом инициализации поля color: окружность (Circle) задает значение этого поля в месте определения, а прямоугольник (Square) инициализирует его в конструкторе. Класс Line мы обсудим чуть позже. В методе main() один списокArrayList используется для хранения объектов Class, а другой — для хранения фигур. Восстановление объектов выполняется вполне тривиально:
//: io/RecoverCADState.java
// Восстановление состояния вымышленной системы CAD.
// {RunFirst: StoreCADState}
import java.io.*;
import java.util.*;
public class RecoverCADState {
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
</spoiler> Мы видим, что значения переменных xPos, уPos и dim сохранились и были успешно восстановлены, однако при восстановлении статической информации произошло что-то странное. При записи все статические поля color имели значение 3, но восстановление дало другие результаты. В окружностях значением стала единица (то есть константа RED), а в прямоугольниках поля color вообще равны нулю (помните, в этих объектах инициализация проходит в конструкторе). Похоже, статические поля вообще не сериализовались! Да, это именно так — хотя класс Class и реализует интерфейс Serializable, происходит это не так, как нам хотелось бы. Отсюда, если вам понадобится сохранить статические значения, делайте это самостоятельно.
Именно для этой цели предназначены методы serializeStaticState() и deserializeStaticState() класса Line. Вы можете видеть, как они вызываются в процессе сохранения и восстановления системы. (Заметьте, порядок действий при сохранении информации должен соблюдаться и при ее десериализации.) Поэтому для правильного выполнения этих программ необходимо сделать следующее:
· Добавьте методы sеrializeStaticState() и deserializeStaticState() во все фигуры Shape.
· Уберите из программы список shapeTypes и весь связанный с ним код.
· При сериализации и восстановлении вызывайте новые методы для сохранения статической информации.
Также стоит позаботиться о безопасности, ведь сериализация сохраняет и закрытые (private) поля. Если в вашем объекте имеется конфиденциальная информация, ее необходимо пометить как transient. Но в таком случае придется подумать о безопасном способе хранения такой информации, ведь при восстановлении объекта необходимо восстанавливать все его данные.
В пакете JDK-1.4 появился программный интерфейс АРІ для работы с предпочтениями (preferences). Предпочтения гораздо более тесно связаны с долговременным хранением, чем механизм сериализации объектов, поскольку они позволяют автоматически сохранять и восстанавливать вашу информацию. Однако они применимы лишь к небольшим, ограниченным наборам данных — хранить в них можно только примитивы и строки, и длина строки не должна превышать 8 Кбайт (не так уж мало, но вряд ли подойдет для решения серьезных задач). Как и предполагает название нового API, предпочтения предназначены для хранения и получения информации о предпочтениях пользователя и конфигурации программы. Предпочтения представляют собой наборы пар «ключ-значение» (как в картах), образующих иерархию узлов. Хотя иерархия узлов и годится для построения сложных структур, чаще всего создают один узел, имя которого совпадает с именем класса, и хранят информацию в нем. Простой пример:
//: io/PreferencesDemo.java
import java.util.prefs.*;
import static net.mindview.util.Print.*;
public class PreferencesDemo {
public static void main(String[] args) throws Exception {
Preferences prefs = Preferences
.userNodeForPackage(PreferencesDemo.class);
prefs.put("Location", "Oz");
prefs.put("Footwear", "Ruby Slippers");
prefs.putInt("Companions", 4);
prefs.putBoolean("Are there witches?", true);
int usageCount = prefs.getInt("UsageCount", 0);
usageCount++;
prefs.putInt("UsageCount", usageCount);
for(String key : prefs.keys())
print(key + ": "+ prefs.get(key, null));
// You must always provide a default value:
print("How many companions does Dorothy have? " +
prefs.getInt("Companions", 0));
}
}
<spoiler text="Output:"> (Sample)
Location: Oz
Footwear: Ruby Slippers
Companions: 4
Are there witches?: true
UsageCount: 53
How many companions does Dorothy have? 4
</spoiler> Здесь используется метод userNodeForPackage(), но с тем же успехом можно было бы заменить его методом systemNodeForPackage(), это дело вкуса. Предполагается, что префикс user используется для хранения индивидуальных предпочтений пользователя, asystem — для хранения информации общего плана о настройках установки. Так как метод main() статический, для идентификации узла применен класс PreferencesDemo.class, хотя в нестатических методах обычно вызывается метод getClass(). Использовать текущий класс для идентификации узла не обязательно, но чаще всего именно так и поступают.
Созданный узел используется для хранения или считывания информации. В данном примере в узел помещаются различные данные, после вызывается метод keys(). Последний возвращает массив строк String[], что может быть непривычно, если вы привыкли использовать метод keys() в коллекциях. Обратите внимание на второй аргумент метода get(). Это значение по умолчанию, которое будет возвращено, если для данного ключа не будет найдено значение. При переборе множества ключей мы знаем, что каждому из них сопоставлено значение, поэтому передача null по умолчанию безопасна, но обычно используется именованный ключ:
prefs.getInt("Companions ". 0));
В таких ситуациях стоит обзавестись имеющим смысл значением по умолчанию. В качестве характерного средства выражения часто выступает следующего рода конструкция:
int usageCount = prefs.getlntC"UsageCount", 0);
usageCount++;
prefs.putlnt("UsageCount". usageCount);
В этом случае при первом запуске программы значение переменной usageCount будет нулем, а при последующих запусках оно должно измениться.
Запустив программу PreferencesDemo.java, вы увидите, что значение usageCount действительно увеличивается при каждом запуске программы, но где же хранятся данные? Никакие локальные файлы после запуска программы не создаются. Система предпочтений привлекает для хранения данных системные ресурсы, а конкретная реализация зависит от операционной системы. Например, в Windows используется реестр (поскольку он и так представляет собой иерархию узлов с набором пар «ключ-значение»). С точки зрения программиста, реализация — это несущественно: информация сохраняется «сама собой», и вам не приходится беспокоиться о том, как это работает на различных системах.
Программный интерфейс предпочтений обладает гораздо большим возможностями, чем было показано в этом разделе. За более подробным описанием обратитесь к документации JDK, этот вопрос там описан достаточно подробно.
Резюме
Библиотека ввода/вывода Java удовлетворяет всем основным потребностям: она позволяет выполнять операции чтения и записи с консолью, файлами, буфером в памяти и даже сетевыми подключениями к Интернету. Наследование позволяет создавать новые типы объектов для ввода и вывода данных. Вы даже можете обеспечить расширяемость для объектов потока, использовав тот факт, что переопределенный метод toString() автоматически вызывается при передаче объекта методу, ожидающему String (ограниченное «автоматическое преобразование типов» Java).
Впрочем, ответы на некоторые вопросы не удается найти ни в документации, ни в архитектуре библиотеки ввода/вывода Java. Например, было бы замечательно, если бы при попытке перезаписи файла возбуждалось исключение — многие программные системы позволяют указать, что файл может быть открыт для вывода только тогда, когда файла с тем же именем еще не существует. В языке Java проверка существования файла возможна лишь с помощью объекта File, поскольку если вы откроете файл как FileOutputStream или FileWriter, старый файл всегда будет стерт.
При знакомстве с библиотекой ввода/вывода возникают смешанные чувства; с одной стороны, она берёт на себя значительный объем работы и к тому же обеспечивает переносимость. Но пока вы не вполне поняли суть работы шаблона декоратора, архитектура библиотеки кажется не совсем понятной, и от вас потребуются определенные усилия для ее изучения и освоения. Кроме того, библиотека не совсем полна: например, только отсутствие необходимых средств заставило меня писать инструменты, подобные TextFile (новый класс Java SE5PrintWriter — шаг в правильном направлении, но это лишь частичное решение). К числу значительных усовершенствований Java SE5 следует отнести и то, что в нем появились возможности форматирования вывода, присутствующие практически в любом другом языке.
Впрочем, как только вы действительно проникаетесь идеей шаблона декоратора и начинаете использовать библиотеку в ситуациях, где требуется вся ее гибкость, вы извлекаете из библиотеки все большую выгоду, и даже требующийся для надстроек дополнительный код не мешает этому.