反序列化对象与原始对象是同一个实例吗?

22

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


7
originalObj == deserilized 会返回 false,但是 originalObj.equals(deserilized) 应该返回 true。 - robbmj
如果我理解正确,答案是否定的,这两个引用不会指向同一个对象A。 - robbmj
2
如果您想要扩展您的问题,请不要在评论中进行,而是在问题正文中进行。评论不是用于长时间讨论、代码或任何格式化内容的地方。 - RealSkeptic
1
可能是序列化是否保留对象标识?的重复问题。 - RealSkeptic
1
@robbmj 这取决于equals方法的实现。对于许多类来说,它是有效的;但对于默认实现来说,它不起作用。 - Paŭlo Ebermann
显示剩余3条评论
5个回答

25

你的问题的答案不能仅仅是肯定或否定。需要分析这个概念。我建议你拿起一支铅笔和纸,按照以下要点自己操作。

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

看下面的图表以便更好地理解上述概念:

enter image description here

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

情况1 当您单独保存对象并反序列化它们时,在堆中会发生以下情况:

enter image description here

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

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

enter image description here

现在你可以发现,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的新副本。”因此,如果我更改对象A副本的字段(例如年龄),则不会在BCDE内部的对象A中反映出对象A副本中的更改。 - Charlie Olson
1
这是正确的。因为对象在不同的堆地址中。如果答案对您有帮助,请接受相同。 - Anshuman
在这种情况下,您能否建议一种方法,以便我可以像上面的示例所示实现我想要实现的目标? - Charlie Olson
1
为此,您将不得不在B、C、D和E中用已经存在的对象A替换其引用。为此,您可以通过实现自己的readObject方法,在反序列化后替换对象A的引用。 - Anshuman

3

序列化之前:

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.a, C.a, D.aE.a指向同一个引用A,即otherA

然而:

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));
    }
}

1
在覆盖equals方法时,应始终遵守等式契约,但并不需要返回true(毕竟,原始的Object.equals也遵守此契约)。重要的是,equals比较由序列化机制序列化/反序列化的那些字段,以兼容的方式进行比较。 - Paŭlo Ebermann

2
反序列化后的实例一定与原始实例不同,即deserialized != original始终为真。
反序列化后的实例可能与原始实例相等也可能不相等,即deserialized.equals(original)。对于一个合理的可序列化类的实现,在反序列化后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”的值不会被序列化(因为它是瞬态的)。

1
不,它们在内存中不会是相同的对象。originalObj == deserilized将为false,但是originalObj.equals(deserilized)应该为true。
如果我理解正确,答案是否定的,引用不会指向相同的Object A。
但是,如果您愿意,可以显式设置每个对象B、C、D和E中Object A的所有引用,使其指向同一个Object 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;
    }
}

1
不,简单的回答是反序列化后的对象在内存中将不是同一实例!它将为相同的分配新的内存。还请查看http://www.javalobby.org/java/forums/t17491.html链接,其中包含使用单例进行反序列化检索对象的示例!还请深入了解readResolve()方法,在某些情况下会有帮助。

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