Java篇 - 聊聊Serializable (常见问题集锦)

接着来聊聊Serializable,Serializable的意思是序列化。

 

1. 序列化的概念

序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

 

 

2. 序列化的使用场景

  • 当你想把内存中的对象状态保存到一个文件中或者数据库中时候。
  • 当你想用套接字在网络上传送对象的时候。
  • 当你想通过RMI传输对象的时候。

RMI是Java中的概念:远程方法调用(Remote Method Invocation),能够让在客户端Java虚拟机上的对象像调用本地对象一样调用服务端java虚拟机中的对象上的方法。

 

 

3. 序列化的过程

在没有序列化前,每个保存在堆(Heap)中的对象都有相应的状态(state),即实例变量比如:
 

    public static void main(String[] args) {

        Coder coder = new Coder();
        coder.name = "kuang";
        coder.age = 27;
        coder.lang = "java";
    }

    private static class Coder implements Serializable {

        private static final long serialVersionUID = -7245589157910452589L;

        String name;
        int age;
        String lang;
    }

当通过下面的代码序列化之后,coder对象中的实例变量的值都被保存到coder.ser文件中,这样以后又可以把它从文件中读出来,重新在堆中创建原来的对象。当然保存时候不仅仅是保存对象的实例变量的值,JVM还要保存一些小量信息,比如类的类型等以便恢复原来的对象。

    public static void main(String[] args) {

        Coder coder = new Coder();
        coder.name = "kuang";
        coder.age = 27;
        coder.lang = "java";
        try {
            FileOutputStream fs = new FileOutputStream("./coder.ser");
            ObjectOutputStream os = new ObjectOutputStream(fs);
            os.writeObject(coder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

看看生成的coder.ser文件:

aced 0005 7372 002f 696f 2e6b 7a77 2e61
6476 616e 6365 2e63 7364 6e5f 626c 6f67
2e54 6573 7453 6572 6961 6c69 7a61 626c
6524 436f 6465 729b 727f 0cb6 5366 9302
0003 4900 0361 6765 4c00 046c 616e 6774
0012 4c6a 6176 612f 6c61 6e67 2f53 7472
696e 673b 4c00 046e 616d 6571 007e 0001
7870 0000 001b 7400 046a 6176 6174 0005
6b75 616e 67

以十六进制的方式存储,具体不深入剖析,里面包含了序列化号,类的信息(本身和父类),变量类型,变量值等,和Java的字节码一个道理。

 

 

4. 反序列化过程


        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./coder.ser")));
            Coder coder1 = (Coder) ois.readObject();
            System.out.println("deserialize coder = " + coder1.name + " " + coder1.age + " " + coder1.lang);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

执行输出:

deserialize coder = kuang 27 java

 

 

5. 关于serialVersionUID

序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。

serialVersionUID有两种显示的生成方式:        

  • 一是默认的1L,比如:private static final long serialVersionUID = 1L;        
  • 二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,一般IDE可以帮助你生成。

如果我序列化成功了一个对象,然后更改它的serialVersionUID,反序列化时会报错,比如上面的例子,serialVersionUID改成7245589157910452599:

java.io.InvalidClassException: io.kzw.advance.csdn_blog.TestSerializable$Coder; local class incompatible: stream classdesc serialVersionUID = -7245589157910452599, local class serialVersionUID = -7245589157910452689
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
	at io.kzw.advance.csdn_blog.TestSerializable.main(TestSerializable.java:24)

 

 

6. 序列化后能修改类吗?

修改之后需要重新进行序列化,但是其实是可以的,如果你能接收反序列化的对象拿不到修改的属性值。

private static class Coder implements Serializable {

        private static final long serialVersionUID = -7245589157910452689L;

        String name;
        int age;
        String lang;
        int sex;
    }

上面给Coder类新增了一个sex属性,反序列化后拿不到sex的值,因为之前序列化的对象没有这个属性。

 

 

7. 序列化前和序列化后的对象的关系

是==还是equal? 是浅复制还是深复制?

答案是深复制,反序列化还原后的对象地址与原来的的地址不同。

 

 

8. 静态变量能否序列化?

不能。

    private static class Coder implements Serializable {

        private static final long serialVersionUID = -7245589157910452689L;

        static int years;

        String name;
        int age;
        String lang;
        int sex;
    }

 给上面的Coder类加一个静态属性years。

 

然后序列化:

        Coder coder = new Coder();
        coder.name = "kuang";
        coder.age = 27;
        coder.lang = "java";
        coder.sex = 1;
        Coder.years = 6;
        try {
            FileOutputStream fs = new FileOutputStream("./coder.ser");
            ObjectOutputStream os = new ObjectOutputStream(fs);
            os.writeObject(coder);
        } catch (IOException e) {
            e.printStackTrace();
        }

 

再看看反序列化:

        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./coder.ser")));
            Coder coder1 = (Coder) ois.readObject();
            System.out.println("deserialize coder = " + coder1.name + " " + coder1.age + " " + coder1.lang
            + " " + coder1.sex + " " + coder1.years);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

执行输出:

deserialize coder = kuang 27 java 1 0
 

序列化会忽略静态变量,即序列化不保存静态变量的状态。静态成员属于类级别的,所以不能序列化,即序列化的是对象的状态不是类的状态。这里的不能序列化的意思是序列化信息中不包含这个静态成员域。

 

 

9transient

Java中transient关键字的作用就是让某些被修饰的成员属性变量不被序列化。

private static class Coder implements Serializable {

        private static final long serialVersionUID = -7245589157910452689L;

        static int years;

        transient String name;
        int age;
        String lang;
        int sex;
    }

执行反序列化:

deserialize coder = null 27 java 1 0

比如在android中,不同页面间传递的数据时,支持的类型包括基本类型和String,还有Serializable对象和Parcelable。如果Serializable的某个对象中的某个属性被transient修饰了,那么接收数据的地方时拿不到这个属性的值的。

 

 

10. 对象包含其他类型对象时

    public static void main(String[] args) {

        Coder coder = new Coder();
        coder.name = "kuang";
        coder.age = 27;
        coder.lang = "java";
        coder.sex = 1;
        Coder.years = 6;
        GF gf = new GF();
        gf.name = "unknow";
        coder.gf = gf;
        try {
            FileOutputStream fs = new FileOutputStream("./coder.ser");
            ObjectOutputStream os = new ObjectOutputStream(fs);
            os.writeObject(coder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static class Coder implements Serializable {

        private static final long serialVersionUID = -7245589157910452689L;

        static int years;

        transient String name;
        int age;
        String lang;
        int sex;

        GF gf;
    }

    private static class GF {

        String name;
    }

上面的GF类没有序列化,所以对Coder对象进行序列化时会报错:

java.io.NotSerializableException: io.kzw.advance.csdn_blog.TestSerializable$GF

 

必须让GF类也实现Serializable接口:

    private static class GF implements Serializable {

        private static final long serialVersionUID = -9136411921026672520L;
        
        String name;
    }

所以一个对象要想被成功序列化和反序列化,它包含的自定义类必须也是可序列化的。String类自身实现了Serializable接口。

 

 

11. 单例模式中的readResolve()

单例模式是为了运行时,只创建一个该类的对象。

熟悉单例模式的同学都知道有这么一种写法:

   public static class CoderInstance implements Serializable {

        private static final long serialVersionUID = 19856593275581391L;

        private static class InstanceHolder {
            private static final CoderInstance instatnce = new CoderInstance("kuang", 27);
        }

        public static CoderInstance getInstance() {
            return InstanceHolder.instatnce;
        }

        private String name;

        private int age;

        private CoderInstance() {
            System.out.println("none-arg constructor");
        }

        private CoderInstance(String name, int age) {
            System.out.println("arg constructor");
            this.name = name;
            this.age = age;
        }

        private Object readResolve() throws ObjectStreamException {
            return InstanceHolder.instatnce;
        }
    }

那么为什么要加入readResolve()这个方法呢?

无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。

这样才能保证单例对象的唯一性。

 

 

最后,Serializable是需要走IO的,所以性能可想而知,不过它非常简单方便,正确使用还是能满足需求的。android中的Parcelable也是一个序列化实现,是在内存中做数据传递用的,性能比Serializable高出很多,不过编写维护起来很费劲。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java中的序列化是指将一个对象转换为字节序列的过程,以便于在网络上传输或者将对象保存到磁盘上。在Java中,只有实现了Serializable接口的对象才能被序列化,否则会抛出NotSerializableException异常。序列化可以通过ObjectOutputStream类来实现,反序列化可以通过ObjectInputStream类来实现。序列化在Java中广泛应用于分布式系统、缓存、消息队列等场景。 ### 回答2: Java序列化是指将对象转换为可存储或传输的字节序列的过程,可以使得对象的状态在程序结束后仍能够保存。使用Java的序列化机制能够方便实现Java对象的持久化,并且在数据传输过程中也可以进行对象的存储和传输,从而可以进行跨平台数据交互。 在Java中,如果一个类需要进行序列化,就需要实现Serializable接口。该接口是一个标记接口,不包含任何方法,只是用来表示这个类可以被序列化。当使用ObjectOutputStream来将某个对象转换成字节序列时,如果该对象所属的类实现了Serializable接口,那么该对象就可以被序列化,否则会抛出NotSerializableException异常。 序列化可以实现多种用途,例如: 1. 持久化:将对象序列化后储存在磁盘上,方便下次读取使用。 2. 分布式:在分布式系统中,对象在不同节点间传输时需要进行序列化和反序列化。 3. 缓存:将对象序列化后储存在缓存中,提高读取速度。 在序列化中也需要注意一些问题,例如: 1. 序列化ID:在序列化时,会对原对象进行哈希处理,生成一个序列化ID。反序列化时,会对这个ID进行比对,如果不一致就会抛出InvalidClassException异常。如果需要保持原有的序列化ID,可以通过声明静态变量serialVersionUID进行指定。 2. 敏感信息:在序列化时,需要注意敏感信息的处理,例如密码、证书等信息不应该被序列化。 3. 序列化版本:在序列化时,需要注意版本的处理。如果对象已经发生了变化,例如增删了某些属性或方法,就需要对版本做出相应的改变,否则会出现兼容性问题。 总之,Java的序列化机制为我们提供了一种方便、高效、跨平台的数据交互方式,使用起来相对简单,在实际开发中也非常实用。 ### 回答3: Java的序列化是指将一个对象转换成字节流的过程。当一个对象被序列化之后,它的字节流可以被传输到网络或者存储到本地磁盘上,以便于以后的操作。反序列化则是将字节流转换成对象的过程。 序列化在Java中的实现方式是通过实现Serializable接口来实现的。在Java中,Serializable接口是一个空接口,仅仅是一个标识接口,用来标记对象可以被序列化和反序列化。如果一个类实现了Serializable接口,那么就可以将该类的对象序列化和反序列化。 Java的序列化机制可以使用ObjectOutputStream类和ObjectInputStream类来实现。ObjectOutputStream类用于将对象序列化成字节流,而ObjectInputStream类用于将字节流反序列化成对象。 序列化的主要用途是将对象在网络中传输以及将对象存储到本地磁盘上。在网络中传输对象时,可以将对象序列化成字节流,然后在网络中传输。接收方可以将收到的字节流反序列化成对象,这样就可以在不同的机器上传递对象。在将对象存储到本地磁盘上时,可以将对象序列化成字节流,然后将字节流写入磁盘,以便以后可以读取该对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值