0

0

详解java中的transient关键字

angryTom

angryTom

发布时间:2019-11-26 16:14:58

|

2077人浏览过

|

来源于博客园

转载

说实话学了一段时间java的朋友对于transient这个关键字依旧很陌生基本没怎么用过,但是transient关键字在java中却起到了不可或缺的地位!如果要说讲到,我觉得最可能出现的地方是io流中对象流(也叫序列化流)的时候会讲到!

详解java中的transient关键字

相信很多人都是直到自己碰到才会关心这个关键字,记得博主第一次碰到transient关键字是在阅读JDK源码的时候。在学习java的过程中transient关键字少见的原因其实离不开它的作用:transient关键字的主要作用就是让某些被transient关键字修饰的成员属性变量不被序列化。实际上也正是因此,在学习过程中很少用得上序列化操作,一般都是在实际开发中!至于序列化,相信有很多小白童鞋一直迷迷糊糊或者没有具体的概念,这都不是事,下面博主会很清楚的让你记住啥是序列化,保证你这辈子忘不了(貌似有点夸张,有点装b,感觉要被打)

1、何谓序列化?

说起序列化,随之而来的另一个概念就是反序列化,小白童鞋不要慌,记住了序列化就相当于记住了反序列化,因为反序列化就是序列化反过来,所以博主建议只记住序列化概念即可,省的搞晕自己。

(推荐视频:java视频教程)  

立即学习Java免费学习笔记(深入)”;

专业术语定义的序列化:

Java提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。

序列化: 字节 ——> 对象

其实,我总结的就是上面的结论,如果不理解,直接参照专业术语的定义,理解之后就记住我的话就行了,记不住,请打死我

图理解序列化:

1.jpg

2、为何要序列化?

从上一节提到序列化的概念,知道概念之后,我们就必须要知道 为何要序列化了。

讲为何要序列化原因之前,博主我举个栗子:

就像你去街上买菜,一般操作都是用塑料袋给包装起来,直到回家要做菜的时候就把菜给拿出来。而这一系列操作就像极了序列化和反序列化!

Java中对象的序列化指的是将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据和信息,一个序列化后的对象 可以被写到数据库或文件中,也可用于 网络传输,一般当我们使用 缓存cache(内存空间不够有可能会本地存储到硬盘)或 远程调用rpc(网络传输)的时候,经常需要让我们的实体类实现Serializable接口,目的就是为了让其可序列化。

在开发过程中要使用transient关键字修饰的栗子:

如果一个用户有一些密码等信息,为了安全起见,不希望在网络操作中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。

在开发过程中不需要transient关键字修饰的栗子:

1、类中的字段值可以根据其它字段推导出来。

2、看具体业务需求,哪些字段不想被序列化;

不知道各位有木有想过为什么要不被序列化呢?其实主要是为了节省存储空间。优化程序!

PS:记得之前看HashMap源码的时候,发现有个字段是用transient修饰的,我觉得还是有道理的,确实没必要对这个modCount字段进行序列化,因为没有意义,modCount主要用于判断HashMap是否被修改(像put、remove操作的时候,modCount都会自增),对于这种变量,一开始可以为任何值,0当然也是可以(new出来、反序列化出来、或者克隆clone出来的时候都是为0的),没必要持久化其值。

当然,序列化后的最终目的是为了反序列化,恢复成原先的Java对象,要不然序列化后干嘛呢,就像买菜一样,用塑料袋包裹最后还是为了方便安全到家再去掉塑料袋,所以序列化后的字节序列都是可以恢复成Java对象的,这个过程就是反序列化。

3、序列化与transient的使用

1、需要做序列化的对象的类,必须实现序列化接口:Java.lang.Serializable 接口(一个标志接口,没有任何抽象方法),Java 中大多数类都实现了该接口,比如:String,Integer类等,不实现此接口的类将不会使任何状态序列化或反序列化,会抛NotSerializableException异常 。

2、底层会判断,如果当前对象是 Serializable 的实例,才允许做序列化,Java对象 instanceof Serializable 来判断。

3、在 Java 中使用对象流ObjectOutputStream来完成序列化以及ObjectInputStream流反序列化   

==ObjectOutputStream:通过 writeObject()方法做序列化操作== 

