Effective Java笔记-序列化

谨慎地实现Serializable接口

1.代价一:一旦这个类被发布,就大大降低了"改变这个类的实现"的灵活性:如果一个类实现了Serializable接口,它的字节流编码就变成了它的导出API的一部分,你必须永远支持这种序列化形式.
2.默认的序列化形式跟类的内部表示法有关(包括类中私有和包级私有的实例域,这意味着类中私有和包级私有的实例域都将变成导出的API的一部分,这不符合"最低限度地访问域"的实践准则)
3.另外采用默认的序列化形式意味着不能修改类的实现(类的内部表示)
4.可以自定义类的序列化形式

5.每个可序列化的都有一个唯一标识号(也被称为序列号版本UID(serial version UID))与它关联,如果你没有一个名为serialVersionUID的私有静态final的long域显式地指定该标识号,系统就会自动根据这个类来调用一个复杂的运算过程,从而在运行时产生该标识号,这个自动产生的值会受到类名称,他所实现的接口的名称,以及所有公有和受保护的成员的名称所影响.这个唯一标识号存在的意义就是说唯一标识号相同的类(一个类的原来的实现和更新的实现)才可以相互序列化和反序列化转换(或者说唯一标识号相同的两个实现具有相同的序列化形式),否则就会报InvalidClassException

6.代价二:他增加了出现Bug和安全漏洞的可能性
7.序列化机制(反序列化机制)是一种语言之外的对象创建机制

8.很容易破坏对象的约束关系(由真正的构造器建立起来的约束关系)

9.代价三:随着类发行新的版本,相关的测试负担也增加了

10.每发行新的类版本就要测试二进制兼容性:”和旧版本的序列化-反序列化”成功,和语义兼容性

11.为了继承而设计的类应该尽可能少的去实现Serializable接口,用户的接口也应该尽可能少地继承Serializable.如果违反这条规则,扩展这个类或者实现这个接口的程序员就会背上沉重的负担
12.为了继承而设计的类中,实现了Serializable接口的有Throwable类(所以RMI的异常可以从服务器端传到客户端),Component类(因此GUI可以被发送,保存和恢复),HttpServlet(因此会话状态可以被缓存(存到硬盘??))
13.如果你实现了一个带有实例域的可序列化和可扩展的类,且当类的实例域被初始化成他们的默认值时就会违背类的某些约束,你就应该添加下面这段代码:
    private void readObjectNoData() throws InvalidObjectException{
        throw new InvalidObjectException("Stream data required");
    }

14.如果一个类是不可序列化的且没有无参构造器那么他的子类不可能可序列化,所以,对于为继承而设计的不可序列化的类,你都应该提供一个无参构造器.这通常很简单,因为为了继承而设计的类通常没有状态,但并不总是这样,下面谈一下继承而设计的类有状态时应该则么做
15.你可能好奇不就是增加一个无参构造器吗,直接加就好了
16.首先我们要知道序列化和反序列化的原理:序列化其实就是调用writeObject方法,反序列化就是java自动帮你调用无参构造器获得一个状态为默认值的对象,然后调用对象的readObject方法,一般我们会在readObject方法中调用一个初始化的方法并传入参数(以序列化形式传过来的)
17.那么问题就来了,如果我们本来就有一个有参的构造器,在其中我们初始化状态并建立状态约束,然后我们再加一个无参的构造器和一个初始化方法,在初始化方法中我们也给状态赋值并建立状态约束,这样我们就有了两个"初始化方法"了,会增大状态空间引起混乱.
18.下面是代码示例:
// Nonserializable stateful class allowing serializable subclass
public abstract class AbstractFoo {
    private int x, y; // Our state

    // This enum and field are used to track initialization
    private enum State {
        NEW, INITIALIZING, INITIALIZED
    };

    private final AtomicReference<State> init = new AtomicReference<State>(
            State.NEW);

    public AbstractFoo(int x, int y) {
        initialize(x, y);
    }

    // This constructor and the following method allow
    // subclass's readObject method to initialize our state.
    protected AbstractFoo() {
    }

    protected final void initialize(int x, int y) {
        if (!init.compareAndSet(State.NEW, State.INITIALIZING))
            throw new IllegalStateException("Already initialized");
        this.x = x;
        this.y = y;
        // Do anything else the original constructor did
        init.set(State.INITIALIZED);
    }

    // These methods provide access to internal state so it can
    // be manually serialized by subclass's writeObject method.
    protected final int getX() {
        checkInit();
        return x;
    }

    protected final int getY() {
        checkInit();
        return y;
    }

    // Must call from all public and protected instance methods
    private void checkInit() {
        if (init.get() != State.INITIALIZED)
            throw new IllegalStateException("Uninitialized");
    }
    // Remainder omitted
}
// Serializable subclass of nonserializable stateful class
public class Foo extends AbstractFoo implements Serializable {
    private void readObject(ObjectInputStream s) throws IOException,
            ClassNotFoundException {
        s.defaultReadObject();

        // Manually deserialize and initialize superclass state
        int x = s.readInt();
        int y = s.readInt();
        initialize(x, y);
    }

    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();

