22

Consider the following test case using standard JUnit asserts and hamcrest's assertThat:

byte b = 0;
int i = 0;

assertEquals(b, i); // success
assertThat(b, equalTo(i)); // java.lang.AssertionError: Expected: <0> but: was <0>

if (b == i) {
    fail(); // test fails, so b == i is true for the JVM
}

Why is that so? The values are apparently equal for the JVM because b == i is true, so why does hamcrest fail?

sina
  • 1,817
  • 1
  • 18
  • 42
  • 6
    Because `Byte.valueOf((byte) 0).equals(Integer.valueOf(0))` is false. – assylias Nov 26 '15 at 15:08
  • 1
    As seen in *assylias*' example above, the byte gets auto-boxed into a Byte-object. As seen in the [Hamcrest's equalTo docs](http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/core/IsEqual.html#equalTo(T)) it uses the Object1.equals(Object2). Since both the byte and int are primitives, it auto-boxes them to Byte and Integer objects. Byte1.equals(Integer1) will return false, even though the values of these boxed object are the same. – Kevin Cruijssen Nov 26 '15 at 15:15

2 Answers2

29

Assert#assertThat is a generic method. Primitive types don't work with generics. In this case, the byte and int are boxed to Byte and Integer, respectively.

It then becomes (within assertThat)

Byte b = 0;
Integer i = 0;

b.equals(i);

Byte#equals(Object)'s implementation checks if the argument is of type Byte, returning false immediately if it isn't.

On the other hand, assertEquals is Assert#assertEquals(long, long) in which case both the byte and int arguments are promoted to long values. Internally, this uses == on two primitive long values which are equal.


Note that this boxing conversion works because assertThat is declared as

public static <T> void assertThat(T actual, Matcher<? super T> matcher) {

where the byte is boxed to a Byte for T, and the int is a boxed to an Integer (within the call to equalTo), but inferred as a Number to match the Matcher<? super T>.

This works with Java 8's improved generic inference. You'd need explicit type arguments to make it work in Java 7.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
13

This happens because the int and byte are boxed to Integer and Byte as hamcrest matchers operate on objects, not on primitives. So you are comparing an Integer with a Byte, and the implementation of Byte.equals() is:

public boolean equals(Object obj) {
    if (obj instanceof Byte) {
        return value == ((Byte)obj).byteValue();
    }
    return false;
}

and Integer.equals():

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

In other words, an Integer and Byte are always unequal. When comparing primitives, just use Assert.assertEquals instead. The hamcrest matchers are powerful, but mostly intended for (complex) object assertions.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • Is there any reason for Java to not check for values in the same range? E.g. `if (obj instanceof Integer) { return ((Integer)obj).intValue() == (int) value;}` in `Byte.equals()`? – sina Nov 26 '15 at 15:21
  • @sina Well, that's probably the reason we have primitives we can use instead. When Java compares two objects, it first checks if both objects are of the same type; if not, it simply returns false. `Integer` and `Byte` are objects, so the same applies to them. If `(new Byte(0)).equals(new Integer(0))` would return true it would be a bit strange to me. A comparable example would be if `(new Dog("Luke")).equals(new Cat("Luke"))` would return true, simply because they have the same name (I know, not the best example here, but then you see how strange it looks if it would return true). – Kevin Cruijssen Nov 26 '15 at 15:36
  • @KevinCruijssen It is a thing that you don't expect when boxing happens. One could argue that `Number` could require an equals that does allow `Integer` and `Byte` to be comparable, but that would probably again lead to unexpected behavior with `Float` and `Double` or very complex implementation to take into account 'other' number types. – Mark Rotteveel Nov 26 '15 at 15:52