为了账号安全,请及时绑定邮箱和手机立即绑定

Java语言基础----------transient关键字详解

标签:
Java
背景

序列化和反序列化是程序设计中经常遇到的场景。在Java中,让一个类支持序列化和反序列化,存在两种方式。第一,实现Serializable接口。第二,实现Externalizable接口。两个接口之间存在紧密的联系,ExternalizableSerializable的子接口。
图片描述

两者之间的区别就是使用方法上的差异。

对于Serializable接口,使用的时候是将对象的所有属性序列化(静态变量除外),另外可以使用transient关键字控制不参与序列化的变量。

对于Externalizable接口,默认不序列化变量。如果要进行序列化,则必须在writeExternalreadExternal方法中手动的进行指定。

Externalizable接口中,一旦在writeExternalreadExternal方法中指定,那么即便是静态变量,也同样会被序列化。

作用

在使用Serializable接口实现类的序列化时,就引出了transient关键字的作用了。在一些场景下,用户希望对象中的某些字段不被序列化(比如用户信息中的敏感属性),那么可以使用该关键字,最终的效果就是被该关键字修饰的变量不能被序列化。所以可以把transient关键字理解为序列化黑名单属性标记。

使用

通过代码详细了解一下transient关键字的使用。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Test
{
    public static void main(String[] args) throws IOException, ClassNotFoundException
    {
        Person person = new Person("zhangsir", 18);
        System.out.println(person);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d://test.txt"));
        oos.writeObject(person);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d://test.txt"));
        Person person1 = (Person)ois.readObject();
        System.out.println(person1);
    }
}
class Person implements Serializable
{
    private String name = null;
    private transient int age = 0;

    Person(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 "name is " + getName() + " and age is " + getAge();
    }
}

运行结果如下:

name is zhangsir and age is 18
name is zhangsir and age is 0
Process finished with exit code 0

可见,从源文件读取数据反序列化之后生成的对象,age的值变成了默认的0,说明并没有对age属性进行序列化和反序列化。

总结

1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,

拓展
1)通过Externalizable接口实现类的序列化代码如下:
import java.io.*;

public class Test
{
    public static void main(String[] args) throws IOException, ClassNotFoundException
    {
        Person person = new Person("zhangsir", 18);
        System.out.println(person);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d://test.txt"));
        oos.writeObject(person);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d://test.txt"));
        Person person1 = (Person) ois.readObject();
        System.out.println(person1);
    }
}
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Person implements Externalizable
{
    private String name = null;
    private transient int age = 0;

    // 默认的构造函数必须有,而且可见性为public,反序列化会用到。
    // 否则会报java.io.InvalidClassException: Person; no valid constructor错误
    public Person(){}

    Person(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 "name is " + getName() + " and age is " + getAge();
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.write(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String)in.readObject();
        age = in.read();
    }
}

运行结果如下:

name is zhangsir and age is 18
name is zhangsir and age is 18

可见,使用Externalizable接口可以实现对静态变量的序列化。

2)序列化类中serialVersionUID的作用

一般在支持序列化的类中,都会设置一个serialVersionUID(序列化版本号)。这个变量用于在反序列化过程控制是否支持反序列化重构,即是否允许两个不同类的对象进行转化。如果两个类的serialVersionUID不同,那么在反序列化过程中就会报错失败。这个字段的设置,一般有三种情况,分别是默认不指定,手动指定为1L(或者其他固定值),自动生成64位hash值。

  1. 如果不在类中显示指定,那么JVM会在编译的时候根据类的名称,属性和方法等自动分配一个默认值。如此每个类的serialVersionUID都是不同的。此种情况下,也就不允许反序列化重构,即一个类对象的序列化结果不允许反序列化成其他类的对象。
  2. 如果手动在类中指定该字段的值为1L(或者其他固定值),那么就会出现很多类的serialVersionUID字段是相同的。这种情况,一般是不会有问题的,因为该字段只是控制了多个类是否可以反序列化重构。实际代码中具体反序列化成哪个类,还是依靠编码人员根据自己的逻辑去进行指定的。
  3. 该字段也可以使用ide来生成,这种生成的方式与第一种情况相同,所以都是不同的。但是编码人员可以根据业务逻辑去修改该字段。如果需要多个类支持反序列化重构,则修改成相同的serialVersionUID字段就可以了。

所以,从严谨程度上来说,是3>2>1,但是这三种情况,都是允许的。

参考资料

http://www.cnblogs.com/lanxuezaipiao/p/3369962.html

http://blog.csdn.net/gao_chun/article/details/42643523

点击查看更多内容
5人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
1万
获赞与收藏
1426

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消