认识Serializable和重写读写方法

原创 2018年04月15日 17:11:29

简述

需要将对象持久化到我们的存储设备上或者通过网络传输到其他客户端,那么我们就需要序列化。

Serializable为Java中带的一种序列化方式,使用起来非常简单。只需要让类实现一个Serializable接口就可以进行序列化了。

使用

先讲初步使用,后面会讲到serialVersionUID。

  1. 定义一个User类,实现Serializable接口
public class User implements Serializable {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  1. 操作User类,持久化到cache.txt文件中,并从文件中读出来
//创建到User类
        User user = new User("大灰狼", 18);
        try {
            File file = new File("cache.txt");
            if (!file.exists()) {
                file.createNewFile();
            }

            //将User类序列化到文件中
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
            objectOutputStream.writeObject(user);

            System.out.println("序列化成功");

            //从文件中读出来
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
            User user1 = (User) objectInputStream.readObject();
            System.out.println("" + user1);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

就这样,最简单到使用就完成了。

serialVersionUID

如果,咱们某一天改了User类的内部结构,如增加了一个字段:boolean isMan,但是文件类已经持久化的数据并没有改动,我们需要反序列化读出来

  • 读取代码
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
            User user1 = (User) objectInputStream.readObject();
            System.out.println("" + user1);

我们会很明显的看到报出一个错误:

java.io.InvalidClassException: serial.User; local class incompatible: stream classdesc serialVersionUID = -3951066851478225822, local class serialVersionUID = -8381808064053356693
  • 原因:因为咱们存储的时候,有帮我们自动生成一个serialVersionUID就是-3951066851478225822,可以当作存储在文件中的类的版本标示。而我们本地类版本升级了,自动帮我们修改了版本标示。在反序列化的时候会检测这个版本标示是否相同,所以在反序列化的时候就通不过了,这时候就不能反序列化了。

我们自然不想看到这种情况出现,所以通常情况下,我们应该手动指定serialVersionUID的值,如123L

只需要在User中添加:

private static final long serialVersionUID = 123L;

那我们重新试试(前面的文件不能用了,删除后再来).

  1. 先写文件,只包含name和age。

  2. 然后我们再升级User类,在其中添了两个字段如下:

private boolean isMan;
private String address;
  1. 再读一次文件,打印如下
User{name='大灰狼', age=18, isMan=false, address='null'}

可以看到能成功读取了,只是缺失字段使用了默认值代替,做到了最大限度的恢复数据。

注意:类升级指的是内部结构,如果改了类名,那是怎么都反序列化不回来的,因为类结构有了毁灭性的改变,那错误将是这样的:
java.lang.ClassNotFoundException: serial.User

重写序列化和反序列化

就如同我们调用一样,序列化过程其实就是通过ObjectOutputStream的writeObject、readObject来体现的。我们跟踪下流程,可以看到调用到了要序列化对象的私有方法writeObject,部分代码如下:

  1. 从ObjectOutputStream进行写方法
public final void writeObject(Object obj) throws IOException {
    try {
           //调用该方法执行写
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }
  1. writeObject0:
private void writeObject0(Object obj, boolean unshared){
            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;
            }
}
  1. ObjectStreamClass.lookup
static ObjectStreamClass lookup(Class<?> cl, boolean all) {

            try {
            //创建流对象
                entry = new ObjectStreamClass(cl);
            } catch (Throwable th) {
                entry = th;
            }
}
  1. 创建流对象,寻找到要序列化对象中的私有方法,得到writeObjectMethod和readObjectMethod
private ObjectStreamClass(final Class<?> cl) {

//获取对象中的私有方法,writeObject和readObject
                        writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE);
                        readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);
}
  1. 在执行写序列化数据时调用到方法:writeSerialData
private void writeSerialData(Object obj, ObjectStreamClass desc)
                try {
                    curContext = new SerialCallbackContext(obj, slotDesc);
                    bout.setBlockDataMode(true);
                    //执行到了创建好流对象的方法
                    slotDesc.invokeWriteObject(obj, this);
                    bout.setBlockDataMode(false);
                    bout.writeByte(TC_ENDBLOCKDATA);
                } finally {
                    curContext.setUsed();
                    curContext = oldContext;
                    if (extendedDebugInfo) {
                        debugInfoStack.pop();
                    }
                }
}
  1. invokeWriteObject调用到了writeObjectMethod.invoke
