【问题标题】:Why does Java have transient fields?为什么Java有瞬态字段?
【发布时间】:2010-10-28 23:08:35
【问题描述】:

为什么 Java 有 transient 字段?

【问题讨论】:

    标签: java field transient


    【解决方案1】:

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

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

    变量可以标记为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 关键字用于防止某些字段序列化的使用情况并提供了一个场景。

    【讨论】:

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

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

    什么是序列化?

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

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

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

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

    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,所以它不会被存储在持久化存储中。

    【讨论】:

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

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

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

    【讨论】:

      【解决方案4】:

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

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

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

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

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

      【讨论】:

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

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

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

      【讨论】:

      • 是的,类似于“password or crediCardPin”类的字段成员。
      【解决方案6】:

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

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

      【讨论】:

      • 我认为transient如果是在这个时候设计的就不会是关键字。他们可能会使用注释。
      【解决方案7】:

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

      【讨论】:

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

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

      【讨论】:

      • 请在回答时提供更多信息。
      【解决方案9】:

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

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

      例子:

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

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

      private transient attr2;
      

      这可以防止在类被序列化时包含字段形式。

      【讨论】:

      • 为什么要通过网络传输对象?能举个例子吗?
      【解决方案10】:

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

      【讨论】:

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

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

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

      问:在哪里使用瞬态?

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

      transient int result=10;
      

      注意:瞬态变量不能是局部变量。

      【讨论】:

        【解决方案12】:

        transient-keyword 的简化示例代码。

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

        【讨论】:

          【解决方案13】:

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

          【讨论】:

            【解决方案14】:

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

            【讨论】:

              【解决方案15】:

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

              1. 序列化和反序列化是对称的过程,如果不是你不能指望结果是确定的,在大多数情况下,未确定的值是没有意义的;
              2. 序列化和反序列化是幂等的,这意味着你可以任意多次序列化,结果是一样的。

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

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

              【讨论】:

                猜你喜欢
                • 2011-06-01
                • 1970-01-01
                • 2012-03-30
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-11-11
                • 2018-10-27
                相关资源
                最近更新 更多