《Java基础知识》Java序列化与反序列化详解

序列化的作用:为了不同jvm之间共享实例对象的一种解决方案.由java提供此机制。

序列化应用场景:

1. 分布式传递对象。

2. 网络传递对象。

3. tomcat关闭以后会把session对象序列化到SESSIONS.ser文件中,等下次启动的时候就把这些session再加载到内存中。

完整案例:

import java.io.Serializable;

public class Box implements Serializable {

    public Box(){
        System.out.println("调用构造Box方法");
    }

    private String width;
    private String height;

    public String getWidth() {
        return width;
    }

    public void setWidth(String width) {
        this.width = width;
    }

    public String getHeight() {
        return height;
    }

    public void setHeight(String height) {
        this.height = height;
    }

    @Override
    public String toString() {
        return "Box{" +
                "width=" + width +
                ", height=" + height +
                '}';
    }
}
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableDemo {
    public static void main(String[] args){
        Box myBox = new Box();
        myBox.setWidth("50");
        myBox.setHeight("30");

        try{
            File file = new File("src\\demo\\knowledgepoints\\file\\foo.ser");

            //把对象信息写入文件中。
            ObjectOutputStream oout = new ObjectOutputStream (new FileOutputStream(file));
            oout.writeObject(myBox);
            oout.close();

            //把对象信息从文件中获取出来。
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
            Box newMyBox = (Box)oin.readObject(); // 没有强制转换到Person类型
            oin.close();
            System.out.println(newMyBox);
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

运行结果:

序列化相关注意事项:

    a)序列化时,只对对象的状态进行保存,而不管对象的方法;

    b)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;

    c)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;

    d)  被序列化的对象需要 实现(Serializable)接口;

    e) 案例:构造方法没有执行,说明生成新对象,不是通过New出来的;

Java 对象被序列化需要实现(Serializable)接口,原因:

在 我们将对象序列化的类 ObjectOutputStream 的方法 writeObject0 中可以找到答案。

以下是JDK8的部分源码,红色字体部分就是原因。

private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
        boolean oldMode = bout.setBlockDataMode(false);
        depth++;
        try {
            // handle previously written and non-replaceable objects
            int h;
            if ((obj = subs.lookup(obj)) == null) {
                writeNull();
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
            } else if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
                return;
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
                return;
            }

            // check for replacement object
            Object orig = obj;
            Class<?> cl = obj.getClass();
            ObjectStreamClass desc;
            for (;;) {
                // REMIND: skip this check for strings/arrays?
                Class<?> repCl;
                desc = ObjectStreamClass.lookup(cl, true);
                if (!desc.hasWriteReplaceMethod() ||
                    (obj = desc.invokeWriteReplace(obj)) == null ||
                    (repCl = obj.getClass()) == cl)
                {
                    break;
                }
                cl = repCl;
            }
            if (enableReplace) {
                Object rep = replaceObject(obj);
                if (rep != obj && rep != null) {
                    cl = rep.getClass();
                    desc = ObjectStreamClass.lookup(cl, true);
                }
                obj = rep;
            }

            // if object replaced, run through original checks a second time
            if (obj != orig) {
                subs.assign(orig, obj);
                if (obj == null) {
                    writeNull();
                    return;
                } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                    writeHandle(h);
                    return;
                } else if (obj instanceof Class) {
                    writeClass((Class) obj, unshared);
                    return;
                } else if (obj instanceof ObjectStreamClass) {
                    writeClassDesc((ObjectStreamClass) obj, unshared);
                    return;
                }
            }

            // remaining cases
            if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }

 

如何将序列化控制在自己手中?

1. 通过关键字【transient】实现,字段值不被序列化。

改一下Box类:

import java.io.Serializable;

public class Box implements Serializable {

    public Box(){
        System.out.println("调用构造Box方法");
    }

    // 关键字:transient 控制width不被序列化,保护数据。
    private transient String width;
    private String height;

    public String getWidth() {
        return width;
    }

    public void setWidth(String width) {
        this.width = width;
    }

    public String getHeight() {
        return height;
    }

    public void setHeight(String height) {
        this.height = height;
    }

