我的编程空间,编程开发者的网络收藏夹
学习永远不晚

深入学习java之transient关键字

短信预约 信息系统项目管理师 报名、考试、查分时间动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

深入学习java之transient关键字

对于transient这个关键字或许很陌生基本没怎么用过,但是transient关键字在java中却起到了不可或缺的地位!

在学习java的过程中transient关键字少见的原因其实离不开它的作用:transient关键字的主要作用就是让某些被transient关键字修饰的成员属性变量不被序列化。实际上也正是因此,在学习过程中很少用得上序列化操作,一般都是在实际开发中!至于序列化,相信有很多小白童鞋一直迷迷糊糊或者没有具体的概念,下面本篇文章就介绍一下。

1、何谓序列化?

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

专业术语定义的序列化:

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

宜春的术语定义序列化:

序列化: 字节 ——> 对象

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

图理解序列化:
在这里插入图片描述
啥?你不懂啥是字节?其实,我在一篇IO流的文章里就已经介绍了序列化,放心,绝对特别详细~光看文章名字就知道了~

史上最骚最全最详细的IO流教程,小白都能看懂!

2、为何要序列化?

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

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

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

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

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

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

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

类中的字段值可以根据其它字段推导出来。
2、看具体业务需求,哪些字段不想被序列化;

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

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

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

3、序列化与transient的使用

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

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

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

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

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

该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
在这里插入图片描述
由于字节嘛所以肯定要涉及流的操作,也就是对象流也叫序列化流ObjectOutputstream,下面进行多种情况分析序列化的操作代码!

在这里,我真的强烈建议看宜春博客的读者朋友,请试着去敲,切记一眼带过或者复制过去运行就完事了,特别是小白童鞋,相信我!你一定会有不一样的收获。千万不要觉得浪费时间,有时候慢就是快,宜春亲身体会!

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.2、实现Serializable接口序列化情况

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

在这里插入图片描述

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

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=456
name=程序员老过, 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 = 996890129747019948L;
    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=456
name=程序员老改, psw=null

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

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

如果对static关键字还是不太清楚理解的童鞋可以参考这篇文章,应该算是不错的:深入理解static关键字

3.6、final序列化情况

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

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

4、java类中serialVersionUID作用

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

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

5、transient关键字小结

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

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

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

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

本文来自 java入门 栏目,欢迎学习!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

深入学习java之transient关键字

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

深入学习java之transient关键字

对于transient这个关键字或许很陌生基本没怎么用过,但是transient关键字在java中却起到了不可或缺的地位!在学习java的过程中transient关键字少见的原因其实离不开它的作用:transient关键字的主要作用就是让某些被transien
深入学习java之transient关键字
2021-04-10

java学习之路——final关键字详解

一、概述学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。那么我们能不能随意的继承API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了final用于修饰不可改变内容。final: 不可改变。可以用
java学习之路——final关键字详解
2021-08-02

深入学习java之泛型

概念:泛型,即“参数化类型”。就是将原来具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。本质:为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体
深入学习java之泛型
2017-12-30

如何学习Java volatile关键字

今天就跟大家聊聊有关如何学习Java volatile关键字,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。相信大多数Java程序员都学习过volatile这个关键字的用法。百度百科上
2023-06-02

深入浅析Java中的 static关键字

本篇文章为大家展示了深入浅析Java中的 static关键字,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、 static代表着什么 在Java中并不存在全局变量的概念,但是我们可以通过
2023-05-31

java深入学习之static静态代码块

静态代码块static可以修饰属性、方法、内部类、代码块。静态代码块在类加载时就调用, 而且只执行一次, 并且优先于各种代码块以及构造函数。有一个地方的静态代码被使用,那么所有的静态代码块都会被调用。相关视频教程推荐:java免费视频教程示例如下:父类publ
java深入学习之static静态代码块
2015-02-26

深入浅析Java中的this、final关键字

这篇文章给大家介绍深入浅析Java中的this、final关键字,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。this关键字this引用对象自身。它也可以在构造方法内部用于调用同一个类的其他构造方法。 隐藏的静态变量可
2023-05-31

深入理解Java中的final关键字_动力节点Java学院整理

Java中的final关键字非常重要,它可以应用于类、方法以及变量。这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使用final关键字的实例。final经常
2023-05-31

C语言学习之关键字的示例详解

关键字,这名字一听,就很关键。而有些关键字,你可能不是很了解,更别谈使用。所以,这篇文章将带你见识常见的关键字,一起领略它们的风采吧
2022-11-13

深入探讨:Go语言学习中哪些类库是关键?

深入探讨:Go语言学习中哪些类库是关键?Go语言作为一门高效、简洁的编程语言,越来越受到开发者的青睐。在学习Go语言的过程中,掌握一些关键的类库对于提升开发效率和编写高质量代码至关重要。本文将深入探讨在Go语言学习中哪些类库是关键,并附有
深入探讨:Go语言学习中哪些类库是关键?
2024-03-01

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录