【问题标题】:Why is JVM creating a copy of an object when passing it to a method?为什么JVM在将对象传递给方法时会创建对象的副本?
【发布时间】:2009-12-04 22:10:49
【问题描述】:

我今天遇到了一个奇怪的问题:我有一个将两个 Date 对象作为参数的方法。调用方法传递了对与它们完全相同的对象的引用(有问题的方法是 EqualsBuilder.append)。第一个参数很好地传递了,但第二个参数没有。这是一个新的 Date 对象,它不同于第一个对象,因为除年月日以外的所有字段都设置为 0。请注意,我没有任何代码可以复制该对象... JVM 中的错误?

代码非常简单,我只注意到这是因为我的单元测试在比较应该是同一个对象时失败了(我用一些随机长初始化它)。

编辑

  • 我自己都不相信……
  • 我没有假设 JVM 中存在错误,我花了 4 个小时盯着这段代码并对其进行调试。
  • 我查看了调试器以确认它们是同一个对象(还将在星期一的调用方法中使用 == 进行测试)。
  • 我在 Windows XP 上使用 1.6.0_17
  • 我现在无法立即发布实际代码,将在星期一发布。

编辑 2

  • 重启eclipse后无法重现该bug
  • 我有 7 位目击者可以证明它发生了 :)
  • 其中一位目击者表示,在之前的演出中,他们遇到了这种程度的问题,并且他们每 3 年遇到一次此错误(或奇怪的行为)
  • 因此,我想我重现这种情况的可能性很小(我真希望我已经截屏了)

编辑 3

  • 这是相关类的代码:

    导入 java.util.Date; 导入 java.util.List;

导入 org.apache.commons.lang.builder.EqualsBuilder; 导入 org.apache.commons.lang.builder.HashCodeBuilder;

公共类 Foo {

private final long roadId;
private final Date creationDate;
private final Date editDate;
private final List<String> vehicleTypes;
private final boolean continuous;

public Foo(final long roadId, final Date creationDate, final Date editDate, final List<String> vehicleTypes, final boolean continuous) {
    super();
    this.roadId = roadId;
    this.creationDate = creationDate;
    this.editDate = editDate;
    this.vehicleTypes = vehicleTypes;
    this.continuous = continuous;
}

public long getRoadId() {
    return roadId;
}

public Date getCreationDate() {
    return creationDate;
}

public Date getEditDate() {
    return editDate;
}

public List<String> getVehicleTypes() {
    return vehicleTypes;
}

public boolean isContinuous() {
    return this.continuous;
}

@Override
public int hashCode() {
    final HashCodeBuilder builder = new HashCodeBuilder();
    builder.append(this.roadId);
    builder.append(this.creationDate);
    builder.append(this.editDate);
    builder.append(this.vehicleTypes);
    builder.append(this.continuous);        
    return builder.toHashCode();
}

@Override
public boolean equals(final Object obj) {
    if (this == obj) {
        return true;
    }
    if (!(obj instanceof Foo)) {
        return false;
    }
    final Foo other = (Foo)obj;
    EqualsBuilder builder = new EqualsBuilder();
    builder.append(this.roadId, other.roadId);
    builder.append(this.creationDate, other.creationDate);
    builder.append(this.editDate, other.editDate);
    builder.append(this.vehicleTypes, other.vehicleTypes);
    builder.append(this.continuous, other.continuous);
    return builder.isEquals();
}

}

  • 以及失败的单元测试: 导入 java.util.Arrays; 导入 java.util.Date; 导入 java.util.List;

导入静态 org.junit.Assert.*; 导入 org.junit.Before; 导入 org.junit.Test;

