为什么Java有瞬态字段?

1641

为什么Java有transient字段?


为了不使它们被序列化。 - user207421
15个回答

1818

transient关键字在Java中用于表示一个字段不应该参与序列化(比如保存到文件)的过程。

根据Java SE 7版语言规范第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 方法来生成缩略图像。
如需更多信息,请参阅文章“发现Java序列化API的秘密”(最初在Sun Developer Network上可用),其中讨论了使用场景并展示了一种使用transient关键字来防止序列化某些字段的情况。

264
为什么它是一个关键字而不是注释@DoNotSerialize - Elazar Leibovich
384
我猜这是因为在Java没有注释的时候。 - Peter Wippermann
61
我觉得Java中把Serializable设为内部的有些奇怪。它可以作为接口或抽象类来实现,要求用户覆盖读取和写入方法。 - caleb
9
readObject通常被链接到反序列化机制中,并自动调用。此外,在许多情况下,您不需要覆盖它-默认实现就可以胜任。 - Mike Adler
19
可能是因为在Java中处理二进制格式非常痛苦,由于缺乏无符号整数。 - user1804599
显示剩余10条评论

489

在理解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,因此它不会被存储在持久性存储中。


30
这个例子取自这段代码,你可以在这里阅读它:http://www.javabeat.net/2009/02/what-is-transient-keyword-in-java/ - Krishna
14
这部分让我感到奇怪,可能会令人困惑:“这意味着对象的状态被转换为一系列字节并存储在文件中”。在我看来,大多数情况下序列化不涉及写入文件(例如:接下来的网络示例)。 - Garcia Hurtado
9
这个例子很糟糕,因为中间名显然不是一个短暂的属性。 - Raphael
4
如果你有更好的例子,请提供。对我来说,这个例子很有帮助,至少解释了这个概念。请确保翻译时不改变原意,使内容更通俗易懂,但不要添加解释或其他额外的信息。 - Arefe
1
@Raphael - 一个实际的例子是,在链表中,大小可以是一个瞬态变量,因为在反序列化对象之后,它可以被重新计算。 - Tarun
显示剩余2条评论

91
为了允许您定义不想要序列化的变量。
在对象中,您可能有一些不想要序列化/持久化的信息(例如对父工厂对象的引用),或者可能没有序列化的意义。将这些标记为“瞬态”的意思是序列化机制将忽略这些字段。

41

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

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

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

既然我们知道了transient关键字和瞬态字段的目的,就很重要知道应该标记哪些字段为瞬态字段。静态字段也不会被序列化,所以相应的关键字也可以起到作用。但是这可能会破坏你的类设计;这就是transient关键字发挥作用的地方。我尽量不允许那些其值可以从其他字段派生的字段被序列化,因此我将它们标记为瞬态字段。如果你有一个叫做interest的字段,它的值可以根据其他字段(principal, ratetime)计算出来,那就没有必要对它进行序列化。

另一个很好的例子是文章单词计数。如果你正在保存一篇完整的文章,实际上没有必要保存它的单词数量,因为在反序列化文章时可以计算出来。或者考虑日志记录器;Logger实例几乎不需要被序列化,所以它们可以被标记为瞬态字段。


69
你的“简单句”只是一种重言,没有解释任何内容。最好不要使用它。 - user207421
1
这是一个很好的解释,说明字段应该是“transient”。 - Arefe
1
兴趣领域和单词计数是瞬态字段的好例子。 - Tarun
1
另一个很好的用例:如果您的对象具有像套接字这样的组件,并且如果您想要序列化它,那么套接字会发生什么?如果持久化,反序列化后套接字将保留什么?将该套接字对象设置为“瞬态”是有意义的。 - Mohammed Siddiq
唯一剩下的需要标记为瞬态的字段是那些无论出于任何原因都不能被序列化的类。正如已经提到的,它可能是一个Socket,或者某种其他类型的会话存储,或者只是一个不允许序列化的类——重点是,在不需要序列化字段的情况下,有时是明确禁止的,并且“transient”成为所需类的序列化要求。此外,“Logger”实例往往是静态的,因此首先不需要是“transient”。 - Zoe stands with Ukraine

32

transient变量是一种在类被序列化时不会包含的变量。

其中一个例子是针对那些仅在特定对象实例上下文中才有意义且在对象序列化和反序列化后失效的变量。在这种情况下,将这些变量变为null是有用的,这样当需要时可以重新初始化它们,并提供有用的数据。


是的,像“密码或信用卡PIN码”这样的字段成员在一个类中。 - Mateen

18
除了java本地的序列化系统外,其他序列化系统也可以使用这个修饰符。比如Hibernate,不会持久化标记为@Transient或使用transient修饰符的字段。Terracotta也尊重此修饰符。
我认为这个修饰符的比喻意义是“该字段仅用于内存中,不能以任何方式持久化或移动到此特定VM之外。它是非可移植的”,即您不能依赖在另一个VM内存空间中的值,就像volatile表示您不能依赖某些内存和线程语义一样。

16
如果现在设计的话,我认为 transient 不会成为一个关键字,而会使用注解。 - Joachim Sauer

17

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


请纠正我,但是Thread不可序列化,所以无论如何它都会被跳过? - TFennis
4
如果一个可序列化的类A引用了一个不可序列化的类B(例如你举的例子中的Thread),那么A必须把这个引用标记为transient,或者重写默认的序列化过程以便处理B,或者假设只有B的可序列化子类被实际引用(所以实际的子类必须对它们的“坏”父类B负责),否则序列化将会失败。只有标记为transient的情况下,B才会被自动且静默地跳过。 - A.H.
3
不行,会引发异常。 - user207421
1
@A.H.: 为什么使用异或运算符?我认为任何这些操作的组合都可以工作,而且某些组合可能很有用(例如,即使只引用B的可序列化子类,覆盖默认序列化过程也可能很有用,反之亦然)。 - supercat

9
在回答这个问题之前,我需要解释一下序列化的概念。因为如果你理解了计算机科学中序列化的含义,就能够更容易地理解这个关键词。
当一个对象通过网络传输/保存在物理媒介(文件等)上时,该对象必须被“序列化”。序列化将对象转换为字节流。这些字节会被发送到网络/保存下来,然后对象可以从这些字节重新创建出来。
例如:
public class Foo implements Serializable 
{
 private String attr1;
 private String attr2;
 ...
}

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

private transient attr2;

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

一个对象为什么要通过网络进行传输?你能举个例子吗? - aderchox

7

由于并非所有变量都具有可序列化的性质


57
请在回答时提供更多信息。 - Danny Gloudemans

5
根据谷歌翻译,transient的意思是“持续时间很短暂;短暂的”。如果您想在Java中使任何内容都是短暂的,请使用transient关键字。
问题:在哪里使用transient?
回答:一般来说,在Java中,我们可以通过将数据保存在变量中并将这些变量写入文件来将数据存储到文件中,这个过程称为序列化。现在,如果我们不想将变量数据写入文件,则会使该变量成为短暂的。
transient int result=10;

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接