    @Override
    public String toString() {
        return "Box{" +
                "width=" + width +
                ", height=" + height +
                '}';
    }
}

运行结果:

这个box的width值被擦除了。

 

2. writeObject()方法与readObject()方法。

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Box implements Serializable {

    public Box(){
        System.out.println("调用构造Box方法");
    }

    // 关键字:transient 控制width不被序列化,保护数据。
    private transient String width;
    private String height;

    public String getWidth() {
        return width;
    }

    public void setWidth(String width) {
        this.width = width;
    }

    public String getHeight() {
        return height;
    }

    public void setHeight(String height) {
        this.height = height;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeChars(width);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        width = in.readLine();
    }

    @Override
    public String toString() {
        return "Box{" +
                "width=" + width +
                ", height=" + height +
                '}';
    }
}

运行结果:

加入writeObject()方法与readObject()方法后,width值又回来了,这两个方法都是私有的,已经可以基本猜测是通过反射调用方法,赋值的。

 

3.Externalizable 接口,自定义实现写入和读取序列化对象

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class BoxTmp implements Externalizable {
    public BoxTmp(){
        System.out.println("调用构造Box方法");
    }

    private String width;
    private String height;

    public String getWidth() {
        return width;
    }

    public void setWidth(String width) {
        this.width = width;
    }

    public String getHeight() {
        return height;
    }

    public void setHeight(String height) {
        this.height = height;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeChars(width+","+width);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        String str = in.readLine();
        this.width = str.split(",")[0];
        this.height = str.split(",")[1];
    }

    @Override
    public String toString() {
        return "BoxTmp{" +
                "width='" + width + '\'' +
                ", height='" + height + '\'' +
                '}';
    }
}
public class ExternalizableDemo {
    public static void main(String[] args){
        BoxTmp boxTmp = new BoxTmp();
        boxTmp.setWidth("50");
        boxTmp.setHeight("30");

        try{
            File file = new File("src\\demo\\knowledgepoints\\file\\foo.txt");

            //把对象信息写入文件中。
            ObjectOutputStream oout = new ObjectOutputStream (new FileOutputStream(file));
            oout.writeObject(boxTmp);
            oout.close();

            //把对象信息从文件中获取出来。
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
            BoxTmp newBoxTmp = (BoxTmp)oin.readObject(); // 没有强制转换到Person类型
            oin.close();
            System.out.println(newBoxTmp);
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

运行结果:

 

Externalizable接口中的writeExternal和readExternal 方法可以用来自定义实现值的传递,覆盖等其他操作。

同时构造方法被执行了,说明这个类是被new出来后,由你支配。

 

4. readResolve()方法

import java.io.ObjectStreamException;
import java.io.Serializable;

public class Box implements Serializable {

    public static Box box = new Box();

    public Box(){}

    public Box(String height,String width){
        this.height = height;
        this.width = width;
    }

    public static Box getInstance() {
        if(box == null){
            box = new Box("20","10");
        }
        return box;
    }

    private String width;
    private String height;

    public String getWidth() {
        return width;
    }

    public void setWidth(String width) {
        this.width = width;
    }

    public String getHeight() {
        return height;
    }

    public void setHeight(String height) {
        this.height = height;
    }

    private Object readResolve() throws ObjectStreamException {
        return Box.box;
    }
}
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerializableDemo {
    public static void main(String[] args){
        Box myBox = Box.getInstance();
        try{
            File file = new File("src\\demo\\knowledgepoints\\file\\foo.ser");

            //把对象信息写入文件中。
            ObjectOutputStream oout = new ObjectOutputStream (new FileOutputStream(file));
            oout.writeObject(myBox);
            oout.close();

            //把对象信息从文件中获取出来。
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
            Box newMyBox = (Box)oin.readObject(); // 没有强制转换到Person类型
            oin.close();
            System.out.println("newMyBox == myBox : "+(newMyBox == myBox));
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

运行结果:

Box类中实现readResolve() 方法可以实现单例对象还是同一个。

 

参考资料

https://www.cnblogs.com/qq3111901846/p/7894532.html

posted @ 2019-10-30 19:42  加速丨世界  阅读(653)  评论(0编辑  收藏  举报