【问题标题】:Is a deserialised object the same instance as the original反序列化对象是否与原始对象相同
【发布时间】:2015-03-14 17:14:52
【问题描述】:

当我从一个类实例化一个对象时,一个对象被保存在 java 堆中。当我通过序列化保存对象并稍后反序列化对象时,我是否正确理解该对象现在将具有新的堆地址但仍将是该类的完全相同的实例。

【问题讨论】:

  • originalObj == deserilized 将为 false,但 originalObj.equals(deserilized) 应为 true。
  • 如果我理解正确,答案是否定的,这两个引用将不会指向同一个对象 A
  • 如果你想扩展你的问题,不要在 cmets 中做,在问题正文中做。 cmets 不是长时间讨论或代码或任何格式化的地方。
  • @robbmj 这取决于 equals 方法的实现。对于许多类它有效,对于默认实现它不起作用。

标签: java serialization


【解决方案1】:

您的问题的答案不能只是是或否。需要对概念进行分析。我建议你拿铅笔和纸自己做,牢记以下几点。

  • 所有 java 对象都是在 java 堆中创建的(除了一些是 保留在池中,但对于您的问题,我们现在将跳过它们。
  • 当使用 new 关键字创建类的实例时, 反序列化,clone方法或者反射api的newInstance方法, 堆中的新空间被保留,我们将其分配给对象引用 (引用可以是对象的类或超类之一 对象类的类——同样我们可以忽略这个细节 现在)。
  • 当您保存对象时,对象的状态将与它的所有状态一起保存 嵌套对象。
  • 反序列化对象时,对象将创建一个新条目 在堆中,不会对任何对象有任何引用。

请看下图,在您的上下文中描绘上述概念:

所有对象 A 引用都指向一个堆条目,如果您尝试 objectB.getObjectA() == objectC.getObjectA() 或任何其他此类操作,您将得到 true。

案例 1 当您单独保存对象并在此处反序列化它们时,堆中会发生什么:

您现在可以知道 objectBcopy.getObjectA() == objectCcopy.getObjectA() 不会返回 true,因为对象 A 对复制对象的引用不再相同。

案例 2 相反,当您将对象保存在单个文件中并稍后对其进行反序列化时,堆中会发生以下情况:

您现在可以知道 objectBcopy.getObjectA() == objectCcopy.getObjectA() 现在将是 true,因为对象 A 副本的引用是相同的,但这仍然是对象 A 的新副本。

支持我的扣除的快速程序(案例 1 和案例 2):

public class Test{

    public static void main (String args[]) throws IOException, ClassNotFoundException{
        A a = new A();

        B b = new B();
        b.a = a;

        C c = new C();
        c.a = a;

        System.out.println("b.a == c.a is " + (b.a == c.a));

        // Case 1 - when two diferent files are used to write the objects
        FileOutputStream fout = new FileOutputStream("c:\\b.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fout);
        oos.writeObject(b);
        oos.close();
        fout.close();

        fout = new FileOutputStream("c:\\c.ser");
        oos = new ObjectOutputStream(fout);
        oos.writeObject(c);
        oos.close();
        fout.close();

        FileInputStream fileIn = new FileInputStream("c:\\b.ser");
        ObjectInputStream in = new ObjectInputStream(fileIn);
        B bCopy = (B) in.readObject();
        in.close();
        fileIn.close();

        fileIn = new FileInputStream("c:\\c.ser");
        in = new ObjectInputStream(fileIn);
        C cCopy = (C) in.readObject();
        in.close();
        fileIn.close();
        System.out.println("Case 1 - bCopy.a == cCopy.a is " + (bCopy.a == cCopy.a));

        // Case 2 - when both the objects are saved in the same file
        fout = new FileOutputStream("c:\\both.ser");
        oos = new ObjectOutputStream(fout);
        oos.writeObject(b);
        oos.writeObject(c);
        oos.close();
        fout.close();


        fileIn = new FileInputStream("c:\\both.ser");
        in = new ObjectInputStream(fileIn);
        bCopy = (B) in.readObject();
        cCopy = (C) in.readObject();
        in.close();
        fileIn.close();
        System.out.println("Case 2 - bCopy.a == cCopy.a is " + (bCopy.a == cCopy.a));
    }
}

class A implements Serializable{

}

class B implements Serializable{
    A a;
}

class C implements Serializable{
    A a;
}

输出如下:

 b.a == c.a is true
 Case 1 - bCopy.a == cCopy.a is false
 Case 2 - bCopy.a == cCopy.a is true

【讨论】:

  • "...但这仍然是对象 A 的新副本。"那么如果我更改了Object A Copy的一个字段(例如Age),那么对象A副本的变化不会反映在BCDE内部的Object A(s)中?
  • 没错。由于对象位于不同的堆地址中。如果回答对你有帮助,请采纳。
  • 在这种情况下,您能否建议一种方法,以便我可以完成我想要达到的目标,如上例所示?
  • 为此,您必须将 B、C、D 和 E 中的对象 A 的引用替换为已经存在的引用。为此,您可以通过实现自己的 readObject 方法来替换反序列化后对 Object A 的引用。
【解决方案2】:

序列化之前:

A originalA = ...;
B.a == C.a == D.a == E.a == originalA

所有B.aC.aD.aE.a都指向AoriginalA的同一个引用。

序列化和反序列化后:

A otherA = ...;
B.a == C.a == D.a == E.a == otherA

所有B.aC.aD.aE.a都指向AotherA的同一个引用。

但是:

originalA != otherA

虽然

originalA.equals(otherA) == true

注意: equals() 将返回 true,仅当它被覆盖一致地 根据序列化字段检查相等性。否则,它可能返回false


编辑:

证明:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Sample {

    static class A implements Serializable {
        private static final long serialVersionUID = 1L;
    }

    static class B implements Serializable {
        private static final long serialVersionUID = 1L;

        A a;
    }

    static class C implements Serializable {
        private static final long serialVersionUID = 1L;

        A a;
    }

    public static void main(String args[]) throws IOException, ClassNotFoundException {
        A originalA = new A();

        B b = new B();
        b.a = originalA;

        C c = new C();
        c.a = originalA;

        System.out.println("b.a == c.a is " + (b.a == c.a));

        FileOutputStream fout = new FileOutputStream("ser");
        ObjectOutputStream oos = new ObjectOutputStream(fout);
        oos.writeObject(b);
        oos.writeObject(c);
        oos.close();
        fout.close();

        FileInputStream fileIn = new FileInputStream("ser");
        ObjectInputStream in = new ObjectInputStream(fileIn);
        B bDeserialized = (B) in.readObject();
        C cDeserialized = (C) in.readObject();
        in.close();
        fileIn.close();

        System.out.println("bDeserialized.a == cDeserialized.a is " + (bDeserialized.a == cDeserialized.a));
    }
}

【讨论】:

  • 虽然 equals 契约在覆盖它时应该始终遵守,但返回 true 并不是必需的(毕竟,原始 O​​bject.equals 也遵守此契约)。重要的是,equals 以兼容的方式比较那些被序列化机制序列化/反序列化的字段。
【解决方案3】:

反序列化的实例肯定是与原始实例不同的实例,因为deserialized != original 将始终为真。

反序列化的实例可能等于也可能不等于原始实例,如deserialized.equals(original)。对于Serializable 类的合理实现,equals 可能在反序列化后为真,但创建一个不包含它的类是微不足道的:

class Pathological implements Serializable {
  transient int value;

  Pathological(int value) { this.value = value; }

  @Override public int hashCode() { return value; }

  @Override public boolean equals(Object other) {
    if (other == this) { return true; }
    if (other instanceof Pathological) {
      return ((Pathological) other).value == this.value;
    }
    return false;
  }
}

除非您在构造Pathological 时碰巧传递了零,否则在序列化/反序列化之后实例将不相等,因为value 的值不会被序列化(因为它是瞬态的)。

【讨论】:

    【解决方案4】:

    不,简单的答案是反序列化的对象不会是内存中的同一个实例!它将为其分配新内存。还可以通过 http://www.javalobby.org/java/forums/t17491.html 链接,其中包含使用单例反序列化检索对象的示例!还可以深入通过 readResolve() 方法,在某些情况下会有所帮助。

    【讨论】:

      【解决方案5】:

      不,它们不会是内存中的同一个对象,originalObj == deserilized 将为 false,但 originalObj.equals(deserilized) 应该为 true。

      对象 B、C、D 和 E。它们在实例化时都有对象 A。然后,假设我对它们进行了序列化和反序列化。当我在反序列化后更改对象 A 的字段时,有没有办法在 BCDE 中反映对象 A 的这种更改?

      如果我理解正确,答案是否定的,引用将不会指向同一个对象 A

      但是,如果您愿意,您可以在每个对象 B、C、D 和 E 中显式设置对象 A 的所有引用,以指向对象 A 的同一实例

      这里有一个演示来说明所提出的观点。

      import java.io.*;
      import java.util.*;
      
      public class Demo {  
        public static void main(String... aArguments) {  
          List<Quark> quarks = Arrays.asList(
            new Quark("up"), new Quark("down")
          );
      
          serialize(quarks);
          List<Quark> recoveredQuarks = deserialize();
      
          System.out.println(quarks == recoveredQuarks);               // false
          System.out.println(quarks.equals(recoveredQuarks));          // true
      
          System.out.println(quarks.get(0) == recoveredQuarks.get(0)); // false
      
          // but you can set it to the same instance
          recoveredQuarks.set(0, quarks.get(0));
          System.out.println(quarks.get(0) == recoveredQuarks.get(0)); // true
      
          quarks.get(0).name = "Charm";
          boolean b = quarks.get(0).name == recoveredQuarks.get(0).name;
          System.out.println(b);                                       // true
        }
      
        static void serialize(List<Quark> quarks) {
          try {
            OutputStream file = new FileOutputStream("quarks.ser");
            OutputStream buffer = new BufferedOutputStream(file);
            ObjectOutput output = new ObjectOutputStream(buffer);
            output.writeObject(quarks);
            output.close();
          }
          catch(IOException ex) { ex.printStackTrace(); }
        }
      
        static List<Quark> deserialize() {
          List<Quark> recoveredQuarks = null;
          try {
            InputStream file = new FileInputStream("quarks.ser");
            InputStream buffer = new BufferedInputStream(file);
            ObjectInput input = new ObjectInputStream(buffer);
            recoveredQuarks = (List<Quark>)input.readObject();
            input.close();
          } 
          catch(ClassNotFoundException ex){ }
          catch(IOException ex){ ex.printStackTrace(); }
          return recoveredQuarks;
        }
      }
      
      class Quark implements Serializable {
          String name;
          Quark(String name) {
            this.name = name;
          }
      
          @Override
          public boolean equals(Object o) {
            if (o != null && o instanceof Quark) {
              return this.name.equals(((Quark)o).name);
            }
            return false;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-25
        • 1970-01-01
        • 2022-07-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多