Брюс Эккель - Философия Java3
}
class Square extends Shape { private static int color, public Squared nt xVal. int yVal. int dim) { super(xVal. yVal. dim). color = RED.
}
public void setColor(int newColor) { color = newColor. } public int getColorO { return color. }
}
class Line extends Shape {
private static int color = RED. public static void
serializeStaticState(ObjectOutputStream os) throws IOException { os writelnt(color); } public static void
deserializeStaticState(ObjectInputStream os) throws IOException { color = os.readlntO. } public Line(int xVal. int yVal. int dim) { super(xVal, yVal, dim);
}
public void setColor(int newColor) { color = newColor; } public int getColorO { return color; }
}
public class StoreCADState {
public static void main(String[] args) throws Exception { List<Class<? extends Shape» shapeTypes =
new ArrayList<Class<? extends Shape»(); // Добавление ссылок на объекты класса: shapeTypes add(Circle.class), shapeTypes add(Square.class); shapeTypes.add(Line.class); List<Shape> shapes = new ArrayList<Shape>(); // Создание фигур, for(int i = 0; i < 10; i++)
shapes add(Shape.randomFactory()); // Назначение всех статических цветов: for(int i = 0; i < 10. i++)
((Shape)shapes.get(i)).setColor(Shape.GREEN); // Сохранение вектора состояния: ObjectOutputStream out = new ObjectOutputStream( new FileOutputStreamC'CADState.out")); out.writeObject(shapeTypes); Line.serializeStaticState(out); out.writeObject(shapes); // Вывод фигур: System out.printin(shapes);
}
} /* 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[ll] dim[22]
. class Linecolor[3] xPos[4] yPos[83] dim[6]
, class Circlecolor[3] xPos[75] yPos[10] dim[42] ]
*/// ~
Класс 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 {
@SuppressWarni ngs("unchecked")
public static void main(Stnng[] args) throws Exception { ObjectlnputStream in = new ObjectInputStream( new Fi1eInputStream("CADState.out")). // Данные читаются в том порядке, в котором они были записаны-List<Class<? extends Shape<@062>> shapeTypes =
(List<Class<? extends Shape») in readObject О. Line deserializeStaticState(in); List<Shape> shapes = (List<Shape>)in.readObject(). System out println(shapes).
}
} /* Output
[class Circlecolor[l] xPos[58] yPos[55] dim[93] . class Squarecolor[0] xPos[61] yPos[61] dim[29] . class Linecolor[3] xPos[68] yPos[0] dim[22] . class Circlecolor[l] xPos[7] yPos[88] dim[28] . class Squarecolor[0] xPos[51] yPos[89] dim[9] . class Linecolor[3] xPos[78] yPos[98] dim[61]
. class
, class
, class
, class
]
*/// ~
Мы видим, что значения переменных xPos, у Pos и dim сохранились и были успешно восстановлены, однако при восстановлении статической информации произошло что-то странное. При записи все статические поля color имели значение 3, но восстановление дало другие результаты. В окружностях значением стала единица (то есть константа RED), а в прямоугольниках поля color вообще равны нулю (помните, в этих объектах инициализация проходит в конструкторе). Похоже, статические поля вообще не сериализовались! Да, это именно так — хотя класс Class и реализует интерфейс Serializable, происходит это не так, как нам хотелось бы. Отсюда, если вам понадобится сохранить статические значения, делайте это самостоятельно.
Именно для этой цели предназначены методы serializeStaticState() и dese-rializeStaticState() класса Line. Вы можете видеть, как они вызываются в процессе сохранения и восстановления системы. (Заметьте, порядок действий при сохранении информации должен соблюдаться и при ее десериализации.) Поэтому для правильного выполнения этих программ необходимо сделать следующее:
1. Добавьте методы serializeStaticState() и deserializeStaticState() во все фигуры Shape.
2. Уберите из программы список shapeTypes и весь связанный с ним код.
3. При сериализации и восстановлении вызывайте новые методы для сохранения статической информации.
Также стоит позаботиться о безопасности, ведь сериализация сохраняет и закрытые (private) поля. Если в вашем объекте имеется конфиденциальная информация, ее необходимо пометить как transient. Но в таком случае придется подумать о безопасном способе хранения такой информации, ведь при восстановлении объекта необходимо восстанавливать все его данные.
Предпочтения
В пакете JDK 1.4 появился программный интерфейс API для работы с предпочтениями (preferences). Предпочтения гораздо более тесно связаны с долговременным хранением, чем механизм сериализации объектов, поскольку они позволяют автоматически сохранять и восстанавливать вашу информацию. Однако они применимы лишь к небольшим, ограниченным наборам данных — хранить в них можно только примитивы и строки, и длина строки не должна превышать 8 Кбайт (не так уж мало, но вряд ли подойдет для решения серьезных задач). Как и предполагает название нового API, предпочтения предназначены для хранения и получения информации о предпочтениях пользователя и конфигурации программы.
Circlecolor[l] xPos[20] yPos[58] dim[16] Squarecolor[0] xPos[40] yPos[ll] dim[22] Linecolor[3] xPos[4] yPos[83] dim[6] Circlecolor[l] xPos[75] yPos[10] dim[42]
Предпочтения представляют собой наборы пар «ключ-значение» (как в картах), образующих иерархию узлов. Хотя иерархия узлов и годится для построения сложных структур, чаще всего создают один узел, имя которого совпадает с именем класса, и хранят информацию в нем. Простой пример:
//• 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.putlntC"Companions". 4); prefs.putBooleanC'Are there witches?", true), int usageCount = prefs.getlntC'UsageCount". 0), usageCount++;
prefs putlntC"UsageCount". usageCount).
for(String key : prefs.keys())
print(key + ": "+ prefs.get(key. null));
// Всегда необходимо указывать значение по умолчанию:
print("How many companions does Dorothy have? " +
prefs.getInt("Companions". 0));
}
} /* Output: Location: Oz Footwear. Ruby Slippers Companions: 4 Are there witches?: true UsageCount- 53
How many companions does Dorothy have? 4 *///-
Здесь используется метод userNodeForPackage(), но с тем же успехом можно было бы заменить его методом systemNodeForPackage(), это дело вкуса. Предполагается, что префикс user используется для хранения индивидуальных предпочтений пользователя, a system — для хранения информации общего плана о настройках установки. Так как метод main() статический, для идентификации узла применен класс PreferencesDemo.class, хотя в нестатических методах обычно вызывается метод getClass(). Использовать текущий класс для идентификации узла не обязательно, но чаще всего именно так и поступают.
Созданный узел используется для хранения или считывания информации. В данном примере в узел помещаются различные данные, после вызывается метод keys(). Последний возвращает массив строк String[], что может быть непривычно, если вы привыкли использовать метод keys() в коллекциях. Обратите внимание на второй аргумент метода get(). Это значение по умолчанию, которое будет возвращено, если для данного ключа не будет найдено значение. При переборе множества ключей мы знаем, что каждому из них сопоставлено значение, поэтому передача null по умолчанию безопасна, но обычно используется именованный ключ:
prefs.getInt("Companions ". 0)),
В таких ситуациях стоит обзавестись имеющим смысл значением по умолчанию. В качестве характерного средства выражения часто выступает следующего рода конструкция:
int usageCount = prefs.getlntC"UsageCount", 0);
usageCount++;
prefs.putlnt("UsageCount". usageCount);
В этом случае при первом запуске программы значение переменной usage-Count будет нулем, а при последующих запусках оно должно измениться.
Запустив программу PreferencesDemo.java, вы увидите, что значение usage-Count действительно увеличивается при каждом запуске программы, но где же хранятся данные? Никакие локальные файлы после запуска программы не создаются. Система предпочтений привлекает для хранения данных системные ресурсы, а конкретная реализация зависит от операционной системы. Например, в Windows используется реестр (поскольку он и так представляет собой иерархию узлов с набором пар «ключ-значение»). С точки зрения программиста, реализация — это несущественно: информация сохраняется «сама собой», и вам не приходится беспокоиться о том, как это работает на различных системах.
Программный интерфейс предпочтений обладает гораздо большим возможностями, чем было показано в этом разделе. За более подробным описанием обратитесь к документации JDK, этот вопрос там описан достаточно подробно.