为什么Java有瞬态字段?
15 回答
Java中的transient
关键字用于指示字段不应该是序列化(这意味着像文件一样保存)过程的一部分。
来自Java 语言规范,Java SE 7 版,第 8.3.1.3 节。transient
领域:
可以标记变量
transient
以指示它们不是对象的持久状态的一部分。
例如,您可能有从其他字段派生的字段,并且只能以编程方式完成,而不是通过序列化来保持状态。
这是一个GalleryImage
包含图像和从图像派生的缩略图的类:
class GalleryImage implements Serializable
{
private Image image;
private transient Image thumbnailImage;
private void generateThumbnail()
{
// Generate thumbnail.
}
private void readObject(ObjectInputStream inputStream)
throws IOException, ClassNotFoundException
{
inputStream.defaultReadObject();
generateThumbnail();
}
}
在此示例中,是通过调用该方法thumbnailImage
生成的缩略图。generateThumbnail
该thumbnailImage
字段标记为transient
,因此仅对image
原始图像进行序列化,而不是同时保留原始图像和缩略图图像。这意味着保存序列化对象所需的存储空间更少。(当然,根据系统的要求,这可能是可取的,也可能不是可取的——这只是一个例子。)
在反序列化时,readObject
调用该方法以执行将对象状态恢复到发生序列化时的状态所需的任何操作。这里需要生成缩略图,所以readObject
重写了方法,调用generateThumbnail
方法生成缩略图。
有关其他信息,文章Discover the secrets of the Java Serialization API(最初可在 Sun Developer Network 上获得)有一个部分讨论了transient
关键字用于防止某些字段序列化的用法并提供了一个场景。
在理解transient
关键字之前,必须先了解序列化的概念。如果读者了解序列化,请跳过第一点。
什么是序列化?
序列化是使对象的状态持久化的过程。这意味着对象的状态被转换为字节流以用于持久化(例如在文件中存储字节)或传输(例如通过网络发送字节)。同样,我们可以使用反序列化从字节中恢复对象的状态。这是 Java 编程中的重要概念之一,因为序列化主要用于网络编程。需要通过网络传输的对象必须转换为字节。为此,每个类或接口都必须实现该Serializable
接口。它是一个没有任何方法的标记接口。
现在transient
关键字及其用途是什么?
默认情况下,对象的所有变量都会转换为持久状态。在某些情况下,您可能希望避免保留某些变量,因为您不需要保留这些变量。因此,您可以将这些变量声明为transient
. 如果变量被声明为transient
,那么它将不会被持久化。这是transient
关键字的主要目的。
我想用下面的例子来解释以上两点(借用这篇文章):
package javabeat.samples; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; class NameStore implements Serializable{ private String firstName; private transient String middleName; private String lastName; public NameStore (String fName, String mName, String lName){ this.firstName = fName; this.middleName = mName; this.lastName = lName; } public String toString(){ StringBuffer sb = new StringBuffer(40); sb.append("First Name : "); sb.append(this.firstName); sb.append("Middle Name : "); sb.append(this.middleName); sb.append("Last Name : "); sb.append(this.lastName); return sb.toString(); } } public class TransientExample{ public static void main(String args[]) throws Exception { NameStore nameStore = new NameStore("Steve", "Middle","Jobs"); ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("nameStore")); // writing to object o.writeObject(nameStore); o.close(); // reading from object ObjectInputStream in = new ObjectInputStream(new FileInputStream("nameStore")); NameStore nameStore1 = (NameStore)in.readObject(); System.out.println(nameStore1); } }
输出如下:
First Name : Steve Middle Name : null Last Name : Jobs
Middle Name被声明为transient
,因此它不会存储在持久存储中。
允许您定义不想序列化的变量。
在一个对象中,您可能拥有不想序列化/持久化的信息(可能是对父工厂对象的引用),或者序列化可能没有意义。将这些标记为“瞬态”意味着序列化机制将忽略这些字段。
为什么 Java 中需要瞬态字段?
该transient
关键字使您可以对序列化过程进行一些控制,并允许您从该过程中排除一些对象属性。序列化过程用于持久化 Java 对象,主要是为了在传输或不活动时保留它们的状态。有时,不序列化对象的某些属性是有意义的。
您应该将哪些字段标记为瞬态?
现在我们知道了transient
关键字和瞬态字段的用途,重要的是要知道哪些字段要标记瞬态。静态字段也没有序列化,因此相应的关键字也可以解决问题。但这可能会破坏您的课程设计;这就是transient
关键字来拯救的地方。我尽量不允许序列化其值可以从其他字段派生的字段,因此我将它们标记为瞬态的。如果您有一个名为的字段interest
,其值可以从其他字段(principal
,rate
& time
)中计算出来,则无需对其进行序列化。
另一个很好的例子是文章字数。如果您要保存整篇文章,则实际上不需要保存字数,因为它可以在文章“反序列化”时计算。或者想想记录器;Logger
实例几乎不需要被序列化,因此它们可以是瞬态的。
transient
变量是类序列化时不包含的变量。
想到什么时候这可能有用的一个例子是,仅在特定对象实例的上下文中才有意义的变量,并且在您序列化和反序列化对象后变得无效。在这种情况下,让这些变量变为有用是很有用的,null
这样您就可以在需要时使用有用的数据重新初始化它们。
本机 java 以外的序列化系统也可以使用此修饰符。例如,Hibernate 不会保留标记有@Transient或瞬态修饰符的字段。兵马俑也尊重这个修饰符。
我相信修饰符的比喻意义是“这个字段仅供内存使用。不要以任何方式将其保留或移动到此特定 VM 之外。它是不可移植的”。即你不能依赖它在另一个VM 内存空间中的值。很像volatile意味着您不能依赖某些内存和线程语义。
transient
用于表示类字段不需要序列化。最好的例子可能是一个Thread
字段。通常没有理由序列化 a Thread
,因为它的状态非常“特定于流”。
在我回答这个问题之前,我需要解释一下序列化,因为如果你理解它在科学计算机中的序列化是什么意思,你就可以很容易地理解这个关键字。
当对象通过网络传输/保存在物理媒体(文件,...)上时,必须“序列化”对象。序列化转换字节状态对象系列。这些字节在网络上发送/保存,并从这些字节重新创建对象。
例子:
public class Foo implements Serializable
{
private String attr1;
private String attr2;
...
}
现在,如果这个类中有一个字段你不想转移或保存,你可以使用transient
关键字
private transient attr2;
这可以防止在序列化类时包含字段表单。
因为并非所有变量都具有可序列化的性质
当您不想共享一些与序列化相关的敏感数据时,就需要它。
根据谷歌短暂的意思==只持续很短的时间;暂时的。
现在,如果您想在 java 中使用瞬态关键字。
Q:在哪里使用瞬态?
A:一般在java中我们可以通过在变量中获取数据并将这些变量写入文件来将数据保存到文件中,这个过程称为序列化。现在,如果我们想避免将变量数据写入文件,我们将该变量设置为瞬态。
transient int result=10;
注意:瞬态变量不能是本地的。
瞬态关键字的简化示例代码。
import java.io.*;
class NameStore implements Serializable {
private String firstName, lastName;
private transient String fullName;
public NameStore (String fName, String lName){
this.firstName = fName;
this.lastName = lName;
buildFullName();
}
private void buildFullName() {
// assume building fullName is compuational/memory intensive!
this.fullName = this.firstName + " " + this.lastName;
}
public String toString(){
return "First Name : " + this.firstName
+ "\nLast Name : " + this.lastName
+ "\nFull Name : " + this.fullName;
}
private void readObject(ObjectInputStream inputStream)
throws IOException, ClassNotFoundException
{
inputStream.defaultReadObject();
buildFullName();
}
}
public class TransientExample{
public static void main(String args[]) throws Exception {
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("ns"));
o.writeObject(new NameStore("Steve", "Jobs"));
o.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("ns"));
NameStore ns = (NameStore)in.readObject();
System.out.println(ns);
}
}
简单地说,transient java 关键字保护字段不被序列化作为它们的非瞬态字段对应部分。
在这段代码片段中,我们的抽象类 BaseJob 实现了 Serializable 接口,我们从 BaseJob 扩展,但我们不需要序列化远程和本地数据源;仅序列化组织名称和 isSynced 字段。
public abstract class BaseJob implements Serializable{
public void ShouldRetryRun(){}
}
public class SyncOrganizationJob extends BaseJob {
public String organizationName;
public Boolean isSynced
@Inject transient RemoteDataSource remoteDataSource;
@Inject transient LocalDaoSource localDataSource;
public SyncOrganizationJob(String organizationName) {
super(new
Params(BACKGROUND).groupBy(GROUP).requireNetwork().persist());
this.organizationName = organizationName;
this.isSynced=isSynced;
}
}
使用瞬态修饰符声明的字段将不参与序列化过程。当一个对象被序列化(以任何状态保存)时,其瞬态字段的值在序列表示中被忽略,而瞬态字段以外的字段将参与序列化过程。这就是瞬态关键字的主要目的。
因为并非所有变量都具有可序列化的性质。
- 序列化和反序列化是对称的过程,如果不是你不能期望结果是确定的,在大多数情况下,未确定的值是没有意义的;
- 序列化和反序列化是幂等的,这意味着您可以根据需要进行多次序列化,并且结果是相同的。
所以如果Object可以存在于内存而不存在于磁盘上,那么Object就不能被序列化,因为反序列化时机器无法恢复内存映射。例如,您不能序列化Stream
对象。
你不能序列化一个Connection
对象,因为它的状态也依赖于远程站点。