Java小白系列(一):关键字transient
宗旨
本系列意在用最浅显的文字和代码示例,让大家实实在在的掌握Java最基础的知识。
一、前言
我们在学习JDK源码时,如果大家有留心,应该会经常看到关键字:transient !但不知道有多少人去真正了解过该关键字的作用。
二、初识transient
transient,中文意思:短暂的!那么,它有什么用呢?这就涉及到 Java 持久化机制。
2.1、Java 持久化
Java为我们提供了一种非常方便的持久化实例机制:Serializable !
可能有人要说:Serializable 不是用于序列化与反序列化么?
嗯,对!没错,那我想问:一个实例序列化后干啥用?
一个实例的序列化,可用于数据传输,然而,其最大的作用还是用于写入磁盘,从而防止数据丢失。
2.2、Java 序列化
我们在编写可用于序列化/反序列化的类时,都会去实现 Serializable,但是有时候,我们又不想序列化某些字段,那么,我们就可以用关键字 transient 来修饰该字段。
// BankCard.java
import java.io.Serializable;
public class BankCard implements Serializable {
private String cardNo;
private transient String password;
public String getCardNo() {
return cardNo;
}
public void setCardNo(String cardNo) {
this.cardNo = cardNo;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "BankCard{cardNo='" + cardNo + '\'' + ", password='" + password + '\'' + '}';
}
}
编写测试类
public class Main {
public static void main(String[] args) {
try {
serializeBankCard();
Thread.sleep(2000);
deSerializeBankCard();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void serializeBankCard() throws Exception {
BankCard bankCard = new BankCard();
bankCard.setCardNo("CardNo: 1234567890");
bankCard.setPassword("********");
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("./bank.txt"));
os.writeObject(bankCard);
os.close();
System.out.println("序列化 " + bankCard.toString());
}
private static void deSerializeBankCard() throws Exception {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("./bank.txt"));
BankCard bankCard = (BankCard) is.readObject();
is.close();
System.out.println("反序列化 " + bankCard.toString());
}
}
// 打印输出
// 序列化 BankCard{cardNo='CardNo: 1234567890', password='********'}
// 反序列化 BankCard{cardNo='CardNo: 1234567890', password='null'}
我们可以看到,我们先创建『银行卡』类,然后将数据序列化写到磁盘上,过了2秒后,我们再从磁盘上读取数据,打印发现,『password』字段是『null』。
我们打开磁盘上的文件,用十六进制查看数据,也可以看到,文件中,只有『CardNo』,没有『password』:
serializable + transient.png三、深入分析 transient
在深入分析之前,我先抛出几个问题,然后,再带大家一一去解惑:
- transient 实现原理;
- transient 修饰的字段真的无法被序列化?
- 静态变量能被序列化么?
- transient + static 修饰的字段能被序列化么?
OK,带着以上问题,我们去看源码!
3.1、Serializable 类
/**
* ......
* The writeObject method is responsible for writing the state of the
* object for its particular class so that the corresponding
* readObject method can restore it. The default mechanism for saving
* the Object's fields can be invoked by calling
* out.defaultWriteObject. The method does not need to concern
* itself with the state belonging to its superclasses or subclasses.
* State is saved by writing the individual fields to the
* ObjectOutputStream using the writeObject method or by using the
* methods for primitive data types supported by DataOutput.
*
* The readObject method is responsible for reading from the stream and
* restoring the classes fields. It may call in.defaultReadObject to invoke
* the default mechanism for restoring the object's non-static and
* non-transient fields.
*
* .........
*
* @see java.io.ObjectOutputStream
* @see java.io.ObjectInputStream
* @see java.io.ObjectOutput
* @see java.io.ObjectInput
* @see java.io.Externalizable
* @since JDK1.1
*/
public interface Serializable {
}
Serializable 的注释就说到了:
- writeObject 负责写数据,readObject 负责读数据;
- 数据(状态)的读写,并不关心它的父类或子类;
- ObjectOutputSteam 是基于 DataOutput 实现的写;
- readObject 负责读流(stream),并存到类成员变量中,它的默认存到非static成员和非transient成员中;
3.2、ObjectOutputStream 类
开头的注释也说了:默认的 Serializable 机制,只写类的对象、签名以及非 transient 和非 static 字段
/*
* The default serialization mechanism for an object writes the class of the
* object, the class signature, and the values of all non-transient and
* non-static fields.
*/
public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants {
private final BlockDataOutputStream bout;
private static class BlockDataOutputStream extends OutputStream implements DataOutput {
......
}
public final void writeObject(Object obj) throws IOException {
......
try {
writeObject0(obj, false);
}
......
}
private void writeObject0(Object obj, boolean unshared) throws IOException {
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
......
ObjectStreamClass desc;
for (;;) {
......
// 重点:获取 Serializable对象的 desc
// desc 中的 fields 会过滤 static 和 transient
// 具体代码流程继续看 3.3
desc = ObjectStreamClass.lookup(cl, true);
......
}
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared); // obj 根据 desc.fields 序列化数据
}
......
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared) throws IOException {
if (extendedDebugInfo) {
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class \"" +
obj.getClass().getName() + "\", " + obj.toString() + ")");
}
try {
desc.checkSerialize();
// 最终调用 bout 来写数据
bout.writeByte(TC_OBJECT);
// 所有的 writeXXX 都将调用 bout 来写数据
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
3.3、ObjectStreamClass类
3.3.1、ObjectStreamClass.lookup
public class ObjectStreamClass implements Serializable {
static ObjectStreamClass lookup(Class<?> cl, boolean all) {
......
if (entry == null) {
try {
entry = new ObjectStreamClass(cl);
} catch (Throwable th) {
entry = th;
}
......
}
......
}
}
3.3.2、ObjectStreamClass构造函数
public class ObjectStreamClass implements Serializable {
private ObjectStreamClass(final Class<?> cl) {
......
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
......
try {
fields = getSerialFields(cl); // 获取可序列化的字段
computeFieldOffsets();
} catch (InvalidClassException e) {
......
fields = NO_FIELDS;
}
......
}
}
}
}
}
3.3.3、ObjectStreamClass.getSerialFields
public class ObjectStreamClass implements Serializable {
private static ObjectStreamField[] getSerialFields(Class<?> cl) throws InvalidClassException {
ObjectStreamField[] fields;
if (Serializable.class.isAssignableFrom(cl) &&
!Externalizable.class.isAssignableFrom(cl) &&
!Proxy.isProxyClass(cl) &&
!cl.isInterface())
{
if ((fields = getDeclaredSerialFields(cl)) == null) {
fields = getDefaultSerialFields(cl);
}
Arrays.sort(fields);
} else {
fields = NO_FIELDS;
}
return fields;
}
}
3.3.4、ObjectStreamClass.getDefaultSerialFields
public class ObjectStreamClass implements Serializable {
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
int mask = Modifier.STATIC | Modifier.TRANSIENT; // 重点1:掩码 mask
for (int i = 0; i < clFields.length; i++) {
// 重点2:过滤 static 或者 transient
if ((clFields[i].getModifiers() & mask) == 0) {
list.add(new ObjectStreamField(clFields[i], false, true));
}
}
int size = list.size();
return (size == 0) ? NO_FIELDS : list.toArray(new ObjectStreamField[size]);
}
}
3.4、断点查看运行时JDK源码
jdk_runtime.png简单的看完以上源码,至少回答了我们这么几个问题:
- static 变量是无法序列化的,无论是否加了 transient 修饰符;
- transient 修饰的变量也是无法序列化的;
那么,还有一个问题,transient 修饰的变量真的无法序列化么?
再回答该问题前,首先我想问下大家,大家知道 Java 除了提供 Serializable 序列化外,还有提供其它方式么?
答案是:Java 提供了两种序列化方式
- Serializable,序列化与反序列化由 Java 实现(ObjectOutputStream / ObjectInputStream 的默认机制);
- Externalizable,序列化与反序列化由开发者自主来实现;
先来看看示例
// BankCardExt.java
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class BankCardExt implements Externalizable {
private String cardNo;
private transient String password;
public String getCardNo() {
return cardNo;
}
public void setCardNo(String cardNo) {
this.cardNo = cardNo;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(cardNo);
out.writeObject(password);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
cardNo = (String) in.readObject();
password = (String) in.readObject();
}
@Override
public String toString() {
return "BankCardExt{cardNo='" + cardNo + "\', password='" + password + "\'}";
}
}
这个类的读和写,由我们来实现。
再来个测试类
public class Main {
public static void main(String[] args) {
try {
serializeBankCard();
Thread.sleep(2000);
deSerializeBankCard();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void serializeBankCard() throws Exception {
BankCardExt bankCard = new BankCardExt();
bankCard.setCardNo("CardNo: 1234567890");
bankCard.setPassword("********");
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("./bankExt.txt"));
os.writeObject(bankCard);
os.close();
System.out.println("序列化 " + bankCard.toString());
}
private static void deSerializeBankCard() throws Exception {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("./bankExt.txt"));
BankCardExt bankCard = (BankCardExt) is.readObject();
is.close();
System.out.println("反序列化 " + bankCard.toString());
}
}
// 打印输出
// 序列化 BankCardExt{cardNo='CardNo: 1234567890', password='********'}
// 反序列化 BankCardExt{cardNo='CardNo: 1234567890', password='********'}
我们看到,读的时候,数据也回来了。
exteralizable.png查看文件,我们也看到了『CardNo』和『password』。
这里还有一点需要注意:写的顺序与读的顺序要保持一致!如果我们顺充不一致,结果将如下:
{
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(cardNo);
out.writeObject(password);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
password = (String) in.readObject();
cardNo = (String) in.readObject();
}
}
// 打印输出
// 序列化 BankCardExt{cardNo='CardNo: 1234567890', password='********'}
// 反序列化 BankCardExt{cardNo='********', password='CardNo: 1234567890'}
OK,最后一个问题我们也解答了,当我们实现 Externalizable 时,需要我们自主选择哪些字段需要序列化与反序列化,因此,此时的 transient 修饰符将没有作用。
四、总结
本文分析了 transient 的序列化底层机制:
- 当我们的类实现了 Serializable 时,transient 修饰的变量只能存在于内存中,ObjectOutputStream 将忽略 transient 和 static 修饰的变量;
- 当我们的类实现了 Externalizable 时,此时 transient 关键字将失效,类的成员变量的序列化与反序列化将由我们自己控制;