3

I have created default methods in an interface for implementing equals(Object) and hashCode() in predictable manner. I use reflection to iterate all fields in a type (class) to extract the values and compare them. The code depends on Apache Commons Lang with its HashCodeBuilder and EqualsBuilder.

The thing is that my tests shows me that the first time I call on of these methods, it takes a lot more time in the first call. The timer uses System.nanoTime(). Here is an example from the logs:

Time spent hashCode: 192444
Time spent hashCode: 45453
Time spent hashCode: 48386
Time spent hashCode: 50951

The actual code:

public interface HashAndEquals {

    default <T> int getHashCode(final T type) {
        final List<Field> fields = Arrays.asList(type.getClass().getDeclaredFields());
        final HashCodeBuilder builder = new HashCodeBuilder(31, 7);
        fields.forEach( f -> {
            try {
                f.setAccessible(true);
                builder.append(f.get(type));
            } catch (IllegalAccessException e) {
                throw new GenericException(e.toString(), 500);
            }
        });
        return builder.toHashCode();
    }

    default <T, K> boolean isEqual(final T current, final K other) {
        if(current == null || other == null) {
            return false;
        }
        final List<Field> currentFields = Arrays.asList(current.getClass().getDeclaredFields());
        final List<Field> otherFields = Arrays.asList(other.getClass().getDeclaredFields());
        final IsEqual isEqual = new IsEqual();
        isEqual.setValue(true);
        currentFields.forEach(c -> otherFields.forEach(o -> {
            c.setAccessible(true);
            o.setAccessible(true);
            try {
                if (o.getName().equals(c.getName())) {
                    if (!o.get(other).equals(c.get(current))) {
                        isEqual.setValue(false);
                    }
                }
            } catch (IllegalAccessException e) {
                isEqual.setValue(false);
            }
        }));
        return isEqual.getValue();
    }
}

How these methods are used to implement hashCode and equals:

@Override
public int hashCode() {
    return getHashCode(this);
}

@Override
public boolean equals(Object obj) {
    return obj instanceof Step && isEqual(this, obj);
}

Example of a test:

    @Test
public void testEqualsAndHashCode() throws Exception {
    Step step1 = new Step(1, Type.DISPLAY, "header 1", "description");
    Step step2 = new Step(1, Type.DISPLAY, "header 1", "description");
    Step step3 = new Step(2, Type.DISPLAY, "header 2", "description");
    int times = 1000;
    long total = 0;

    for(int i = 0; i < times; i++) {
        long start = System.nanoTime();
        boolean equalsTrue = step1.equals(step2);
        long time = System.nanoTime() - start;
        total += time;
        System.out.println("Time spent: " + time);
        assertTrue( equalsTrue );
    }
    System.out.println("Average time: " + total / times);

    for(int i = 0; i < times; i++) {
        assertEquals( step1.hashCode(), step2.hashCode() );
        long start = System.nanoTime();
        System.out.println(step1.hashCode() + " = " + step2.hashCode());
        System.out.println("Time spent hashCode: " + (System.nanoTime() - start));
    }
    assertFalse( step1.equals(step3) );
} 

The reason for putting these methods in an interface is to be as flexible as possible. Some of my classes may need inheritance.

My test indicate that I can trust that both hashcode and equals always returns the same value for objects with the same internal state.

What I'd like to know is if I am missing something. And if the behavior of these methods can be trusted? (I know project Lombok and AutoValue offers some help to implement these methods, but my client are not too keen on these libraries).

Any insight as to why it always takes approximately 5 times longer to perform the method call the first time would also be very helpful.

Nissa
  • 4,636
  • 8
  • 29
  • 37
thomas77
  • 1,100
  • 13
  • 27

1 Answers1

9

There is nothing special with default methods here. The first time you invoke a method on a formerly unused class, the invocation will trigger class loading, verification, and initialization of the class and the method’s execution will start in interpreted mode before the JIT compiler/ hotspot optimizer will kick in. In the case of an interface, it will be loaded and some of the verification steps performed when a class implementing it has been initialized, however the other steps are still being deferred until it will be actually used, in your case when a default method of the interface is invoked for the first time.

It is a normal phenomenon in Java that a first time execution takes more time than subsequent executions. In your case, you are using lambda expressions which have an additional first time overhead when the functional interface implementation will be generated at runtime.

Note that your code is a common antipattern that exists longer than default methods. There is no is-a relationship between HashAndEquals and the class “implementing” it. You can (and should) provide these two utility method as static methods in a dedicated class instead and use import static if you want to invoke these methods without prepending the declaring class.

There is no benefit in inheriting these methods from an interface. After all, each class has to override Object.hashCode and Object.equals anyway and can choose deliberately whether to use these utility methods or not.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thank you for a great answer. Also, thank you for pointing out the missing `is-a` relationship. I also try to favor composition over inheritance, but in this instance I failed miserably :). Since lamda expressions generates a functional interface, would you recommend using a plain old for-loop for performance? – thomas77 Sep 30 '14 at 15:27
  • I wouldn't call this an antipattern in this specific case, since it's not applied to a domain concept: `equals` and `hashCode` are not functions that are part of the "domain" of the class, but rather technical requirements. By using `default` methods, one uses syntactic sugar (mix-in) to add a purely technical requirement to these classes. It's much shorter (read: DRY) to write `implements HashAndEquals` than to copy-and-paste override `equals` and `hashCode` in every single class. – Alexander Langer Sep 30 '14 at 19:49
  • 1
    @Alexander Langer : it seems you have missed the important aspect that `interface` `default` methods can not override methods inherited from `java.lang.Object`. That’s why I wrote “each class has to override `Object.hashCode` and `Object.equals` anyway”. In fact, the intended use is *exactly* to copy-and-paste `equals` and `hashCode` methods (the implementations calling the `default` methods) in every single class. The methods inherited from the `interface` are not meant to be called from the outside but merely an implementation artifact exported via an `interface`, which is an anti-pattern. – Holger Sep 30 '14 at 20:12
  • yes, in fact, I didn't realize that the `default` methods can't provide the implementations, but I actually assumed that your "is-a" also meant normal inheritance, since you wrote "that exists longer than `default` methods". – Alexander Langer Sep 30 '14 at 21:08
  • 1
    @Alexander Langer: let me say it this way: if you can provide a working `equals`/`hashCode` in a base class, it might be called `ValueType` and the relationship, e.g. `ComplexNumber` *is-a* `ValueType` would justify an inheritance. I still wouldn’t recommend such default Reflection-based implementation though but that was not the anti-pattern I was referring to. In the past, `interface`s were abused to declare constants which were imported by implementing the particular `interface` only for using the constants inside the implementation, just like these `default` methods of this question. – Holger Sep 30 '14 at 21:17
  • ah, thanks for the clarification. Yes, that's indeed an antipattern. – Alexander Langer Sep 30 '14 at 21:26
  • 1
    @thomas77: regarding your performance question, I would not blame the lambda expressions. First, it’s the overhead of Reflection which will dominate. Second, in your `isEqual` method you have two nested iterations, which implies that you are performing n×m operations. I recommend to insist that only objects of the same class can be equal and thus, you only have to iterate over one array of `Field`s and just compare the actual values of the two instances for the same field. – Holger Oct 01 '14 at 07:38