Justin
Justin

Reputation: 4216

Comparing "T extends Number" for equality

I have a matrix class which takes in a generic object which extends Number.

For example:

public class Matrix<T extends Number>

I am trying to compare two matrices which have the same values:

Matrix:
row=[0] 273 455 
row=[1] 243 235 
row=[2] 244 205 
row=[3] 102 160 

and

Matrix:
row=[0] 273 455 
row=[1] 243 235 
row=[2] 244 205 
row=[3] 102 160 

In the Matrix class, I have a equals method which looks like this:

public boolean equals(Object obj) {
    if (obj == null)
        return false;
    if (!(obj instanceof Matrix))
        return false;

    Matrix<T> m = (Matrix<T>) obj;
    if (this.rows != m.rows)
        return false;
    if (this.cols != m.cols)
        return false;
    for (int i=0; i<matrix.length; i++) {
        T t1 = matrix[i];
        T t2 = m.matrix[i];
        if (!t1.equals(t2))
            return false;
    }
    return true;
}

This line is failing:

t1.equals(t2)

even when the two numbers are equal. e.g. "273" and "273"

When I debug the equals method, it is failing because it is assuming the numbers are Longs:

This is from Java SDK Long.class:

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

Essentially, it fails because the obj isn't an instance of Long.

I can easily change my equals method to do:

        if (t1.longValue()!=t2.longValue())
            return false;

But I am wonder what is the correct way to check for equality in this situation and why the equals method on the generic T is assuming it's a Long.

EDIT:

My testing code is defining ''Matrix generic type of Integer'' which makes the equality testing (which is comparing using Long) strange to me.

Testing code:

    Matrix<Integer> matrix1 = new Matrix<Integer>(4, 3);
    matrix1.set(0, 0, 14);
    matrix1.set(0, 1, 9);
    matrix1.set(0, 2, 3);
    matrix1.set(1, 0, 2);
    matrix1.set(1, 1, 11);
    matrix1.set(1, 2, 15);
    matrix1.set(2, 0, 0);
    matrix1.set(2, 1, 12);
    matrix1.set(2, 2, 17);
    matrix1.set(3, 0, 5);
    matrix1.set(3, 1, 2);
    matrix1.set(3, 2, 3);

    Matrix<Integer> matrix2 = new Matrix<Integer>(3, 2);
    matrix2.set(0, 0, 12);
    matrix2.set(0, 1, 25);
    matrix2.set(1, 0, 9);
    matrix2.set(1, 1, 10);
    matrix2.set(2, 0, 8);
    matrix2.set(2, 1, 5);

    Matrix<Integer> result1 = new Matrix<Integer>(4,2);
    result1.set(0, 0, 273);
    result1.set(0, 1, 455);
    result1.set(1, 0, 243);
    result1.set(1, 1, 235);
    result1.set(2, 0, 244);
    result1.set(2, 1, 205);
    result1.set(3, 0, 102);
    result1.set(3, 1, 160);

    Matrix<Integer> matrix3 = matrix1.multiply(matrix2);
    if (!matrix3.equals(result1)) {
        System.err.println("Matrix multiplication error. matrix3="+matrix3+" result1"+result1);
        return false;
    }

Here is the link to the Matrix code without the equals() method defined. I haven't checked in the equals() code yet.

Upvotes: 4

Views: 731

Answers (4)

Thomas
Thomas

Reputation: 88707

...why the equals method on the generic T is assuming it's a Long.

The reason is simple: Assuming the matrix you're testing with is of type Matrix<Long>, then t1 is an instance of Long (the generic type just allows you to use Long here and has no relevance at runtime) and thus Long.equals() would be called.

In the following case Integer.equals() should be called:

Matrix<Integer> m1 = ...;
Matrix<Long> m2 = ...;

m1.equals( m2 ); 

Since members of m1 are of type Integer, the call t1.equals(t2) would have the signature Integer.equals(Long).

So what could you do to be able to get two matrices of different types but with equal values to be equal?

