ChatGPT解决这个技术问题 Extra ChatGPT

为什么Java有瞬态字段?

为什么Java有瞬态字段?


Z
Zoe stands with Ukraine

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

Java Language Specification, Java SE 7 EditionSection 8.3.1.3. transient Fields

变量可以被标记为瞬态的,以表明它们不是对象持久状态的一部分。

例如,您可能有从其他字段派生的字段,并且只能以编程方式完成,而不是通过序列化来保持状态。

这是一个 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 关键字用于防止某些字段的序列化的用法并提供了一个场景。


但为什么它是关键字,而不是注释 @DoNotSerialize
我想,这属于 Java 中没有注释的时代。
我觉得可序列化是 Java 内部的,这很奇怪。它可以实现为需要用户重写读写方法的接口或抽象类。
@MJafar: readObject 通常链接到反序列化机制中,因此会自动调用。此外,在许多情况下,您不需要覆盖它——默认实现就可以了。
@caleb 可能是因为在 Java 中自己处理二进制格式非常痛苦,因为缺少无符号整数。
9
9 revs, 9 users 56%

在理解 transient 关键字之前,必须先了解序列化的概念。如果读者了解序列化,请跳过第一点。

什么是序列化?

序列化是使对象的状态持久化的过程。这意味着对象的状态被转换为字节流以用于持久化(例如在文件中存储字节)或传输(例如通过网络发送字节)。同样,我们可以使用反序列化从字节中恢复对象的状态。这是 Java 编程中的重要概念之一,因为序列化主要用于网络编程。需要通过网络传输的对象必须转换为字节。为此,每个类或接口都必须实现 Serializable 接口。它是一个没有任何方法的标记接口。

现在transient关键字及其用途是什么?

默认情况下,对象的所有变量都会转换为持久状态。在某些情况下,您可能希望避免保留某些变量,因为您不需要保留这些变量。因此,您可以将这些变量声明为 transient。如果变量被声明为 transient,那么它将不会被持久化。这就是 transient 关键字的主要用途。

我想用下面的例子来解释以上两点(借用自this article):

包javabeat.samples;导入 java.io.FileInputStream;导入 java.io.FileOutputStream;导入 java.io.IOException;导入 java.io.ObjectInputStream;导入 java.io.ObjectOutputStream;导入 java.io.Serializable;类 NameStore 实现 Serializable{ private String firstName;私有瞬态字符串中间名;私人字符串姓氏; 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("名字:"); sb.append(this.firstName); sb.append("中间名:"); sb.append(this.middleName); sb.append("姓氏:"); sb.append(this.lastName);返回 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")); // 写入对象 o.writeObject(nameStore); o.close(); // 从对象中读取 ObjectInputStream in = new ObjectInputStream(new FileInputStream("nameStore")); NameStore nameStore1 = (NameStore)in.readObject(); System.out.println(nameStore1); } }

输出如下:

名字 : Steve 中间名 : null 姓氏 : Jobs

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


此示例取自此代码,您可以在此处阅读:javabeat.net/2009/02/what-is-transient-keyword-in-java
这部分让我感到奇怪并且可能令人困惑:“这意味着对象的状态被转换为字节流并存储在文件中”。在我看来,大多数时候序列化不涉及写入文件(例如:下面的网络示例)
这个例子是一个糟糕的例子,因为中间名显然不是一个临时属性。
@Raphael 对我来说,这个例子很有帮助,至少解释了这个概念。如果您知道,您会提供更好的例子吗?
@Raphael - 一个实际的例子是在 LinkedList 中,大小可能是一个瞬态变量,因为它可以在反序列化对象后重新计算。
c
cody.tv.weber

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

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


Z
Zoe stands with Ukraine

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

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

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

既然我们知道了 transient 关键字和瞬态字段的用途,那么了解要标记瞬态的字段就很重要了。静态字段也没有序列化,因此相应的关键字也可以解决问题。但这可能会破坏您的课程设计;这就是 transient 关键字的用武之地。我尽量不允许序列化其值可以从其他字段派生的字段,因此我将它们标记为瞬态的。如果您有一个名为 interest 的字段,其值可以从其他字段(principalratetime)中计算出来,则无需对其进行序列化。

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


你的“简单句子”只是一个重言式。它什么也没解释。没有它你会过得更好。
这是一个很好的解释,该字段应该是 transient
兴趣领域和字数是瞬态领域的好例子。
另一个很好的用例:如果你的对象有像套接字这样的组件,如果你想序列化,那么套接字会发生什么?如果会持续存在,那么在您反序列化套接字将持有什么之后?将该套接字对象设为 transient 是有意义的
唯一应该将字段标记为瞬态的是那些出于任何原因实际上无法序列化的类。正如已经提到的,它可能是一个 Socket,或者其他类型的会话存储,或者只是一个不允许序列化的类——重点是,除了不需要序列化字段的时候,有时它被主动禁止,并且 transient 成为对感兴趣的类进行序列化的要求。此外,Logger 实例往往是静态的,因此首先不需要是 transient
Z
Zoe stands with Ukraine

transient 变量是在类被序列化时不包括在内的变量。

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


是的,类似“密码或信用证”一类的字段成员。
D
DragonFax

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

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


我认为如果在此时设计,transient 就不会是关键字。他们可能会使用注释。
M
Matthew Steven Monkan

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


如果我错了,请纠正我,但 Thread 不可序列化,所以无论如何它都会被跳过?
@TFennis:如果可序列化类 A 引用不可序列化类 B(如您的示例中的 Thread),则 A 必须将引用标记为 transient XOR 必须覆盖默认序列化过程才能按顺序用 B XOR 做一些合理的事情假设只有 B 的可序列化子类被实际引用(因此实际的子类必须注意它们的“坏”父 B)XOR 接受序列化将失败。仅在一种情况下(标记为瞬态)B 会自动且静默地跳过。
@TFennis 不,它会导致异常。
@AH:为什么是异或?我认为对这些事情进行任何组合的代码都会起作用,并且某些组合可能有用(例如,即使仅引用了 B 的可序列化子类,覆盖默认序列化过程也可能有用,反之亦然)。
Z
Zoe stands with Ukraine

在我回答这个问题之前,我需要解释一下序列化,因为如果你理解它在科学计算机中的序列化是什么意思,你就可以很容易地理解这个关键字。

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

例子:

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

现在,如果您不想转移或保存此类中的某个字段,则可以使用 transient 关键字

private transient attr2;

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


为什么要通过网络传输对象?能给我举个例子吗?
S
Silfverstrom

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


请在回答时提供更多信息。
s
setzamora

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


除了敏感数据之外,还有一些您可能不想序列化字段的用例。例如,您可能永远不想序列化 Thread(例如归功于 @AH),在这种情况下,您会将其标记为瞬态。但是,线程本身并不是敏感数据,序列化它只是没有逻辑意义(而且它是不可序列化的)。
@glen3b 这个答案不排除这种情况。在海报提到的情况下,这当然是必要的。
p
ppreetikaa

根据谷歌短暂的意思==只持续很短的时间;暂时的。

现在,如果您想在 java 中使用瞬态关键字。

Q:在哪里使用瞬态?

A:一般在java中我们可以通过在变量中获取数据并将这些变量写入文件来将数据保存到文件中,这个过程称为序列化。现在,如果我们想避免将变量数据写入文件,我们将该变量设置为瞬态。

transient int result=10;

注意:瞬态变量不能是本地的。


M
Mohammed H

瞬态关键字的简化示例代码。

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);
    }
}

n
nyulan

简单地说,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;

   }
}

V
Vishal Sheth

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


Y
Yu Jiaao

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

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

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

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