Computing Thoughts

Bruce Eckel's Programming Blog

Feb 4, 2021 - 6 minute read

Java Object Equivalence

This is an update to the subsection “Testing Object Equivalence” in the “Operators” chapter of On Java 8. This will appear in the book in its next update.

The relational operators == and != work with all objects, but their results can be confusing:

// operators/Equivalence.java

public class Equivalence {
  static void show(String desc, Integer n1, Integer n2) {
    System.out.println(desc + ":");
    System.out.printf(
      "%d==%d %b %b%n", n1, n2, n1 == n2, n1.equals(n2));
  }
  @SuppressWarnings("deprecation")
  public static void test(int value) {
    Integer i1 = value;                             // [1]
    Integer i2 = value;
    show("Automatic", i1, i2);
    // Old way, deprecated in Java 9 and on:
    Integer r1 = new Integer(value);                // [2]
    Integer r2 = new Integer(value);
    show("new Integer()", r1, r2);
    // Preferred in Java 9 and on:
    Integer v1 = Integer.valueOf(value);            // [3]
    Integer v2 = Integer.valueOf(value);
    show("Integer.valueOf()", v1, v2);
    // Primitives can't use equals():
    int x = value;                                  // [4]
    int y = value;
    // x.equals(y); // Doesn't compile
    System.out.println("Primitive int:");
    System.out.printf("%d==%d %b%n", x, y, x == y);
  }
  public static void main(String[] args) {
    test(127);
    test(128);
  }
}
/* Output:
Automatic:
127==127 true true
new Integer():
127==127 false true
Integer.valueOf():
127==127 true true
Primitive int:
127==127 true
Automatic:
128==128 false true
new Integer():
128==128 false true
Integer.valueOf():
128==128 false true
Primitive int:
128==128 true
*/

show() compares the behavior of == against the method equals() that exists for all objects. The printf() format argument uses the specifier %d for int output, %b for Boolean output, and %n to produce a newline.

For “not equals” comparisons, use n1 != n2 and !n1.equals(n2).

In test(), integer-valued objects are created in four different ways:

  • [1]: Automatic conversion to Integer. These are translated into calls to Integer.valueOf().
  • [2]: Using standard new object-creation syntax. Originally this was the preferred way to create “wrapped/boxed” Integer objects.
  • [3]: Starting with Java 9, valueOf() is preferred over [2]. If you try to use form [2] with Java 9, you’ll get a warning and a suggestion to use [3] instead. It is difficult to determine whether [3] is preferred over [1], and [1] seems much cleaner.
  • [4]: As primitive ints.

The @SuppressWarnings("deprecation") is not necessary for Java 8, but is included in case you compile the code with Java 9 or newer.

For a value of 127, the comparisons produce the results you expect, except form [2] which produces false for ==. Although the contents of the objects are the same, the references point to different objects in memory. The operators == and != compare object references, and have different behavior depending on how the Integer objects are created—presumably, [1] and [3] yield Integers that point to the same storage in memory. Integer values from -128 through 127 produce this behavior for == and !=, but outside that range they do not, as seen with test(128).

If you’re using Integer you must only use equals(). If you accidentally use == and != for Integer and don’t test it for values outside -128 through 127, your code will pass but will be quietly broken. If you’re using primitive ints then you can’t use equals() and must use == and !=. This can cause problems if you start by using ints and then later change to Integers, or vice-versa.

In Java 9 and on, the use of new Integer() is deprecated because it is so much less efficient than Integer.valueOf(). Thus, you should avoid new Integer(), new Double(), etc. in Java 8 as well. Deprecating something for efficiency reasons is not something I’ve seen before (it might have happened, but this is the first time I’ve been aware of it).

When working with floating-point numbers, you encounter different equivalence problems, not because of Java but because of the nature of floating-point numbers:

// operators/DoubleEquivalence.java

public class DoubleEquivalence {
  static void show(String desc, Double n1, Double n2) {
    System.out.println(desc + ":");
    System.out.printf(
      "%e==%e %b %b%n", n1, n2, n1 == n2, n1.equals(n2));
  }
  @SuppressWarnings("deprecation")
  public static void test(double x1, double x2) {
    // x1.equals(x2) // Won't compile
    System.out.printf("%e==%e %b%n", x1, x2, x1 == x2);
    Double d1 = x1;
    Double d2 = x2;
    show("Automatic", d1, d2);
    Double r1 = new Double(x1);
    Double r2 = new Double(x2);
    show("new Double()", r1, r2);
    Double v1 = Double.valueOf(x1);
    Double v2 = Double.valueOf(x2);
    show("Double.valueOf()", v1, v2);
  }
  public static void main(String[] args) {
    test(0, Double.MIN_VALUE);
    System.out.println("------------------------");
    test(Double.MAX_VALUE,
      Double.MAX_VALUE - Double.MIN_VALUE * 1_000_000);
  }
}
/* Output:
0.000000e+00==4.900000e-324 false
Automatic:
0.000000e+00==4.900000e-324 false false
new Double():
0.000000e+00==4.900000e-324 false false
Double.valueOf():
0.000000e+00==4.900000e-324 false false
------------------------
1.797693e+308==1.797693e+308 true
Automatic:
1.797693e+308==1.797693e+308 false true
new Double():
1.797693e+308==1.797693e+308 false true
Double.valueOf():
1.797693e+308==1.797693e+308 false true
*/

On paper, the comparison of floating point numbers should be very strict—a number that is the tiniest fraction different from another number should still be unequal. This works for the call to test(0, Double.MIN_VALUE), where Double.MIN_VALUE is the smallest representable value. (%e in the printf() call presents the results in exponential notation).

However, this does not hold true for the second test() call, where the argument x2 is the value of x1 minus one million times Double.MIN_VALUE. It seems like x2 should be significantly different than x1, but the two numbers still compare as equal. You encounter this issue in virtually every programming language because when a variable holds a very large number, subtracting a relatively small number won’t make a significant difference. This is called a rounding error and occurs because the machine cannot hold enough information to represent a tiny change to a large number.

You might be fooled into thinking that using == produces a correct result in this case, but it does not—it is simply comparing references.

Using equals() when you’re not working with primitives seems like the straightforward answer, but it’s not as simple as that. Consider class ValA:

// operators/EqualsMethod.java
// Default equals() does not compare contents

class ValA {
  int i;
}

class ValB {
  int i;
  // Works for this example, not a complete equals():
  public boolean equals(Object o) {
    ValB rval = (ValB)o;  // Cast o to be a ValB
    return i == rval.i;
  }
}

public class EqualsMethod {
  public static void main(String[] args) {
    ValA va1 = new ValA();
    ValA va2 = new ValA();
    va1.i = va2.i = 100;
    System.out.println(va1.equals(va2));
    ValB vb1 = new ValB();
    ValB vb2 = new ValB();
    vb1.i = vb2.i = 100;
    System.out.println(vb1.equals(vb2));
  }
}
/* Output:
false
true
*/

In main(), va1 and va2 contain identical values for i, but the result of comparing them using equals() is false, which is confusing again. This happens because the default behavior of equals() is to compare references. To produce the desired behavior of comparing the contents, you must override equals() as in class ValB. ValB.equals() contains only the minimum necessary code to solve the problem for this example, but it is not a proper equals(). Note that the standard argument for equals() is an Object (not a ValB), so we must force o to be a ValB using a cast: (ValB)o (ordinarily you must check the type first before casting, but we’ll skip that until a later chapter). Then we can compare the two values of i, using == because they are primitives.

Most of the standard library classes override equals() to compare the contents of objects instead of their references.