搜索
首页JavaJava基础详解java中的transient关键字
详解java中的transient关键字Nov 26, 2019 pm 04:14 PM
javatransient

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

详解java中的transient关键字

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

1、何谓序列化?

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

(推荐视频: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,下面进行多种情况分析序列化的操作代码!

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

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中的transient关键字的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:博客园。如有侵权,请联系admin@php.cn删除
带你搞懂Java结构化数据处理开源库SPL带你搞懂Java结构化数据处理开源库SPLMay 24, 2022 pm 01:34 PM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于结构化数据处理开源库SPL的相关问题,下面就一起来看一下java下理想的结构化数据处理类库,希望对大家有帮助。

Java集合框架之PriorityQueue优先级队列Java集合框架之PriorityQueue优先级队列Jun 09, 2022 am 11:47 AM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于PriorityQueue优先级队列的相关知识,Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,下面一起来看一下,希望对大家有帮助。

完全掌握Java锁(图文解析)完全掌握Java锁(图文解析)Jun 14, 2022 am 11:47 AM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于java锁的相关问题,包括了独占锁、悲观锁、乐观锁、共享锁等等内容,下面一起来看一下,希望对大家有帮助。

一起聊聊Java多线程之线程安全问题一起聊聊Java多线程之线程安全问题Apr 21, 2022 pm 06:17 PM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于多线程的相关问题,包括了线程安装、线程加锁与线程不安全的原因、线程安全的标准类等等内容,希望对大家有帮助。

Java基础归纳之枚举Java基础归纳之枚举May 26, 2022 am 11:50 AM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于枚举的相关问题,包括了枚举的基本操作、集合类对枚举的支持等等内容,下面一起来看一下,希望对大家有帮助。

详细解析Java的this和super关键字详细解析Java的this和super关键字Apr 30, 2022 am 09:00 AM

本篇文章给大家带来了关于Java的相关知识,其中主要介绍了关于关键字中this和super的相关问题,以及他们的一些区别,下面一起来看一下,希望对大家有帮助。

Java数据结构之AVL树详解Java数据结构之AVL树详解Jun 01, 2022 am 11:39 AM

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于平衡二叉树(AVL树)的相关知识,AVL树本质上是带了平衡功能的二叉查找树,下面一起来看一下,希望对大家有帮助。

一文掌握Java8新特性Stream流的概念和使用一文掌握Java8新特性Stream流的概念和使用Jun 23, 2022 pm 12:03 PM

本篇文章给大家带来了关于Java的相关知识,其中主要整理了Stream流的概念和使用的相关问题,包括了Stream流的概念、Stream流的获取、Stream流的常用方法等等内容,下面一起来看一下,希望对大家有帮助。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能