Java外化与瞬态

时间:2015-06-30 06:56:41

标签: java transient externalizable

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

突出显示的文件如下,

  

如果通过实施一切都自动完成   Serializable接口,为什么有人愿意实现   可外化的界面和麻烦来定义两种方法?只是   完全控制过程。好的......我们来吧   示例来理解这一点。假设我们有一个对象   数百个字段(非瞬态),我们只需要几个字段   存储在持久存储中而不是全部存储。一种解决方案是   声明所有其他字段(我们要序列化的字段除外)为   瞬态和默认的序列化过程将自动进行   照顾好这一点。但是,如果那些少数领域没有固定的话怎么办?   设计tiime而不是在运行时有条件地决定。在   这样的情况,可能会实现Externalizable接口   是一个更好的解决方案同样,可能存在我们的情况   根本不想保持超类的状态(这是   由Serializable接口自动维护   实现)。

2 个答案:

答案 0 :(得分:4)

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

外化更快

在序列化期间,JVM将始终首先检查该类是否为Externalizable。如果是这种情况,那么它将使用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: [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关键字解决此问题? - 我会让你想出这个。当只使用transient关键字时,这种灵活性确实缺失。

设计考虑因素

然而,也有一些危险。 可外部化对象只能用于具有公共默认构造函数的对象(不带参数的公共构造函数)。

这使得无法制作非静态内部课程Externalizable 。问题是JVM在运行时修改构造函数,并在编译期间添加对父类的引用。因此,对于非静态内部类,您不能拥有默认的无参数构造函数。

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

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

隐藏的功能

只是完成,还有一件事让这个话题更复杂一些。实际上可以在不使用Externalizable接口的情况下使用读/写方法。在引入Externalizable之前,可以定义私有 writeObjectreadObject方法。但实际上,你不应该再使用那种方法了。

答案 1 :(得分: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没有给你任何关于如何完成对象序列化的运行时控制,只是该字段将被序列化或不被序列化(作为编译时参数)。

免责声明:上面给出的示例是保存密码的可怕方案,不要将其用于任何生产应用程序。明文密码应在通过PBPDF(如bcrypt,PBKDF#2和scrypt)后保存。