繁体   English   中英

为什么 Java 有瞬态字段?

[英]Why does Java have transient fields?

为什么 Java 有瞬态字段?

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

中间名声明为transient ,因此不会存储在持久存储中。

允许您定义不想序列化的变量。

在一个对象中,您可能有不想序列化/持久化的信息(可能是对父工厂对象的引用),或者序列化可能没有意义。 将这些标记为“transient”意味着序列化机制将忽略这些字段。

为什么 Java 中需要瞬态字段?

transient关键字使您可以对序列化过程进行一些控制,并允许您从该过程中排除某些对象属性。 序列化过程用于持久化 Java 对象,主要是为了在它们传输或不活动时保留它们的状态。 有时,不序列化对象的某些属性是有意义的。

您应该将哪些字段标记为瞬态?

现在我们知道了transient关键字和transient 字段的用途,重要的是要知道哪些字段要标记为transient。 静态字段也不会序列化,因此相应的关键字也可以解决问题。 但这可能会破坏您的类设计; 这就是transient关键字派上用场的地方。 我尽量不允许其值可以从其他人派生的字段被序列化,因此我将它们标记为瞬态。 如果您有一个名为interest的字段,其值可以从其他字段( principal , rate & time )计算出来,则无需对其进行序列化。

另一个很好的例子是文章字数统计。 如果您要保存整篇文章,则实际上没有必要保存字数,因为可以在文章“反序列化”时进行计算。 或者想想记录器; Logger实例几乎不需要序列化,因此它们可以是瞬态的。

transient变量是在类序列化时不包含的变量。

想到这可能有用的一个例子是,变量只在特定对象实例的上下文中有意义,并且一旦您序列化和反序列化对象就会变得无效。 在这种情况下,让这些变量变为null很有用,这样您就可以在需要时使用有用的数据重新初始化它们。

transient用于指示类字段不需要序列化。 最好的例子可能是Thread字段。 通常没有理由序列化Thread ,因为它的状态非常“特定于流”。

除了本机 java 之外的序列化系统也可以使用此修饰符。 例如,Hibernate 不会保留标有@Transient瞬态修饰符的字段。 兵马俑也尊重这个修饰符。

我相信修饰符的比喻意义是“该字段仅用于内存中。不要以任何方式将其持久化或移动到此特定 VM 之外。它不可移植”。 即你不能依赖它在另一个 VM 内存空间中的值。 很像volatile意味着你不能依赖某些内存和线程语义。

在我回答这个问题之前,我需要解释一下序列化,因为如果你理解了科学计算机中序列化的含义,你就很容易理解这个关键字了。

当对象通过网络传输/保存在物理媒体(文件,...)上时,对象必须被“序列化”。 序列化转换字节状态对象系列。 这些字节在网络上发送/保存,并从这些字节重新创建对象。

例子:

public class Foo implements Serializable 
{
 private String attr1;
 private String attr2;
 ...
}

现在,如果这个类中有一个字段你不想转移或保存,你可以使用transient关键字

private transient attr2;

这可以防止在类序列化时包含字段表单。

因为并非所有变量都具有可序列化的性质

当您不想共享一些与序列化相关的敏感数据时需要它。

根据谷歌瞬态含义 == 仅持续很短的时间; 暂时的。

现在,如果您想在 java 中使任何东西变得瞬态,请使用瞬态关键字。

问:在哪里使用瞬态?

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 扩展,但我们不需要序列化远程和本地数据源; 仅序列化 organizationName 和 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;

   }
}

使用瞬态修饰符声明的字段不会参与序列化过程。 当一个对象被序列化(保存在任何状态)时,其瞬态字段的值在序列表示中被忽略,而瞬态字段以外的字段将参与序列化过程。 这就是transient关键字的主要目的。

因为并非所有变量都具有可序列化的性质。

  1. 序列化和反序列化是对称过程,如果不是你不能期望结果是确定的,在大多数情况下,未确定的值是没有意义的;
  2. 序列化和反序列化是幂等的,这意味着您可以根据需要进行多次序列化,并且结果是相同的。

所以如果Object可以存在于内存中而不能存在于磁盘上,那么这个Object就不能被序列化,因为反序列化时机器无法恢复内存映射。 例如,您不能序列化Stream对象。

您不能序列化Connection对象,因为它的状态也依赖于远程站点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM