为什么JVM在将对象传递给方法时会创建对象的副本?

时间:2009-12-04 22:10:49

标签: java jvm

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

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

编辑

  • 我不相信自己......
  • 我没有假设JVM中的错误,我花了4个小时盯着这段代码并进行调试。
  • 我查看调试器以验证它们是否是同一个对象(也将在星期一的调用方法中使用==进行测试)。
  • 我在Windows XP上使用1.6.0_17
  • 我不能在这一秒发布实际代码,将在星期一这样做。

编辑2

  • 重新启动eclipse后,我无法重现错误
  • 我有7个目击证人可以证明它发生了:)
  • 对那些目击者说,在之前的一次演出中,他们遇到了那种程度的事情,他们在3年内遇到过这个错误(或奇怪的行为)
  • 因此,我想我再现这种情况的可能性很小(我真的希望我拍了截图)

编辑3

  • 以下是相关课程的代码:

    import java.util.Date; import java.util.List;

import org.apache.commons.lang.builder.EqualsBuilder; import 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();
}

}

  • 单元测试失败: import java.util.Arrays; import java.util.Date; import java.util.List;

import static org.junit.Assert。*; import org.junit.Before; import 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中存在一个错误的可能性极小(尽管它是一个软件而且没有软件没有错误)。

现在我已经使用导致错误的构造函数检查了代码,或许我很幸运能够再次遇到错误。感谢您的反馈。

5 个答案:

答案 0 :(得分:3)

当JVM作为方法参数传递时,JVM正在复制对象的断言是非常的,并且(对我而言)没有一些具体证据令人难以置信。请提供您认为具有此行为的调用/调用方法的源代码,以及JVM版本和硬件/ OS平台的详细信息。

"Extraordinary claims require extraordinary proof"


  

重新启动eclipse后,我无法重现错误

这表明这是一个“Eclipse古怪”问题。我希望Eclipse怪异意味着您运行的代码与您正在查看的源代码不匹配。但我们永远不会知道......

答案 1 :(得分:2)

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

答案 2 :(得分:1)

如果对象不同,它们首先不能相同。我怀疑你认为你有两个对同一个对象的引用,但你有两个对象。你是怎么确定你只有一个物体的?

答案 3 :(得分:1)

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

答案 4 :(得分:0)

此代码:

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的来源看起来并不像它有任何异常。