void invokeWriteObject(Object obj, ObjectOutputStream out)
        throws IOException, UnsupportedOperationException
    {
    if (writeObjectMethod != null) {
            try {
            //通过反射执行了要序列化对象的writeObject方法
                writeObjectMethod.invoke(obj, new Object[]{ out });
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof IOException) {
                    throw (IOException) th;
                } else {
                    throwMiscException(th);
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

到这里我们就能看到我们要序列化对象的writeObject也被调用了,readObject过程分析也是一样的。通过这样我们可以想象,是不是我们就可以自己实现这个方法来重写写数据呢?答案肯定是的,哈哈哈.

重写writeObject和readObject:
  1. 改isMan默认不序列化
  2. 重写写和读方法
public class User implements Serializable {

    private static final long serialVersionUID = 123L;

    private String name;
    private int age;
    private transient boolean isMan;
    private String address;

    public User(String name, int age, boolean isMan, String address) {
        this.name = name;
        this.age = age;
        this.isMan = isMan;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public boolean isMan() {
        return isMan;
    }

    public void setMan(boolean man) {
        isMan = man;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", isMan=" + isMan +
                ", address='" + address + '\'' +
                '}';
    }

    /**
     * 重写写方法
     *
     * @param oos
     * @throws IOException
     */
    private void writeObject(ObjectOutputStream oos) throws IOException {
        //调用默认的序列化方法,如里面注释一样,可以把非静态和非transient字段给序列化了
        oos.defaultWriteObject();
        //把isMan也序列化一下
        oos.writeBoolean(isMan);
        System.out.println("序列化成功");
    }

    /**
     * 重写读方法
     *
     * @param ois
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private void readObject(ObjectInputStream ois) throws IOException,
            ClassNotFoundException {
        //调用默认的反序列化方法,如里面注释一样,可以把非静态和非transient字段给反序列化了
        ois.defaultReadObject();
        //读取序列化的字段
        isMan = ois.readBoolean();
        System.out.println("反序列化成功");
    }
}

再进行序列化和反序列化,就能看到isMan咱们也能够正常写入和读了。

序列化是持久化的一个必备品,记录到这里大概对Serializable就更熟悉了。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ddxxii/article/details/79950919

联合主键实现Serializable接口,同时重写equals和hasCode方法 的原因和步骤

关于联合主键 联合主键为什么要重写equals方法和hashCode方法,是为了保证唯一性 1、在数据库保证唯一性是使用的联合主键 2、把一系列的对象放到内存的时候,为了区分同名对象,数据库是使...
  • qq_26975307
  • qq_26975307
  • 2016-12-11 10:59:24
  • 597

联合主键实现Serializable接口,重写equals和hasCode方法

序列化的目的:在使用多台服务器时,其中一台坏了,序列化后可以将该服务器的对象传给另外一台服务器。还有假如内存存满了,可以使用虚拟内存(硬盘临时分出的空间),序列化后可以将内存的部分内容写到硬盘上。 ...
  • xun_2008
  • xun_2008
  • 2011-12-18 09:19:30
  • 1155

8_实现Serializable接口_重写equals和hashCode方法

控制台java application运行报错: 19:20:51,916 WARN RootClass:233 - composite-id class does not override eq...
  • Mr_machicao
  • Mr_machicao
  • 2017-08-27 18:27:15
  • 264

java 序列化和 hashcode、equals 方法重写

序列化、hashCode、equals 方法
  • tengdazhang770960436
  • tengdazhang770960436
  • 2016-12-02 16:46:38
  • 1372

愿你从根本上了解HashMap

想必对于一个Java开发程序员来说,HashMap用的不少了,当被人问起你了解HashMap的时候,也许你会说:HashMap是线程不安全的,HashMap采取K,V的形式存储,HashMap是高效,...
  • Barnetthe
  • Barnetthe
  • 2015-09-07 20:55:32
  • 691

什么是writeObject 和readObject?可定制的序列化过程(转)

(对象序列化与反序列化、transient关键字) 这篇文章很直接,简单易懂。尝试着翻译一下 ,原文是What are writeObject and readObject? Customizing...
  • fengwen2011
  • fengwen2011
  • 2016-04-10 21:55:08
  • 2516

java 序列化的问题 如何认识和解决序列化 demo

  • 2011年06月10日 03:43
  • 2KB
  • 下载

Serializable方式实现数据传递

FirstActivity.javapackage com.sdutacm.getcontextskillpractise;import android.content.Intent; import ...
  • CCCrunner
  • CCCrunner
  • 2017-08-09 20:06:31
  • 176

Java 序列化Serializable详解(附详细例子)

Java 序列化Serializable详解(附详细例子)  1、什么是序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserializatio...
  • u010093630
  • u010093630
  • 2014-05-30 01:10:57
  • 9982

详细介绍Serializable的使用和注意事项

引言 本篇文章目的在于注意使用Serializable过程中的注意事项 开启Serializable Serializable是Java提供的一套标准的序列化接口,是一个空接口,为对象提...
  • Justin_1107
  • Justin_1107
  • 2017-06-28 14:14:58
  • 1037
收藏助手
不良信息举报
您举报文章:认识Serializable和重写读写方法
举报原因:
原因补充:

(最多只允许输入30个字)