заключительные переходные поля и сериализация

Действительно ли возможно иметь 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
59
задан doublep 3 June 2010 в 18:53
поделиться

3 ответа

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

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

(Строго говоря, финалы являются окончательными только при первом чтении, поэтому возможны уловки, которые присваивают значение до того, как оно будет прочитано, но для меня это заходит слишком далеко.)

34
ответ дан 24 November 2019 в 18:31
поделиться

Вы можете изменить содержимое поля с помощью 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!

16
ответ дан 24 November 2019 в 18:31
поделиться

Общим решением проблем, подобных этой, является использование "последовательного прокси" (см. Effective Java 2nd Ed). Если вам нужно приспособить это к существующему классу с возможностью последовательного выполнения без нарушения совместимости с последовательными классами, то вам придется немного повозиться.

5
ответ дан 24 November 2019 в 18:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: