1、什么是序列化?

答:

序列化我的理解是我们看到的数据转化成二进制bytes流向磁盘,网络。并经过磁盘、网络之后另外个时间点/另外一个操作系统进行反序列化,将二进制的bytes流翻译成我们能理解的数据格式;比如:json格式。

上面是大白话的回答:

下面是官方回答:

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。

序列化不仅仅局限于语言,而是网络、操作系统方面的层次。

也可以从生命周期的角度理解:

JVM里面的数据都是随着JVM消失而消失的,如果我们需要数据比JVM的生命周期长,我们就可以为它实现一个序列化接口,让它可以保存到磁盘,或者发送到网络外面。

2、Java的序列化是什么样子?

答:

从第一问可以得知:序列化是对对象流的读写操作。大白话就是怎么将一个对象变成一串流。 Java为我们封装好了使用方法,我们只需要implements Serializable 即可。

如果需要更多的定制化内容,就需要重写 writeObject() 方法。而需要定制化反序列化需要重写 readObject()方法。

对象流在java中就是ObjectOutputStream,下面是一个ObjectOutputStream的使用案例:

@Test
public void test2() {
    String suffix = String.valueOf(Math.random());
    String rootPath = Thread.currentThread().getContextClassLoader().getResource("").toString().substring(6);
    List<File> files = new ArrayList<>(100);

    try {
        for (int i = 8; i < 108; i++) {
            String fileName = Integer.toBinaryString(Objects.hashCode(i));
            List<Comment> comments = new ArrayList<>(20);
            for (int j = 0; j < 20; j++) {
                Comment comment = new Comment();
                comment.setId(j);
                comment.setContent("Love " + fileName);
                comments.add(comment);
            }
            User user = new User();
            user.setId(i);
            user.setName(fileName);
            user.setPassword(Integer.toBinaryString(fileName.hashCode()));
            user.setComments(comments);
            File file = File.createTempFile(fileName, suffix, new File(rootPath));
            try (FileInputStream fis = new FileInputStream(file);
                 FileOutputStream fos = new FileOutputStream(file, true);
                ) {

                ObjectOutputStream oos = new ObjectOutputStream(fos);
                oos.writeObject(user);
                ObjectInputStream ois = new ObjectInputStream(fis);
                User object = (User) ois.readObject();
                System.out.println(object);
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        files.forEach(File::deleteOnExit);
    }
}

private static class Comment  implements Serializable {
    private int id;
    private String content;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Comment{" +
            "id=" + id +
            ", content='" + content + '\'' +
            '}';
    }
}

private static class User implements Serializable {
    private int id;
    private String name;
    private String password;
    private List<Comment> comments;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public List<Comment> getComments() {
        return comments;
    }

    public void setComments(List<Comment> comments) {
        this.comments = comments;
    }

    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", password='" + password + '\'' +
            ", comments=" + comments +
            '}';
    }
}

对象流的使用要点是:使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

3、什么是反序列化?Java的反序列化

答:

反序列化就是序列化的反过程,指的是从磁盘/网络的流(bytes)回到内存,怎么恢复我们认识的样子。

Java的反序列化也是需要实现Serializable的,然后使用输入流,将bytes放入内存。案例可以看第2问的代码。

定制化反序列化需要重写 readObject()方法。

4、序列化/放序列化不会保存/解析静态变量?

答:

是的,序列化会忽略静态变量,这是jdk考虑一般静态变量是共同拥有的不是这个类独有的,所以不会将其序列化,不过使用者可以重写writeObject(),将静态变量写入磁盘/流向网络。

5、序列化 ID (serialVersionUID)为什么有的地方有,有的地方没有?

答:

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)。

serialVersionUID其实是一个对象的身份证,可能在负载的网络交互过程中,存在着两个很类似的对象(类名,字段大多相等),但是两个是不同的对象。这个时候程序就需要使用serialVersionUID去做区分。

如果改动serialVersionUID,就会出现反序列化异常的情况。

比如:

public class SerializableDemo1 {   
    public static void main(String[] args) {
        //Initializes The Object      
       User1 user = new User1();
        user.setName("hollis");
       //Write Obj to File
       ObjectOutputStream oos = null;
       try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));                  oos.writeObject(user);
       } catch (IOException e) {
            e.printStackTrace();
        } finally {
           IOUtils.closeQuietly(oos);
        }
   }
}
class User1 implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    public String getName() {
       return name;
    } 
  public void setName(String name) {
        this.name = name;
    }
 }

修改一下User1 类,把 serialVersionUID 的值改为 2L。

class User1 implements Serializable {
    private static final long serialVersionUID = 2L;

然后执行以下代码,把文件中的对象反序列化出来:

public class SerializableDemo2 {
   public static void main(String[] args) {
       //Read Obj from File
       File file = new File("tempFile");
        ObjectInputStream ois = null;
       try {
            ois = new ObjectInputStream(new FileInputStream(file));
            User1 newUser = (User1) ois.readObject();
            System.out.println(newUser);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(ois);
            try {
               FileUtils.forceDelete(file);
            } catch (IOException e) {
               e.printStackTrace();
            }
       }
    }
 }

最后运行出现如下异常:

java.io.InvalidClassException: serializablation.User1; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

这是因为,在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。

上面说明了serialVersionUID的作用,但是为什么有的地方有,有的地方没有呢?

我们试验一段代码:

去掉上面代码的序列化id

class User1 implements Serializable {
    private String name;
    public String getName() {
       return name;
    } 
  public void setName(String name) {
        this.name = name;
    }
 }

运行保存

加上一个字段,运行加载

class User1 implements Serializable {
    //private static final long serialVersionUID = 2L;
    
    private String name;
    private int 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;
    }
}

运行的结果为:

java.io.InvalidClassException: self.donghao.experiments.serializablation.User1; local class incompatible: stream classdesc serialVersionUID = -3952380580598475573, local class serialVersionUID = 1397259157723020558

从这里可以看出,系统自己添加了一个 serialVersionUID。

强烈建议序列化的对象一定要加上serialVersionUID

可以使用IDEA根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段,比如:
private static final long serialVersionUID = xxxxL;

6、序列化的继承关系

答:

序列化如果需要父类也序列化,就需要父类也implements Serializable

7、使用了序列化框架,还需要序列化吗?

答:

看情况,比如我们使用了fastjson框架,发现序列化/反序列化一切正常,数据也是随意乱改程序也没异常,其实这只是http协议帮我们把数据可视化了。在rmi/dubbo等rpc网络调用中,还是需要序列化的,序列化id。如果是http协议,确实是不需要我们自己implements Serializable ,json框架也会帮我们构建对象的流。所以使用者需要根据自己的情况去使用序列化。

8、Java中transient 关键字可以阻止该变量被序列化到文件中

答:

字段使用transient声明了之后,就不会序列化到文件/网络之中。

在变量声明前加上 transient 关键字,可以阻止该变量被序列化到文件中,在被反序列化后, transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。