        // Manually serialize superclass state
        s.writeInt(getX());
        s.writeInt(getY());
    }

    // Constructor does not use the fancy mechanism
    public Foo(int x, int y) {
        super(x, y);
    }

    private static final long serialVersionUID = 1856835860954L;
}

19.内部类不应该实现Serializable,他们使用编译器产生的<em>合成域</em>来保存外围实例的引用,以及保存来自外围作用域的局部变量的值."这些域如何对应到类定义中"并没有明确的规定.因此内部类的默认序列化形式是定义不清楚的.然而静态成员类却可以实现Serializable接口.

第75条 考虑使用自定义的序列化形式

1.理想的序列化形式应该只包含该对象所表示的<em>逻辑数据</em>,如果一个对象的物理表示法等同于它的逻辑内容,可能就适合于使用默认的序列化形式.
2.即使你确定了默认的序列化形式是合适的,通常还必须提供一个readObject方法以保证约束关系和安全性
3.transient域表示不会包含在默认的序列化形式中
4.在决定将一个域做成transient之前,请一定要确信它的值将是该对象逻辑状态的一部分
5.无论你是否使用默认的序列化形式,如果在读取整个对象状态的任何其他地方强制同步,则也必须在对象序列化(writeObject)上强制这种同步(实际上是想表示writeObject跟其他方法一样,他就是一个访问对象状态的方法,如果其中的状态要同步访问那就同步)
6.不管你选择了哪种序列化形式,都要为自己编写的每个可序列化的类声明一个显式的序列版本UID

第76条 保护性地编写readObject方法

1.对于对象引用域必须保持为私有的类,要保护性地拷贝这些域中的每个对象.不可变类的可变组件就属于这一类别.
2.对于任何约束条件,如果检查失败,则抛出一个InvalidObjectException异常.这些检查动作应该跟在所有的保护性拷贝之后.
3.如果整个对象图在被反序列化之后必须进行验证,就应该使用ObjectInputValidation接口
4.无论是直接方式还是间接方式,都不要调用类中任何可覆盖的方法
详见书

第77条 对于实例控制,枚举类型优先于readResolve

如果一个单例模式的类加上了implement Serializable,他就不再符合单例模式了
那么如何实现实例控制呢?
一种方法是使用readResolve特性:对于一个正在被反序列化的对象,如果它的类定义了一个readResolve方法,并且具备正确的声明,那么在反序列化之后,<em>新建对象</em>上的readResolve方法就会被调用,然后,该方法返回的对象引用将被返回,取代新建的对象.如果依赖readResolve进行实例控制,带有对象引用类型的所有实例域则都必须声明为transient的,否则可能被攻击(具体的攻击方式见书)
如:
public class Elvis implements Serializable {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    }

    private transient String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" };

    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }

    private Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }
}
推荐使用枚举类型实现单例模式:
public enum Elvis {
    INSTANCE;
    private String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" };

    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}
    JVM保障了除了所声明的常量不会有别的实例

第78条 考虑用序列化代理代替序列化实例

public final class Period implements Serializable {
    private final Date start;
    private final Date end;

    /**
     * @param start
     *            the beginning of the period
     * @param end
     *            the end of the period; must not precede start
     * @throws IllegalArgumentException
     *             if start is after end
     * @throws NullPointerException
     *             if start or end is null
     */
    public Period(Date start, Date end) {
        this.start = new Date(start.getTime());
        this.end = new Date(end.getTime());
        if (this.start.compareTo(this.end) > 0)
            throw new IllegalArgumentException(start + " after " + end);
    }

    public Date start() {
        return new Date(start.getTime());
    }

    public Date end() {
        return new Date(end.getTime());
    }

    public String toString() {
        return start + " - " + end;
    }

    // Serialization proxy for Period class
    //为可序列化的类设计一个私有的静态嵌套类,精确地表示外围类的实例的逻辑状态.这个嵌套类被称作序列化代理
    private static class SerializationProxy implements Serializable {
        private final Date start;
        private final Date end;

        //他应该有一个单独的构造器,其参数就是那个外围类
        SerializationProxy(Period p) {
            this.start = p.start;
            this.end = p.end;
        }

        private static final long serialVersionUID = 234098243823485285L; // Any
                                                                            // number
                                                                            // will
                                                                            // do
                                                                            // (Item
                                                                            // 75)

        // readResolve method for Period.SerializationProxy
        //提供readResolve使得序列化系统在反序列化时将序列化代理转变为外围类的实例
        private Object readResolve() {
            return new Period(start, end); // Uses public constructor
        }
    }

    // writeReplace method for the serialization proxy pattern
    private Object writeReplace() {
        return new SerializationProxy(this);
    }

    // readObject method for the serialization proxy pattern
    //攻击者可能伪造,所以将readObject变为private
    private void readObject(ObjectInputStream stream)
            throws InvalidObjectException {
        throw new InvalidObjectException("Proxy required");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值