tags:

views:

203

answers:

5

I encountered a strange problem today: i had a method that took two Date objects as arguments. The calling method passed reference to the very same object as both of them (the method in question was EqualsBuilder.append). The first parameter got passed fine, but the second one not. It was a new Date object that was different from the first one in the sense that all the fields except for year month and day were set to 0. Note that I didn't have any code that would copy the object... Is that a bug in the JVM?

The code was pretty straighforward and I only noticed that because my unit test failed on comparison of what supposed to be the very same object (i initialized it with some random long).

Edit:

  • I did not believe that myself...
  • I did not assume bug in JVM, I spent literally 4 hours staring at this code and debugging it.
  • I looked in the debugger to verify that they are the same object (will also test with == in the calling method on Monday).
  • I use 1.6.0_17 on Windows XP
  • I can't post the actual code right this second, will do that on Monday.

Edit 2:

  • After restarting eclipse, I cannot reproduce the bug
  • I have 7 eye witnesses that can testify that it happened :)
  • on of those witnesses said that on a previous gig they encountered something to that extent, and that they encountered this bug (or weird behaviour) once in 3 years
  • hence, I guess that my odds of reproducing the situation are pretty slim (I really wish I had taken screenshots)

Edit 3:

  • Here's the code for the class in question:

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

import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder;

public class 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();
}

}

  • And the unit test that failed: 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;

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

}

Now, if I changed this:

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

to this:

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

it worked fine.

I don't think that the code is wrong, especially that after restarting the JVM all the tests passed. At the same time I know that it's highly unlikely that there is a bug in the JVM (although it is a piece of software and the no software is bug-free).

Right now I have checked in the code with the constructor that caused errors in the first place, perhaps I will be lucky enough to encounter that again. Thanks for feedback.

A: 

This code:

public void testDates() {
 Date d = new Date();

 runTest(d, d);
}

private void runTest(Date a, Date b) {
 System.out.println(a +" " +b);
}

printed this result for me:

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

Does that fit the situation you're describing? (Obviously the result doesn't). The source of EqualsBuilder doesn't look like there's anything unusual about it.

rich
it dire does. and of course the result does not :(
Bartosz Radaczyński
+1  A: 

If the objects are different they cannot be the same in the first place. My suspicion is that you THINK you have two references to the same object, but you have two objects. How did you determine you only had one object?

Thorbjørn Ravn Andersen
+2  A: 

In Java objects are never copied when passed into methods. In Java all variables are passed by value, and in the case of objects the reference to the object is passed in by value. There is never copying done.

Jeremy Raymond
never say never... I could not believe that either.
Bartosz Radaczyński
@Bartosz: in this case it is perfectly ok to say never. You are wrong :)
Fredrik
+2  A: 

Your assertion that a JVM is copying an object when it is passed as a method argument is extraordinary, and (to me) unbelievable without some concrete evidence. Please provide the source code of the calling / called methods that you believe exhibits this behavior, and details of your JVM version and hardware / OS platform.

"Extraordinary claims require extraordinary proof".

Stephen C
+1  A: 

I'm going to suggest that java.sql.Date is mixed up in this somehow (based on the statement, "all the fields except for year month and day were set to 0").

Ross