==ObjectInputStream:通过 readObject() 方法做反序列化操作==

4、该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。

2.jpg

由于字节嘛所以肯定要涉及流的操作,也就是对象流也叫序列化流ObjectOutputstream,下面进行多种情况分析序列化的操作代码!

在这里,我真的强烈建议看宜春博客的读者朋友,请试着去敲,切记一眼带过或者复制过去运行就完事了,特别是小白童鞋,相信我!你一定会有不一样的收获。!

衣购网站项目(三层开发)源码
衣购网站项目(三层开发)源码

商品查询功能提供了一个快速查看商品的途径。商品查询分为基本查询和高级查询。基本查询:提供关键字和商品大类两种条件的查询,用户可以只填写关键字或者选择商品大类或者关键字和商品大类都填写来查询商品。高级查询:提供关键字,商品大类,商品小类,商品价格范围四种条件的查询,用户可以任意填写其中一种或几种的查询条件来查询想要了解的商品信息。商品查询功能大大的方便了用户,提高了网站的用户体验。(5)帮助系统模块

下载

3.1、没有实现Serializable接口进行序列化情况

package TransientTest;
import java.io.*;

class UserInfo { //================================注意这里没有实现Serializable接口
    private String name;
    private transient String password;

    public UserInfo(String name, String psw) {
        this.name = name;
        this.password = psw;
    }

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

public class TransientDemo {
    public static void main(String[] args) {

        UserInfo userInfo = new UserInfo("老王", "123");
        System.out.println("序列化之前信息:" + userInfo);

        try {
            ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("userinfo.txt"));
            output.writeObject(new UserInfo("老王", "123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果

3.jpg

3.2、实现Serializable接口序列化情况

当我们加上实现Serializable接口再运行会发现,项目中出现的userinfo.txt文件内容是这样的:

4.jpg

其实这都不是重点,重点是序列化操作成功了!

3.3、普通序列化情况

package TransientTest;
import java.io.*;

class UserInfo implements Serializable { //第一步实现Serializable接口
    private String name;
    private String password; //都是普通属性==============================

    public UserInfo(String name, String psw) {
        this.name = name;
        this.password = psw;
    }

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

public class TransientDemo {
    public static void main(String[] args) throws ClassNotFoundException {

        UserInfo userInfo = new UserInfo("程序员老王", "123");
        System.out.println("序列化之前信息:" + userInfo);

        try {
            ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步开始序列化操作
            output.writeObject(new UserInfo("程序员老王", "123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream input = new ObjectInputStream(new FileInputStream("userinfo.txt")); //第三步开始反序列化操作
            Object o = input.readObject(); //ObjectInputStream的readObject方法会抛出ClassNotFoundException
            System.out.println("序列化之后信息:" + o);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

序列化之前信息:UserInfo{name='程序员老王', password='123'}
序列化之后信息:UserInfo{name='程序员老王', password='123'}

3.4、transient序列化情况

package TransientTest;
import java.io.*;

class UserInfo implements Serializable { //第一步实现Serializable接口
    private String name;
    private transient String password; //特别注意:属性由transient关键字修饰===========

    public UserInfo(String name, String psw) {
        this.name = name;
        this.password = psw;
    }

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

public class TransientDemo {
    public static void main(String[] args) throws ClassNotFoundException {

        UserInfo userInfo = new UserInfo("程序员老王", "123");
        System.out.println("序列化之前信息:" + userInfo);

        try {
            ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步开始序列化操作
            output.writeObject(new UserInfo("程序员老王", "123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream input = new ObjectInputStream(new FileInputStream("userinfo.txt")); //第三步开始反序列化操作
            Object o = input.readObject(); //ObjectInputStream的readObject方法会抛出ClassNotFoundException
            System.out.println("序列化之后信息:" + o);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

序列化之前信息:UserInfo{name='程序员老王', password='123'}
序列化之后信息:UserInfo{name='程序员老王', password='null'}

特别注意结果,添加transient修饰的属性值为默认值null!如果被transient修饰的属性为int类型,那它被序列化之后值一定是0,当然各位可以去试试,这能说明什么呢?说明被标记为transient的属性在对象被序列化的时候不会被保存(或者说变量不会持久化)

3.5、static序列化情况

package TransientTest;
import java.io.*;

class UserInfo implements Serializable { //第一步实现Serializable接口
    private String name;
    private static String password; //特别注意:属性由static关键字修饰==============

    public UserInfo(String name, String psw) {
        this.name = name;
        this.password = psw;
    }

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

public class TransientDemo {
    public static void main(String[] args) throws ClassNotFoundException {

        UserInfo userInfo = new UserInfo("程序员老王", "123");
        System.out.println("序列化之前信息:" + userInfo);

        try {
            ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("userinfo.txt")); //第二步开始序列化操作
            output.writeObject(new UserInfo("程序员老王", "123"));
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream input = new ObjectInputStream(new FileInputStream("userinfo.txt")); //第三步开始反序列化操作
            Object o = input.readObject(); //ObjectInputStream的readObject方法会抛出ClassNotFoundException
            System.out.println("序列化之后信息:" + o);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

序列化之前信息:UserInfo{name='程序员老王', password='123'}
序列化之后信息:UserInfo{name='程序员老王', password='123'}

这个时候,你就会错误的认为static修饰的也被序列化了,其实不然,实际上这里很容易被搞晕!明明取出null(默认值)就可以说明不会被序列化,这里明明没有变成默认值,为何还要说static不会被序列化呢?

实际上,反序列化后类中static型变量name的值实际上是当前JVM中对应static变量的值,这个值是JVM中的并不是反序列化得出的。也就是说被static修饰的变量并没有参与序列化!但是咱也不能口说无凭啊,是的,那我们就来看两个程序对比一下就明白了!

第一个程序:这是一个没有被static修饰的name属性程序:

package Thread;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class UserInfo implements Serializable {
    private String name;
    private transient String psw;

    public UserInfo(String name, String psw) {
        this.name = name;
        this.psw = psw;
    }

    public String getName() {
        return name;
    }

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

    public String getPsw() {
        return psw;
    }

    public void setPsw(String psw) {
        this.psw = psw;
    }

    public String toString() {
        return "name=" + name + ", psw=" + psw;
    }
}
public class TestTransient {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo("程序员老过", "456");
        System.out.println(userInfo);
        try {
            // 序列化,被设置为transient的属性没有被序列化
            ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt"));
            o.writeObject(userInfo);
            o.close();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        try {
            //在反序列化之前改变name的值 =================================注意这里的代码
            userInfo.setName("程序员老改");
            // 重新读取内容
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt"));
            UserInfo readUserInfo = (UserInfo) in .readObject();
            //读取后psw的内容为null
            System.out.println(readUserInfo.toString());
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}

运行结果:

name=程序员老过, psw=456name=程序员老过, psw=null

从程序运行结果中可以看出,在反序列化之前试着改变name的值为程序员老改,结果是没有成功的!

第二个程序:这是一个被static修饰的name属性程序:

package Thread;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class UserInfo implements Serializable {
    private static final long serialVersionUID = 996890129747019948 L;
    private static String name;
    private transient String psw;

    public UserInfo(String name, String psw) {
        this.name = name;
        this.psw = psw;
    }

    public String getName() {
        return name;
    }

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

    public String getPsw() {
        return psw;
    }

    public void setPsw(String psw) {
        this.psw = psw;
    }

    public String toString() {
        return "name=" + name + ", psw=" + psw;
    }
}
public class TestTransient {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo("程序员老过", "456");
        System.out.println(userInfo);
        try {
            // 序列化,被设置为transient的属性没有被序列化
            ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt"));
            o.writeObject(userInfo);
            o.close();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        try {
            //在反序列化之前改变name的值
            userInfo.setName("程序员老改");
            // 重新读取内容
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt"));
            UserInfo readUserInfo = (UserInfo) in .readObject();
            //读取后psw的内容为null
            System.out.println(readUserInfo.toString());
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}

运行结果:

name=程序员老过, psw=456name=程序员老改, psw=null

从程序运行结果中可以看出,在反序列化之前试着改变name的值为程序员老改,结果是成功的!现在对比一下两个程序是不是就很清晰了?

static关键字修饰的成员属性优于非静态成员属性加载到内存中,同时静态也优于对象进入到内存中,被static修饰的成员变量不能被序列化,序列化的都是对象,静态变量不是对象状态的一部分,因此它不参与序列化。所以将静态变量声明为transient变量是没有用处的。因此,反序列化后类中static型变量name的值实际上是当前JVM中对应static变量的值,这个值是JVM中的并不是反序列化得出的。

3.6、final序列化情况

对于final关键字来讲,final变量将直接通过值参与序列化,至于代码程序我就不再贴出来了,大家可以试着用final修饰验证一下!

主要注意的是final 和transient可以同时修饰同一个变量,结果也是一样的,对transient没有影响,这里主要提一下,希望各位以后在开发中遇到这些情况不会满头雾水!

4、java类中serialVersionUID作用

既然提到了transient关键字就不得不提到序列化,既然提到了序列化,就不得不提到serialVersionUID了,它是啥呢?基本上有序列化就会存在这个serialVersionUID。

5.jpg

serialVersionUID适用于Java的序列化机制。简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException,在开发中有时候可写可不写,建议最好还是写上比较好。

5、transient关键字小结

1、变量被transient修饰,变量将不会被序列化
2、transient关键字只能修饰变量,而不能修饰方法和类。
3、被static关键字修饰的变量不参与序列化,一个静态static变量不管是否被transient修饰,均不能被序列化。
4、final变量值参与序列化,final transient同时修饰变量,final不会影响transient,一样不会参与序列化

第二点需要注意的是:本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口

第三点需要注意的是:反序列化后类中static型变量的值实际上是当前JVM中对应static变量的值,这个值是JVM中的并不是反序列化得出的。

结语:被transient关键字修饰导致不被序列化,其优点是可以节省存储空间。优化程序!随之而来的是会导致被transient修饰的字段会重新计算,初始化!

本文来自php中文网,java教程栏目,欢迎学习!  

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Golang gRPC 服务开发与Protobuf实战
Golang gRPC 服务开发与Protobuf实战

本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

0

2026.01.15

公务员递补名单公布时间 公务员递补要求
公务员递补名单公布时间 公务员递补要求

公务员递补名单公布时间不固定,通常在面试前,由招录单位(如国家知识产权局、海关等)发布,依据是原入围考生放弃资格,会按笔试成绩从高到低递补,递补考生需按公告要求限时确认并提交材料,及时参加面试/体检等后续环节。要求核心是按招录单位公告及时响应、提交材料(确认书、资格复审材料)并准时参加面试。

2

2026.01.15

公务员调剂条件 2026调剂公告时间
公务员调剂条件 2026调剂公告时间

(一)符合拟调剂职位所要求的资格条件。 (二)公共科目笔试成绩同时达到拟调剂职位和原报考职位的合格分数线,且考试类别相同。 拟调剂职位设置了专业科目笔试条件的,专业科目笔试成绩还须同时达到合格分数线,且考试类别相同。 (三)未进入原报考职位面试人员名单。

10

2026.01.15

国考成绩查询入口 国考分数公布时间2026
国考成绩查询入口 国考分数公布时间2026

笔试成绩查询入口已开通,考生可登录国家公务员局中央机关及其直属机构2026年度考试录用公务员专题网站http://bm.scs.gov.cn/pp/gkweb/core/web/ui/business/examResult/written_result.html,查询笔试成绩和合格分数线,点击“笔试成绩查询”按钮,凭借身份证及准考证进行查询。

2

2026.01.15

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

63

2026.01.14

php与html混编教程大全
php与html混编教程大全

本专题整合了php和html混编相关教程,阅读专题下面的文章了解更多详细内容。

32

2026.01.13

PHP 高性能
PHP 高性能

本专题整合了PHP高性能相关教程大全,阅读专题下面的文章了解更多详细内容。

73

2026.01.13

MySQL数据库报错常见问题及解决方法大全
MySQL数据库报错常见问题及解决方法大全

本专题整合了MySQL数据库报错常见问题及解决方法,阅读专题下面的文章了解更多详细内容。

20

2026.01.13

PHP 文件上传
PHP 文件上传

本专题整合了PHP实现文件上传相关教程,阅读专题下面的文章了解更多详细内容。

25

2026.01.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.5万人学习

C# 教程
C# 教程

共94课时 | 6.7万人学习

Java 教程
Java 教程

共578课时 | 46.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号