【问题标题】:Java Externalization vs TransientJava 外部化与瞬态
【发布时间】:2015-06-30 06:56:41
【问题描述】:

我正在考虑外部化的目的,因为您可以简单地将属性标记为 transient 并防止其序列化。但是,经过进一步研究,我发现如果您需要在运行时决定需要什么,这种方法(即标记为transient)可能并不理想。从理论上讲,这对我来说是有意义的。但是,实际上我看不到 Externalization 如何对运行时更友好。我的意思是,在类的定义过程中,您仍然必须决定 writeExternal()readExternal() 中需要或不需要什么。那么,这如何对运行时更友好呢?

突出显示的文档如下,

如果通过实施 可序列化的接口,为什么有人要实现 Externalizable 接口又懒得定义这两个方法?简单地 对过程有完全的控制。好吧...让我们采取一个 示例示例来理解这一点。假设我们有一个对象 数百个字段(非瞬态),我们只希望有几个字段 存储在持久存储上,而不是全部。一种解决方案是 将所有其他字段(我们要序列化的字段除外)声明为 瞬态和默认的序列化过程将自动 照顾好那个。但是,如果这几个字段不固定在 设计时间相反,它们是在运行时有条件地决定的。在 在这种情况下,实现 Externalizable 接口可能会 成为更好的解决方案。同样,我们可能在某些情况下 根本不想维护超类的状态(它们是 由 Serializable 接口自动维护 实施)。

【问题讨论】:

  • 您真的需要使用Externalizable,还是Serializable 可以使用?您仍然可以提供自定义的读/写方法。
  • transient 是从哪里来的?你想做什么transient
  • 我不会说它更“运行时友好”,它只是更灵活。因为您可以使用条件、迭代、... 来塑造和过滤您的输出/输入
  • @AndyTurner 请注意,这是一个虚构的代码 sn-p。我的问题的目的是了解与使用 Serializable + transient 相比,上述示例如何更“运行时”友好。
  • @bvdb 你有例子来证明你的观点吗?

标签: java transient externalizable


【解决方案1】:

我想指出,在比较 SerializableExternalizable 方法时,还需要考虑其他优点/缺点。

外化更快

在序列化期间,JVM 将始终首先检查该类是否可外部化。如果是这种情况,那么它将使用read/writeExternal 方法。 (有道理,对)

可外部化的类需要更少的递归,因为您可以准确地确定您需要什么数据。它还导致更紧凑的输出(更少的字节),这将我们带到下一点......

外化输出更紧凑

如果你比较实际的输出,它看起来像这样: 对象的标头包含一个标志,用于标记该类是 Serializable 还是 Externalizable

OBJECT
CLASSDESC
  Class Name: "MyClassName"
  Class UID:  ...
  Class Desc Flags: SERIALIZABLE or EXTERNALIZABLE

如果只是SERIALIZABLE,那么后面会出现一个字段列表(就像定义一样),然后是实际数据。对每个序列化对象重复此操作。

  Field Count: ...
  // followed by an bunch of declarations of objects
  Field type: object
  Field name: "fieldName"
  Class name: "Ljava/lang/String;"

 // followed by the actual data
 STRING: "foo"
 STRING: "bar"
 float: 123456

Externalizable 对象不包含字段和数据的列表,它们仅包含按您保存的顺序编码的数据。

  EXTERNALIZABLE: [00 AA 00 BC ... ]

外化更灵活

如果您保存购物清单,那么您只需要产品名称,对吗?

public class ShoppingList implements Externalizable {
  String name;
  List<Product> productList;     

  @Override
  public void writeExternal(ObjectOutput pOutput) throws IOException
  {
    out.writeUTF(name);
    for (Product product : productList)
    {
      // save only product id
      out.writeUTF(product.getEanCode());
    }
  }
  ...
}

但是如果你是在做账单,那么你也想节省价格吧?

public class Bill implements Externalizable {
  String name;
  List<Product> productList;     

  @Override
  public void writeExternal(ObjectOutput pOutput) throws IOException
  {
    out.writeUTF(name);
    for (Product product : productList)
    {
      // save product id and price
      out.writeUTF(product.getEanCode());
      out.writeInt(product.getPrice());
    }
  }
  ...
}

因此,在某些情况下价格是暂时的,而在某些情况下则不是。您将如何使用 transient 关键字解决这个问题? -- 我会让你弄清楚这一点。 当只使用瞬态关键字时,这种灵活性真的很差。

设计注意事项

但是,也有一些危险。 只能为具有公共默认构造函数的对象实现可外部化对象(没有参数的公共构造函数)。

这使得 不可能创建非静态内部类Externalizable。问题是JVM在运行时修改了构造函数,并在编译时添加了对父类的引用。所以你不能有一个非静态内部类的默认无参数构造函数。

您还必须考虑将来修改对象的可能性(例如添加非瞬态字段)。可序列化类可能存在向后兼容性问题,但本身不需要更改代码。可外部化的类需要在您的读/写方法中更改代码,但有更多选项来处理兼容性问题。

还有一件事。如果您选择这种“技术”在不同应用程序之间进行通信,那么请不要这样做。你想要的是JAXB。它不那么紧凑,但更透明,没有兼容性问题,而且同样灵活。

隐藏功能

为了完整,还有一件事使这个主题变得更加复杂。实际上完全可以使用读/写方法而不使用Externalizable 接口。在引入 Externalizable 之前,可以定义 private writeObjectreadObject 方法。但实际上,你不应该再使用这种方法了。

【讨论】:

  • 感谢您在问题中提供的所有其他详细信息。
【解决方案2】:
public class Foo implements Externalizable{
    private long userID;
    private String userName;
    private char[] userPassword;
    private int age;

    private boolean shouldSavePassword;

    public void setSavePassword(boolean shouldSavePassword){
        this.shouldSavePassword = shouldSavePassword;
    }

    void writeExternal(ObjectOutput out) throws IOException{
        out.writeObject(userID);
        out.writeObject(userName);
        out.writeObject(shouldSavePassword);

        if(shouldSavePassword){
            out.writeObject(userPassword);
        }

        out.writeObject(age);
    }

    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
        userID = in.readLong();
        userName = (String) in.readObject();
        shouldSavePassword = readBoolean();

        if(shouldSavePassword){
            userPassword = (char[]) in.readObject();
        }

        age = in.readInt();
    }
}

请注意字段userPassword 是如何仅根据shouldSavePassword 的运行时值进行序列化的。如果您声明了该字段瞬态,则您已决定是否序列化编译时属性,无法在运行时更改(除非通过反射)。

Externalizable 的灵活性还允许您确定自己的序列化方案,在必要时加密敏感的对象。

另一个用例可能是将单向哈希附加到类的末尾以获得最大可靠性的选项。一个字段可以决定是否保存散列(因为它是额外的计算)。

底线是,transient 不会让您对对象的序列化方式进行任何运行时控制,只是该字段是否会被序列化(作为编译时参数)。


免责声明:上面给出的示例是保存密码的糟糕方案,请勿将其用于任何生产应用程序。明文密码在通过bcrypt、PBKDF#2、scrypt等PBPDF后保存。

【讨论】:

  • @Grateful 哦,伙计,我一定是困了。 implements 是我的意思,我改了。
  • 啊啊啊,好吧!谢谢你的例子。我现在很清楚了。
猜你喜欢
  • 2017-03-05
  • 1970-01-01
  • 1970-01-01
  • 2016-10-03
  • 2012-03-30
  • 2011-06-01
  • 2015-12-20
  • 1970-01-01
  • 2010-12-27
相关资源
最近更新 更多