The general problem would be that you should use compareTo() to check for value equality (since in some cases like BigDecimal mathematically equal values like 2.0 and 2.00 would not result in equals() returing true.

Unfortunately using T extends Number & Comparable<T> would not be an option (see the comments for reference, as well as here: Why doesn't java.lang.Number implement Comparable?), because you would not be able to call Long.compareTo(Integer) that way.

Thus you'd either have to fall back to primitive values and distinguish between integer and floating point values (thus calling t1.longValue() or t1.doubleValue()) or use a Comparator<Number> whose implementation of compareTo(Number lhs, Number rhs) would handle that. (There should be ready to use Comparators like this: http://www.jidesoft.com/javadoc/com/jidesoft/comparator/NumberComparator.html).

If you want to support larger numbers like BigInteger and BigDecimal you could also consider using BigDecimal and create an instance for every value. This should result in some flexibility but would also incur some performance cost. (Disclaimer: this is just an untested idea so don't just take it as is, it is just meant to provide some input for your own thought process).

Upvotes: -1

ajb
ajb

Reputation: 31699

The reason that the program is using Long.equals, even though all your test code uses Matrix<Integer>, is simply that you're storing a Long in it. The code has this:

public class Matrix<T extends Number> {

    private T[] matrix = null;

and then in the constructor:

    this.matrix = (T[]) new Number[rows * cols];

which of course creates an array of null references. But when you create the array using multiply,

Long result = 0l;
for (int i = 0; i < cols; i++) {
     Long l = row[i].longValue() * column[i].longValue();
     result += l;
}
output.set(r, c, (T) result);

where set looks like

public void set(int row, int col, T value) {
    matrix[getIndex(row, col)] = value;
}

The thing is, that even though you tried to put a (T) cast on the result, it doesn't do anything. Note that the language doesn't let you cast between Long and Integer types:

Long x = 3L;
Integer y = 4;
x = (Long)y;     // illegal
y = (Integer)x;  // illegal

Because of type erasure, the compiler doesn't try to check the cast to (T), but displays an unchecked warning if you don't suppress warnings. (It checks to make sure that the thing you're casting is some Number, but that's all.) It does not generate any code that would do a conversion. Thus, even if the multiply method is called on Matrix<Integer>, the code will not try to convert your Long to an Integer. The cast has no effect, and the result is that a reference to a Long is stored in your matrix array. (This runs OK because the code cannot check to make sure that the type is the same as T, again due to type erasure.) So then later, when you use matrix[i].equals, since a Long is stored in the matrix, Long.equals is called.

Unfortunately, I don't think there's a good way to convert a number to an object of some Number class that isn't known at compile time. You could pass T's class as a parameter to the constructor, and then use reflection to try to find a constructor for that class that takes a long parameter, but that's ugly.

Upvotes: 3

Durandal
Durandal

Reputation: 20059

Unfortunately, there is no way to check for value equality of Number implementations.

The closest thing you can generically do is:

public static boolean valueEquals(Number n1, Number n2) {
    return n1.longValue() == n2.longValue() && n1.doubleValue() == n2.doubleValue();
}

This method will return sane results for all primitive wrapper types, but not for all instances of BigDecimal, BigInteger and anything else that offers precision beyond the limits of long/double. The reason both longValue and doubleValue are compared is that if you used only longValue, 1L and 1.1D would be detected as equal (due to truncation in Double.longValue()), while if you used just doubleValue, there would be multiple distinct long values that match a single double (e.g. integer values in the range of 2^53 to 2^63 can be exactly represented as longs, but they do get rounded in the least significant bits when represented as double).

Upvotes: 2

f1sh
f1sh

Reputation: 11934

Since you already know that the values are not always instances of Long, you have to compare the numeric values yourself as you already found out.

To answer your question "I wonder [...] why the equals method on the generic T is assuming it's a Long": Because it is one. When calling t1.equals(t2) with t1 being a Long, the equality check is done by Long.equals(Object). And that results in false if the parameter is not of the same type.

Since you cant be sure which type "arrives" in your equals method, you should implement a Comparator that can handle all the possible types. For instance: How do you compare an Integer to a Double? Both are subclasses of Number.

Upvotes: 1

Related Questions