4

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

强调这一点的文件如下,

如果一切都是通过实现 Serializable 接口来自动处理的,为什么会有人喜欢实现 Externalizable 接口并费心去定义这两个方法呢?只需对过程进行完全控制。好的......让我们举个例子来理解这一点。假设我们有一个具有数百个字段(非瞬态)的对象,并且我们希望仅将少数字段存储在持久存储上,而不是全部。一种解决方案是将所有其他字段(除了我们想要序列化的字段)声明为瞬态,默认的序列化过程将自动处理这一点。但是,如果这几个字段在设计时不是固定的,而是在运行时有条件地决定。在这种情况下,实现 Externalizable 接口可能是一个更好的解决方案。

4

2 回答 2

6

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

外化更快

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

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

外化输出更紧凑

如果您比较实际输出,它看起来像这样:对象的标头包含一个标志,该标志标记该类是 justSerializable还是 may also 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());
    }
  }
  ...
}

So, in some cases the price is transient and in some cases it is not. How would you solve this with the transient keyword ? -- I will let you figure this one out. This kind of flexibility is really missing when using only the transient keyword.

Design considerations

However, there are some dangers as well. Externalizable objects can only be implemented for objects with a public default constructor (a public constructor without arguments).

That makes it impossible to make non-static inner classes Externalizable. The problem is that JVM modifies the constructors at runtime, and adds a reference to the parent class during compilation. So you cannot have a default no-argument constructor for a non-static inner class.

You also have to consider the possibility of modifying your object in future (e.g. adding non-transient fields). Serializable classes could have backwards compatibility issues, but don't require code changes per se. Externalizable classes will require a code change in your read/write method, but have more options to handle compatibility issues.

Just one more thing. If you are chosing this "technology" to communicate between different applications, then please just don't. What you want is JAXB. It's less compact, but more transparent, no compatibility issues, and just as flexible.

Hidden features

Just to be complete, there is one more thing which makes this topic a bit more complicated. It's actually possible to use read/write methods without using the Externalizable interface at all. Before Externalizable was introduced, it was possible to define private writeObject and readObject methods. But really, you shouldn't use that method anymore.

于 2015-06-30T08:07:58.823 回答
4
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 后保存。

于 2015-06-30T07:14:54.577 回答