公共类FooTest {

private static final boolean CONTINUOUS = true;
private static final Date CREATION_DATE = new Date(12345678901L); 
private static final Date EDIT_DATE = new Date(987654321654321L);
private static final long ROAD_ID = 101;
private static final List<String> VEHICLE_TYPES = Arrays.<String> asList("TEST");
private Foo nonEmpty;    
private Foo otherNonEmpty;

@Before
public void setUp() {
    this.nonEmpty = new Foo(FooTest.ROAD_ID, FooTest.CREATION_DATE,
            FooTest.EDIT_DATE, FooTest.VEHICLE_TYPES, true);
    this.otherNonEmpty = new Foo(FooTest.ROAD_ID, FooTest.CREATION_DATE,
            FooTest.EDIT_DATE, FooTest.VEHICLE_TYPES, FooTest.CONTINUOUS);

}

@Test
public void testEquals() {
    assertTrue(this.nonEmpty.equals(this.otherNonEmpty));
}

}

现在,如果我改变这个:

private static final Date CREATION_DATE = new Date(12345678901L); 
private static final Date EDIT_DATE = new Date(987654321654321L);

到这里:

private static final Date CREATION_DATE = new Date(109,1,11); 
private static final Date EDIT_DATE = new Date(110,3,13);

效果很好。

我不认为代码有问题,尤其是在重新启动 JVM 后,所有测试都通过了。同时我知道JVM中不太可能存在错误(尽管它是一个软件并且没有软件是没有错误的)。

现在我已经用最初导致错误的构造函数检查了代码,也许我有幸再次遇到这种情况。感谢您的反馈。

【问题讨论】:

  • 发布代码比试图描述它要清晰得多。
  • 我不相信。我同意@Jonathan Feinberg 的观点。让我们看看代码。
  • 第三个。让我们看一些代码。
  • 您永远不应该首先想到“VM 中的错误”。要么您的代码错误,要么您对应该发生的事情的直觉与现实不符。
  • 更正:您有 7 位目击者说发生了一些事情。您和他们的问题是您(很可能)误解了实际发生的事情。如果没有具体、详细和可重复的证据(屏幕截图不足),就不可能说出真正发生的事情。非凡的陈述需要非凡的证据。

标签: java jvm


【解决方案1】:

您断言当对象作为方法参数传递时 JVM 正在复制对象,这是非同寻常的,并且(对我而言)在没有具体证据的情况下令人难以置信。请提供您认为表现出这种行为的调用/被调用方法的源代码,以及您的 JVM 版本和硬件/操作系统平台的详细信息。

"Extraordinary claims require extraordinary proof".


重启eclipse后无法重现该bug

这表明这是一个“Eclipse 怪异”问题。我预计 Eclipse 的怪异意味着您运行的代码与您正在查看的源代码不匹配。但我们永远无法确定...

【讨论】:

    【解决方案2】:

    在 Java 中,对象在传递给方法时永远不会被复制。在 Java 中,所有变量都是按值传递的,在对象的情况下,对对象的引用是按值传递的。 从不复制完成。

    【讨论】:

    • 永远不要说永远...我也不敢相信。
    • @Bartosz:在这种情况下,绝对可以说永不。你错了:)
    【解决方案3】:

    如果对象不同,它们一开始就不可能相同。我的怀疑是你认为你有两个对同一个对象的引用,但你有两个对象。你是如何确定你只有一个对象的?

    【讨论】:

      【解决方案4】:

      我将建议java.sql.Date 以某种方式混淆(基于声明,“除年月日之外的所有字段都设置为0”)。

      【讨论】:

        【解决方案5】:

        这段代码:

        public void testDates() {
            Date d = new Date();
        
            runTest(d, d);
        }
        
        private void runTest(Date a, Date b) {
            System.out.println(a +" " +b);
        }
        

        为我打印了这个结果:

        Fri Dec 04 22:14:28 GMT 2009 Fri Dec 04 22:14:28 GMT 2009
        

        这符合您所描述的情况吗? (显然结果不是)。 EqualsBuilder 的来源看起来并没有什么不寻常之处。

        【讨论】:

          猜你喜欢
          • 2012-09-01
          • 1970-01-01
          • 2023-03-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-09-02
          • 2017-05-25
          相关资源
          最近更新 更多