前言
说实话学了一段时间java的朋友对于transient这个关键字依旧很陌生基本没怎么用过,但是transient关键字在java中却起到了不可或缺的地位!如果要说讲到,我觉得最可能出现的地方是IO流中对象流(也叫序列化流)的时候会讲到!
在学习java的过程中transient关键字少见的原因其实离不开它的作用:transient关键字的主要作用就是让某些被transient关键字修饰的成员属性变量不被序列化。实际上也正是因此,在学习过程中很少用得上序列化操作,一般都是在实际开发中!
Java中的关键字如下表格:
关键字列表
访问控制 |
private |
protected |
public |
|
|
|
|
|
|
类方法变量修饰符 |
abstract |
class |
extends |
final |
implements |
interface |
native |
new |
static |
strictfp |
synchronized |
transient |
volatile |
|
|
|
|
|
程序控制 |
break |
continue |
return |
do |
while |
if |
else |
for |
instanceof |
switch |
case |
default |
|
|
|
|
|
|
错误处理 |
try |
catch |
throw |
throws |
|
|
|
|
|
包相关 |
import |
package |
|
|
|
|
|
|
|
基本类型 |
boolean |
byte |
char |
double |
float |
int |
long |
short |
|
TRUE |
FALSE |
|
|
|
|
|
|
|
变量引用 |
super |
this |
void |
|
|
|
|
|
|
保留字 |
goto |
const |
|
|
|
|
|
|
|
注:表中true、false严格来讲不是关键字
每个关键字的含义:
关键字含义
private |
一种访问控制方式:私有模式 |
protected |
一种访问控制方式:保护模式 |
public |
一种访问控制方式:共用模式 |
abstract |
表明类或者成员方法具有抽象属性 |
class |
类 |
extends |
(继承)表明一个类型是另一个类型的子类型,这里常见的类型有类和接口 |
final |
用来说明最终属性,表明一个类不能派生出子类,或者成员方法不能被覆盖,或者成员域的值不能被改变 |
implements |
表明一个类实现了给定的接口 |
interface |
接口 |
native |
用来声明一个方法是由与计算机相关的语言(如C/C++/FORTRAN语言)实现的 |
new |
用来创建新实例对象 |
static |
表示具有静态属性 |
strictfp |
用来声明FP_strict(单精度或双精度浮点数)表达式遵循IEEE 754算术规范 |
synchronized |
表明一段代码需要同步执行 |
transient |
声明不用序列化的成员域 |
volatile |
表明两个或者多个变量必须同步地发生变化 |
break |
提前跳出一个块 |
continue |
回到一个块的开始处 |
return |
从成员方法中返回数据 |
while |
用在循环结构中 |
if |
条件语句的引导词 |
else |
用在条件语句中,表明当条件不成立时的分支 |
for |
一种循环结构的引导词 |
instanceof |
用来测试一个对象是否是指定类型的实例对象 |
switch |
分支结构语句的引导词 |
case |
用在switch语句之中,表示其中的一个分支 |
default |
默认,例如,用在switch语句中,表明一个默认的分支,可写可不写,但为了保证代码的规范性和健壮性,建议加上,更易看懂代码。 |
try |
尝试一个可能抛出异常的程序块 |
catch |
用来异常处理时,捕捉异常 |
throw |
抛出一个异常 |
throws |
声明在当前定义的成员方法中所有需要抛出的异常 |
import |
表明要访问指定的类或包 |
package |
包 |
boolean |
基本数据类型之一,布尔类型 |
byte |
基本数据类型之一,字节类型 |
char |
基本数据类型之一,字符类型 |
double |
基本数据类型之一,双精度浮点数类型 |
float |
基本数据类型之一,单精度浮点数类型 |
int |
基本数据类型之一,整数类型 |
long |
基本数据类型之一,长整型类型 |
short |
基本数据类型之一,短整型类型 |
super |
表明当前对象的父类型的引用或父类型的构造方法 |
this |
指向当前实例对象的引用 |
void |
声明当前成员方法没有返回值 |
goto |
保留关键字,没有具体含义 |
const |
保留关键字,没有具体含义 |
提到这里,讲一讲Java中的访问控制修饰符public/protected/friendly/private
1.public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用
2.private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用。
3.protected:protected对于子女、朋友来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。
作用域 |
当前类 |
同一package |
子孙类 |
其它package |
public |
√ |
√ |
√ |
√ |
protected |
√ |
√ |
√ |
× |
friendly |
√ |
√ |
× |
× |
private |
√ |
× |
× |
× |
注:不写时默认为friendly
目录
1、何谓序列化和反序列化?
2.为何要序列化?
3、 序列化与transient的使用
3.1、没有实现Serializable接口进行序列化情况
3.2、实现Serializable接口序列化情况
3.3、普通序列化情况
3.4、属性加上transient序列化情况
4、transient关键字小结
1、何谓序列化和反序列化?
说起序列化,随之而来的另一个概念就是反序列化,记住了序列化就相当于记住了反序列化,因为反序列化就是序列化反过来。
专业术语定义的序列化和反序列化:
“序列化”是一种把对象的状态转化成字节流的机制,“反序列化”是其相反的过程,把序列化成的字节流用来在内存中重新创建一个实际的Java对象。这个机制被用来“持久化”对象。通过对象序列化,可以方便的实现对象的持久化储存以及在网络上的传输。
对象被转换成“字节流”后可以存入文件,内存,亦或者是数据库内进行持久化保存。然后通过“反序列化”可以把“字节流”转换成实际的Java对象。
对象的序列化是与平台无关的,因此在一个平台上被“序列化”的对象可以很容易的在另一个不相同的平台上给“反序列化”出来。
图理解序列化和反序列化:
2.为何要序列化?
Java中对象的序列化指的是将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据和信息,一个序列化后的对象可以被写道数据库或文件中,也可以用于网络传输,一般当我们使用缓存cache(内存空间不够有可能会本地存储到硬盘)或者远程调用rpc(网络传输)的时候,经常需要让我们的实体类实现Serializable接口,目的就是为了让其可序列化。
- 在开发过程中要使用transient关键字修饰的例子:
如果一个用户有一些密码等信息,为了安全起见,不希望在网络操作中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写道磁盘里持久化。
- 在开发过程中不需要transient关键字修饰的例子:
1、类中的字段值可以根据其它字段推导出来。
2、看具体业务需求,哪些字段不想被序列化。
其实主要是为了节省存储空间。优化程序!被序列化的字段或数据会保存在磁盘中从而造成存储资源空间浪费,上面也提到过,通过对象序列化,可以方便的实现对象的持久化储存以及在网络上的传输。
当然,序列化后的最终目的是为了反序列化,恢复成原先的Java对象,要不然序列化后干嘛呢,就像买菜一样,用塑料袋包裹最后还是为了方便安全到家再去掉塑料袋,所以序列化后的字节序列都是可以恢复成Java对象的,这个过程就是反序列化。
3、 序列化与transient的使用
1、需要做序列化的对象的类,必须实现序列化接口:Java.lang.Serializable接口(一个标志接口,没有任何抽象方法),Java中大多数类都实现了该接口,比如:String,Integer类等,不实现此接口的类将不会使任何状态序列化或反序列化,否则会抛出NotSerializableException异常。
2、底层会判断,如果当前对象是Serializable的实例,才允许做序列化,Java对象instanceof Serializable来判断。
3、在Java中使用对象流ObjectOutputStream来完成序列化以及ObjectInputStream流反序列化
1.ObjectOutputStream:通过writeObject()方法做序列化操作
2.ObjectInputStream:通过readObject()方法做反序列化操作
4、该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient关键字修饰。
IO流的主要分类:
分类 |
字节输入流 |
字节输出流 |
字符输入流 |
字符输出流 |
抽象基类 |
InputStream |
OutputStream |
Reader |
Writer |
访问文件 |
FileInputStream |
FileOutputStream |
FileReader |
FileWriter |
访问数组 |
ByteArrayInputStream |
ByteArrayOutputStream |
CharArrayReader |
CharArrayWriter |
访问管道 |
PipedInputStream |
PipedOutputStream |
PipedReader |
PipedWriter |
访问字符串 |
/ |
/ |
StringReader |
StringWriter |
缓冲流 |
BufferedInputStream |
BufferedOutputStream |
BufferedReader |
BufferedWriter |
转换流 |
/ |
/ |
InputStreamReader |
OutputStreamWriter |
对象流 |
ObjectInputStream |
ObjectOutputStream |
/ |
/ |
抽象基类 |
FilterInputStream |
FilterOutputStream |
FilterReader |
FilterWriter |
打印类 |
/ |
PrintStream |
/ |
PrintWriter |
推回输入流 |
PushbackInputStream |
/ |
PushbackReader |
/ |
特殊流 |
DataInputStream |
DataOutputStream |
/ |
/ |
注:倾斜 + 下划线代表抽象类,无法创建实例。红色表示节点流,必须直接与指定的物理节点关联 |
3.1、没有实现Serializable接口进行序列化情况
package com.krt.business.controller;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
class UserInfo{ //没有实现Serializable接口
private String name;
private transient String password;
public UserInfo(String name,String pwd){
this.name = name;
this.password = pwd;
}
@Override
public String toString(){
return "UserInfo{"+"name='"+ name + '\'' + ",password='" + password + '\'' +'}';
}
}
public class TransientDemo {
public static void main(String[] args) {
UserInfo userInfo = new UserInfo("张三","123456");
System.out.println("序列化之前信息:"+userInfo);
try{
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\1122.txt"));
output.writeObject(userInfo);
output.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果:
3.2、实现Serializable接口序列化情况
当我们加上实现Serializable接口再运行会发现,文件1122.txt内容是这样的,说明成功写入了。
3.3、普通序列化情况
package com.krt.business.controller;
import java.io.*;
//第一步实现Serializable接口
class UserInfo implements Serializable {
private String name;
//定义普通成员属性,没有加序列化transient关键字
private String password;
public UserInfo(String name,String pwd){
this.name = name;
this.password = pwd;
}
@Override
public String toString(){
return "UserInfo{"+"name='"+ name + '\'' + ",password='" + password + '\'' +'}';
}
}
public class TransientDemo {
public static void main(String[] args) {
UserInfo userInfo = new UserInfo("张三","123456");
System.out.println("序列化之前信息:"+userInfo);
try{
//第二步开始序列化操作
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\1122.txt"));
output.writeObject(userInfo);
output.close();
}catch (IOException e){
e.printStackTrace();
}
try{
//第三步开始反序列化操作
ObjectInputStream input = new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\1122.txt"));
//ObjectInputStream的readObject方法会抛出类找不到(ClassNotFoundException)异常
Object o = input.readObject();
System.out.println("反序列化之后信息:"+o);
}catch (IOException e){
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果:
3.4、属性加上transient序列化情况
package com.krt.business.controller;
import java.io.*;
//第一步实现Serializable接口
class UserInfo implements Serializable {
private String name;
//定义成员属性,属性加上序列化transient关键字
private transient String password;
public UserInfo(String name,String pwd){
this.name = name;
this.password = pwd;
}
@Override
public String toString(){
return "UserInfo{"+"name='"+ name + '\'' + ",password='" + password + '\'' +'}';
}
}
public class TransientDemo {
public static void main(String[] args) {
UserInfo userInfo = new UserInfo("张三","123456");
System.out.println("序列化之前信息:"+userInfo);
try{
//第二步开始序列化操作
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\1122.txt"));
output.writeObject(userInfo);
output.close();
}catch (IOException e){
e.printStackTrace();
}
try{
//第三步开始反序列化操作
ObjectInputStream input = new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\1122.txt"));
//ObjectInputStream的readObject方法会抛出类找不到(ClassNotFoundException)异常
Object o = input.readObject();
System.out.println("反序列化之后信息:"+o);
}catch (IOException e){
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果:
添加transient修饰的属性默认值为null!如果被transient修饰的属性为int类型,那么它被序列化之后的值为0!说明被标记为transient的属性在对象被序列化的时候不会被保存(或者说变量不会持久化)
4、transient关键字小结
1、变量被transient修饰,变量将不会被序列化
2、transient关键字只能修饰变量,而不能修饰方法和类
3、被static关键字修饰的变量不参与序列化,一个静态static变量不管是否被transient修饰。均不能被序列化
4、final变量值参与序列化,final transient同时修饰变量,final不会影响transient,一样不会参与序列化
第二点需要注意的是:本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口
第三点需要注意的是:反序列化后类中static型变量的值实际上是当前JVM中对应static变量的值,这个值是JVM中的并不是反序列化得出的。
结语:被transient关键字修饰导致不被序列化,其优点是可以节省存储空间。优化程序!随之而来的是会导致被transient修饰的字段会重新计算,初始化。