前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java transient关键字_浅谈Java序列化机制

java transient关键字_浅谈Java序列化机制

作者头像
用户7886150
修改2020-12-14 15:17:20
3890
修改2020-12-14 15:17:20
举报
文章被收录于专栏:bit哲学院bit哲学院

参考链接: Java中的transient关键字

浅谈Java序列化机制

 一、序列化、反序列化概念及其使用场景

 1、序列化、反序列化的概念

 简单的讲,序列化就是将java对象转化成二进制保存到磁盘中去,反序列化就是从磁盘中读取文件流然后转成java对象。

 2、使用场景

 1、网络通讯传输java对象数据2、永久保存java对象

 二、实现序列化的方式有哪些?

 JDK提供了下面两种方式实现序列化:

 1、实现Serializable接口2、实现Externalizable

 下面分别实例演示两种实现方式: 假设本文所有的序列化对象为User,其拥有下面属性:

 /** * 序列化對象 *  * @Author jiawei huang * @Since 2020年1月2日 * @Version 1.0 */public class User {        private String userName;        private String address;    // ....setter/getter}复制代码

 1、基于Serializable接口

 我们在上面User对象的基础上实现Serializable接口,代码如下:

 public class User implements Serializable {    // 序列化ID    private static final long serialVersionUID = 1L;复制代码

 序列化和反序列化代码为:

 // 序列化方法public static void serialize(User user) {    ObjectOutputStream outputStream = null;    try {    outputStream = new ObjectOutputStream(new FileOutputStream("C:甥敳獲AdministratorDesktopdata.txt"));    outputStream.writeObject(user);    } catch (Exception e) {    e.printStackTrace();    } finally {        // close stream        if (outputStream != null) {        try {        outputStream.close();        } catch (IOException e) {        e.printStackTrace();        }        }    }}// 反序列化方法public static void deserialize() {    ObjectInputStream objectInputStream = null;    try {    objectInputStream = new ObjectInputStream(    new FileInputStream("C:甥敳獲AdministratorDesktopdata.txt"));    User user = (User) objectInputStream.readObject();    System.out.println(user.getAddress());    System.out.println(user.getUserName());    } catch (Exception e) {    // error    e.printStackTrace();    } finally {    // close stream    if (objectInputStream != null) {    try {    objectInputStream.close();    } catch (IOException e) {    // error    e.printStackTrace();    }    }    }}复制代码

 测试代码如下:

 public static void main(String[] args) {    User user = new User();    user.setAddress("广东深圳");    user.setUserName("hjw");        serialize(user);    deserialize();}复制代码

 输出如下:

 广东深圳hjw复制代码

 Q1、User实现Serializable接口是必须的吗?

 是的,是必须的,否则报下面异常:

 java.io.NotSerializableException: ex.serializable.User    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)    at ex.serializable.Main.serialize(Main.java:41)    at ex.serializable.Main.main(Main.java:33)复制代码

 因为在ObjectOutputStream中执行了如下代码限制:

 这也说明,jdk并没有默认支持对象的序列化,为什么默认不支持呢?因为java的安全机制限制,我们设想一下,假设对象都默认支持序列化,那么就像上面那个User对象,其私有private修饰属性也被序列化了,那么不符合private的设计语义

 2、基于Externalizable接口

 1、Externalizable接口继承自Serializable接口2、Externalizable接口允许我们自定义对象属性的序列化3、实现Externalizable接口必须重写writeExternal和readExternal方法

 我们重新新建一个User1对象如下:

 public class User1 implements Externalizable {private String userName;private String address;    // setter、getter@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(userName);}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {System.out.println(in.readObject());}}复制代码

 测试代码如下:

 User1 user1 = new User1();user1.setAddress("广东深圳");user1.setUserName("hjw");user1.writeExternal(new ObjectOutputStream(new FileOutputStream("C:甥敳獲AdministratorDesktopdata.txt")));user1.readExternal(new ObjectInputStream(new FileInputStream("C:甥敳獲AdministratorDesktopdata.txt")));复制代码

 输出如下:

 hjw复制代码

 两种实现方式的区别在于Externalizable允许我们自定义序列化规则,Externalizable接口会初始化一次无参构造器,而Serializable不会

 三、transient关键字和static成员变量

 通过实现Externalizable接口我们可以自定义序列化的属性,同样的道理,关键字transient也可以达到相同的效果,但是两者之间还是有一些区别的。

 1、transient修饰的变量,即使使用private修饰也会被序列化2、如果我们想private属性不被序列化,则可以使用Externalizable

 static修饰的成员变量属于类全局属性,其值在JDK1.8存储于元数据区(1.8以前叫方法区),不属于对象范围,将不会被序列化。

 下面验证static不会被序列化: 修改User对象userName属性为static修饰

 User user = new User();user.setAddress("广东深圳");user.setUserName("hjw");serialize(user);user.setUserName("mike");deserialize();复制代码

 输出如下:

 广东深圳mike复制代码

 我们将hjw序列化到了磁盘文件,结果反序列化之后得到的值却是mike

 四、关于serialVersionUID

 java能否反序列化成功,取决于serialVersionUID是否一致,我们可以把它理解成为版本号,一旦版本号不一致,将会报序列化出错。我们可以为对象声明一个自定义serialVersionUID,也可以使用默认的1L。

 总结就是:

 当我们新增一个类实现Serializable接口时,建议我们为其新增一个serialVersionUID,因为假设我们没有声明serialVersionUID,那么后面假设我们修改了该类的接口(新增字段)时,当我们再次反序列化时,就会报错。因为java会拿编译器根据类信息自动生成一个id1和反序列化得到的id2进行比较,如果类有改动,id2和id1肯定不一致啦。

 Q1:既然static修饰的不会被序列化,而java又是如何通过serialVersionUID进行比较的呢?

 serialVersionUID应该是一个特殊字段可以被序列化,应该可以从源码中找到答案,小编没找到,欢迎评论区留言。

 五、序列化、反序列化实现深度克隆

 深度克隆即把引用类型的成员也给克隆了,基于上面例子,我们新增一个类Car,并且User拥有一个Car类型对象。

 public class Car implements Serializable {private static final long serialVersionUID = 1L;private String carName;private int price;    // ......    }复制代码

 public class User implements Serializable {private static final long serialVersionUID = 1L;private String userName;//private Car car;private String address;    @Override    public String toString() {        return "User [userName=" + userName + ", carName=[" + car.getCarName() + "],price=[" + car.getPrice() + "]"        + ", address=" + address + "]";    }    // ......}复制代码

 克隆方法

 public static  T cloneObject(T obj) throws IOException {    ObjectOutputStream oos = null;    ByteArrayOutputStream baos = null;    byte[] bytes = null;    try {    // 序列化    baos = new ByteArrayOutputStream();    oos = new ObjectOutputStream(baos);    oos.writeObject(obj);    bytes = baos.toByteArray();    } catch (Exception e) {    e.printStackTrace();    } finally {    if (baos != null) {    baos.close();    }    if (oos != null) {    oos.close();    }    }        ByteArrayInputStream bais = null;    ObjectInputStream ois = null;    try {    // 反序列化    bais = new ByteArrayInputStream(bytes);    ois = new ObjectInputStream(bais);    return (T) ois.readObject();    } catch (Exception e) {    e.printStackTrace();    } finally {    if (bais != null) {    baos.close();    }    if (oos != null) {    ois.close();    }    }    return null;}复制代码

 测试代码及输出如下:

 User user = new User();user.setAddress("广东深圳");user.setUserName("hjw");Car car = new Car();car.setCarName("单车");car.setPrice(300);user.setCar(car);User clonedUser = cloneObject(user);System.out.println(clonedUser);复制代码

 User [userName=hjw, carName=[单车],price=[300], address=广东深圳]复制代码

 六、总结

 1、新增序列化类时,建议新增一个自定义id,防止后续版本之间不兼容2、static、transient修饰的字段不会被序列化,序列化同样会序列化private属性3、序列化可以实现深度克隆4、实现序列化有两种方式,一种是直接实现Serializable接口,另一种是实现Externalizable接口,后者允许我们自定义序列化规则

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档