Действительно ли возможно иметь final transient
поля, которые установлены на какое-либо значение не по умолчанию после сериализации в Java? Мой вариант использования является переменной кэша — вот почему это transient
. У меня также есть привычка к созданию Map
поля, которые не будут изменены (т.е. содержание карты изменяется, но сам объект, остаются тем же), final
. Однако эти атрибуты, кажется, являются противоречащими — в то время как компилятор позволяет такую комбинацию, мне нельзя было установить поле ни на что, но null
после несериализации.
Я попробовал следующее без успеха:
readObject()
— не может быть сделан, так как поле final
.В примере cache
public
только для тестирования.
import java.io.*;
import java.util.*;
public class test
{
public static void main (String[] args) throws Exception
{
X x = new X ();
System.out.println (x + " " + x.cache);
ByteArrayOutputStream buffer = new ByteArrayOutputStream ();
new ObjectOutputStream (buffer).writeObject (x);
x = (X) new ObjectInputStream (new ByteArrayInputStream (buffer.toByteArray ())).readObject ();
System.out.println (x + " " + x.cache);
}
public static class X implements Serializable
{
public final transient Map <Object, Object> cache = new HashMap <Object, Object> ();
}
}
Вывод:
test$X@1a46e30 {}
test$X@190d11 null
Короткий ответ, к сожалению, «нет» - я часто этого хотел. но переходные процессы не могут быть окончательными.
Последнее поле должно быть инициализировано либо прямым присвоением начального значения, либо в конструкторе. Во время десериализации ни один из них не вызывается, поэтому начальные значения для переходных процессов должны быть установлены в закрытом методе readObject (), который вызывается во время десериализации. И для того, чтобы это сработало, переходные процессы должны быть неокончательными.
(Строго говоря, финалы являются окончательными только при первом чтении, поэтому возможны уловки, которые присваивают значение до того, как оно будет прочитано, но для меня это заходит слишком далеко.)
Вы можете изменить содержимое поля с помощью Reflection. Работает на Java 1.5+. Это будет работать, потому что сериализация выполняется в одном потоке. После того, как другой поток обратится к тому же объекту, он не должен изменить конечное поле (из-за странностей в модели памяти и рефлексии).
Итак, в readObject()
вы можете сделать что-то похожее на этот пример:
import java.lang.reflect.Field;
public class FinalTransient {
private final transient Object a = null;
public static void main(String... args) throws Exception {
FinalTransient b = new FinalTransient();
System.out.println("First: " + b.a); // e.g. after serialization
Field f = b.getClass().getDeclaredField("a");
f.setAccessible(true);
f.set(b, 6); // e.g. putting back your cache
System.out.println("Second: " + b.a); // wow: it has a value!
}
}
Помните: Final - это уже не final!
Общим решением проблем, подобных этой, является использование "последовательного прокси" (см. Effective Java 2nd Ed). Если вам нужно приспособить это к существующему классу с возможностью последовательного выполнения без нарушения совместимости с последовательными классами, то вам придется немного повозиться.