为什么 Java 有瞬态字段?

为什么 Java 有瞬态字段?

答案

Java 中的transient关键字用于指示字段不应成为序列化(意味着保存, 保存到文件)过程的一部分。

摘自Java SE 7 Edition Java 语言规范的 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方法生成缩略图。

有关更多信息,“ 发现 Java 序列化 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关键字修改的字段都是一个 transient 字段。

为什么在 Java 中需要瞬态字段?
transient关键字使您可以控制序列化过程,并允许您从该过程中排除一些对象属性。序列化过程通常用于持久化 Java 对象,以便在它们转移或不活动时可以保留它们的状态。有时,不序列化对象的某些属性是有意义的。

您应将哪些字段标记为瞬态?
现在我们知道了transient关键字和瞬变字段的用途,了解哪些标记瞬变很重要。静态字段也不会序列化,因此相应的关键字也可以解决问题。但这可能会破坏您的班级设计。这是transient关键字可以解决的地方。我尝试不允许将其值可以从其他值派生的字段进行序列化,因此我将它们标记为瞬态。如果您有一个名为interest的字段,其值可以从其他字段( principalratetime )计算得出,则无需对其进行序列化。

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

transient变量是可能无法序列化的变量。

一个可能在何时有用的示例是,仅在特定对象实例的上下文中有意义的变量,并且在对对象进行序列化和反序列化后这些变量将变为无效。在这种情况下,使这些变量变为null很有用,这样您就可以在需要时用有用的数据重新初始化它们。

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

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

我相信修饰符的象征意义是 “此字段仅用于内存使用。请勿以任何方式持久保留或将其移出此特定 VM。其不可移植”。即,您不能依赖其在另一个 VM 内存空间中的值。就像volatile一样,您不能依赖某些内存和线程语义。

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

在回答这个问题之前,我必须向您解释SERIALIZATION ,因为如果您了解科学计算机中的序列化含义,那么您可以轻松理解此关键字。

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

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

现在,如果你想这样做,对象不TRANSFERT / SAVED的话 ,你可以使用关键字 transient

private transient attr2;

当您不想共享序列化附带的一些敏感数据时,需要使用它。