Использование readResolve
Содержание
1 Введение
2 Десериализация без введения новых объектов
3 Интерфейс RandomAccess
4 Производительность для разных типов списков
5 Ссылки
Введение
Одним из стандартных методов, определенных в java.lang.Object является toString. Этот метод используется для получения строкового представления объекта. Вы можете (и обычно должны) переопределить этот метод для записываемых вами классов. В этой статье рассматриваются некоторые вопросы использования toString.
Для начала рассмотрим следующий пример программы:
class MyPoint {
private final int x, y;
public MyPoint(int x, int y) {
this.x = x;
this.y = y;
}
}
public class TSDemo1 {
public static void main(String args[]) {
MyPoint mp = new MyPoint(37, 47);
// использовать Object.toString() по умолчанию
System.out.println(mp);
// аналогично предыдущему, показывая
// функцию toString() по умолчанию
System.out.println(mp.getClass().getName() + "@"
+ Integer.toHexString(mp.hashCode()));
// неявный вызов toString() объекта
// как часть соединения строк
String s = mp + " testing";
System.out.println(s);
// аналогично предыдущему, за исключением того,
// что ссылка на объект равна null
mp = null;
s = mp + " testing";
System.out.println(s);
}
}
В программе TSDemo1 определяется класс MyPoint для представления точек X,Y. В ней не определяется метод toString для класса. Создается экземпляр класса и затем распечатывается. После выполнения программы TSDemo1 вы должны получить примерно следующий результат:
MyPoint@111f71
MyPoint@111f71
MyPoint@111f71 testing
null testing
Десериализация без введения новых объектов
Вы можете удивиться, как можно распечатать произвольный объект класса. Такие методы библиотеки, как System.out.println, не знают ничего о классе MyPoint или его объектах. Поэтому возникает вопрос, каким образом возможно преобразовать такой объект в строковую форму и вывести на печать, как это делает первый оператор вывода в TSDemo1?
Ответ заключается в том, что println вызывает метод java.io.PrintStream.print(Object), который затем вызывает метод String.valueOf. Метод String.valueOf является очень простым:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
При вызове println со ссылкой на объект MyPoint, метод String.valueOf преобразует объект в строку. String.valueOf сначала проверяет ссылку, чтобы убедиться, что она не равна null. Затем он вызывает метод toString для объекта. Поскольку класс MyPoint не имеет метода toString, используется метод в java.lang.Object по умолчанию.
Что же возвращает метод toString по умолчанию в качестве строкового значения? Формат демонстрируется во втором операторе print приведенного выше примера. Возвращаются объединенные в одну строку название класса, "@" и шестнадцатиричное представление хэшкода объекта. В методе hashCode в Object по умолчанию обычно реализуется преобразование адреса памяти объекта в числовое значение. Поэтому ваш результат может отличаться от приведенного выше.
Третья и четвертая части примера TSDemo1 демонстрируют ту же идею: при использовании "+" для соединения строки с объектом, вызывается toString для преобразования объекта в строку. Чтобы увидеть это, вы должны взглянуть на байткод для TSDemo. Байткод для TSDemo1 (в подходящей для чтения форме) можно получить, выполнив следующую команду:
javap -c . TSDemo1
Если вы посмотрите на байткод, вы увидите, что часть его содержит создание объекта StringBuffer и использование StringBuffer.append(Object) для добавления к нему объекта mp. StringBuffer.append(Object) реализуется очень просто:
public synchronized StringBuffer append(Object obj) {
return append(String.valueOf(obj));
}
Как упоминалось ранее, String.valueOf вызывает toString с объектом для получения его строкового значения.
Интерфейс RandomAccess
Хорошо, достаточно о вызове метода toString по умолчанию. Как можно написать свои собственные методы toString? Это действительно очень просто. Вот пример:
class MyPoint {
private final int x, y;
public MyPoint(int x, int y) {
this.x = x;
this.y = y;
}
public String toString() {
return x + " " + y;
}
}
public class TSDemo2 {
public static void main(String args[]) {
MyPoint mp = new MyPoint(37, 47);
// вызвать MyPoint.toString()
System.out.println(mp);
// вызвать toString() и
// получить из него значение X
String s = mp.toString();
String t = s.substring(0, s.indexOf(' '));
int x = Integer.parseInt(t);
System.out.println(t);
}
}
Результат выполнения этой программы:
37 47
37
Метод toString в этом примере действительно работает, но есть парочка проблем. Одной из них является то, что здесь нет никакого описательного текста. Все что вы видите - это непонятная строка "37 47". Вторая проблема - значения X,Y в объектах MyPoint имеют спецификатор доступа private. Не существует другого способа получить к ним доступ кроме выборки из строки, возвращенной из toString. Вторая часть примера TSDemo2 демонстрирует код, требуемый для извлечения значения X из строки. Этот способ является неэффективным и подверженным ошибкам.
Вот другой подход для записи метода toString, который устраняет проблемы предыдущего примера:
class MyPoint {
private final int x, y;
public MyPoint(int x, int y) {
this.x = x;
this.y = y;
}
public String toString() {
return "X=" + x + " " + "Y=" + y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
public class TSDemo3 {
public static void main(String args[]) {
MyPoint mp = new MyPoint(37, 47);
// вызвать MyPoint.toString()
System.out.println(mp);
// получить значения X,Y при помощи методов доступа
int x = mp.getX();
int y = mp.getY();
System.out.println(x);
System.out.println(y);
}
}
Результат работы программы:
X=37 Y=47
37
47
В этом примере в вывод добавляется некоторый описательный текст и определяются два метода доступа для получения значений X,Y. В общем случае при написании метода toString формат возвращаемой строки должен содержать все содержимое объекта. Ваш метод toString должен, также, содержать описание для каждого поля. Также должен иметься способ получения значений полей объекта без извлечения из строки. Обратите внимание, что использование "+" в toString для построения возвращаемого значения не всегда является самым эффективным подходом. Возможно, вы захотите использовать вместо этого StringBuffer.
Производительность для разных типов списков
Элементарные типы языка программирования Java, такие как int, также имеют методы toString, например, Integer.toString(int). Как насчет массивов? Как преобразовать массив в строку? Вы можете присвоить ссылку на массив ссылке на Object, но массивы не являются реальными классами. Программа выглядит следующим образом:
import java.lang.reflect.*;
public class TSDemo4 {
public static String toString(Object arr) {
// если ссылка на объект равна null или не является
// массивом, вызвать String.valueOf()
if (arr == null || !arr.getClass().isArray()) {
return String.valueOf(arr);
}
// создать строковый буфер и
// получить длину массива
StringBuffer sb = new StringBuffer();
int len = Array.getLength(arr);
sb.append('[');
// выполнить итерацию по элементам массива
for (int i = 0; i < len; i++) {
if (i > 0) {
sb.append(',');
}
// получить i-й элемент
Object obj = Array.get(arr, i);
// преобразовать его в строку путем
// рекурсивного вызова toString()
sb.append(toString(obj));
}
sb.append(']');
return sb.toString();
}
public static void main(String args[]) {
// пример #1
System.out.println(toString("testing"));
// пример #2
System.out.println(toString(null));
// пример #3
int arr3[] = new int[] { 1, 2, 3 };
System.out.println(toString(arr3));
// пример #4
long arr4[][] = new long[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
System.out.println(toString(arr4));
// пример #5
double arr5[] = new double[0];
System.out.println(toString(arr5));
// пример #6
String arr6[] = new String[] { "testing", null, "123" };
System.out.println(toString(arr6));
// пример #7
Object arr7[] = new Object[] {
new Object[] { null, new Object(), null },
new int[] { 1, 2, 3 }, null };
System.out.println(toString(arr7));
}
}
В программе TSDemo4 создается метод toString, а затем в метод toString передается ссылка на произвольный Object. Если эта ссылка не равна null или не относится к массиву, вызывается метод String.valueOf. В противном случае, если Object ссылается на массив, TSDemo4 использует отображение для доступа к элементам массива. Array.getLength и Array.get являются основными методами, работающими с массивом. После извлечения элемента в программе рекурсивно вызывается toString для получения строки для элемента. Этот способ гарантирует корректную обработку многомерных массивов.
Вот результат работы программы:
testing
null
[1,2,3]
[[1,2,3],[4,5,6],[7,8,9]]
[]
[testing,null,123]
[[null,java.lang.Object@111f71,null],[1,2,3],null]
Естественно, если вы имеете очень большой массив и вызываете toString, будет использоваться много памяти и результирующая строка может быть не особенно полезна или доступна для чтения человеком.
Ссылки
Дополнительная информация по использованию методов toString находится в разделе 2.6.2 "Вызовы метода" в книге "Язык программирования Java(tm), третье издание" Arnold, Gosling и Holmes на странице http://java.sun.com/docs/books/javaprog/thirdedition/.
Просмотрите также пункт 9 "Всегда переопределяйте toString" в "Руководстве по эффективному программированию в Java" Джошуа Блоха (Joshua Bloch) (http://java.sun.com/docs/books/effective/).