4

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.

Justin
  • 4,196
  • 4
  • 24
  • 48
  • what if in the if statement, you do a class comparison ``if(!t1.getClass().equals(t2.getClass()) && !t1.equals(t2)) { ... }`` – Arnaldo Ignacio Gaspar Véjar Jun 12 '14 at 15:28
  • What other type is it? and why can there be multiple types? Using ``Number`` means that your values can be a of variety of types, such as BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short,... – f1sh Jun 12 '14 at 15:28
  • It seems like a flaw that the API requires `Long.equals` to be equal only to another `Long`, and there isn't an equality function that can compare it to any `Number` (or at least the known `Number` classes provided by the JDK). Unfortunately, I didn't see any such equality function in Apache Commons either. – ajb Jun 12 '14 at 15:35
  • You have not shown the declarations of the two `Matrix` objects that are being compared. Are they both `Matrix`? If they were the comparison should work. I strongly suspect you have two `Matrix` objects with different types for `T`. If that is the case then the comparison WILL fail because it will use `Object#equals()` for comparison. The key here is that `Number` has no `equals()` method of its own because it cannot know how to coerce two arbitrary numbers into a form that supports comparison. – Jim Garrison Jun 12 '14 at 15:38
  • @JimGarrison I've added the testing code. – Justin Jun 12 '14 at 16:39
  • See also http://stackoverflow.com/questions/19410357/is-there-a-number-value-equals – Raedwald Jun 12 '14 at 16:49
  • With respect to the question of why it's comparing using `Long` when all you have are `Matrix`: I think we would need to see how the `matrix` member is declared, how it's assigned in your constructor, and possibly the body of `set`. Something in your code may be causing the compiler to create a `Long` to be stored in `matrix[i]` for some `i`. – ajb Jun 12 '14 at 17:54
  • @ajb I have linked to the Matrix code. – Justin Jun 12 '14 at 17:58
  • @Justin Thanks for the link. I've looked over it--see my answer. – ajb Jun 12 '14 at 18:27
  • Yikes - might I suggest a `Matrix` constructor that accepts a 2D array for easier initialization of those test matrices? – Alex Jun 12 '14 at 18:31
  • @ajb Thanks, great answer. Thanks for explaining the why "Long" equal is used. I am still evaluating a good solution to the equals problem. – Justin Jun 12 '14 at 18:32
  • @Alex This isn't a code review :-) but thanks, that's a good suggestion. – Justin Jun 12 '14 at 20:32

4 Answers4

3

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.

ajb
  • 31,309
  • 3
  • 58
  • 84
2

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).

Durandal
  • 19,919
  • 4
  • 36
  • 70
1

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.

f1sh
  • 11,489
  • 3
  • 25
  • 51
-1

...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).

Community
  • 1
  • 1
Thomas
  • 87,414
  • 12
  • 119
  • 157
  • Thats ok, but why equals assume Long type? – Arnaldo Ignacio Gaspar Véjar Jun 12 '14 at 15:24
  • @ArnaldoIgnacioGasparVéjar because in OP's example, the variable ``t1`` was a Long. Naturally, ``Long.equals(Object)`` is called. – f1sh Jun 12 '14 at 15:27
  • @ArnaldoIgnacioGasparVéjar like f1sh said, `Long.equals()` is called and by definition of a Long only equals another Long with the same value (due to transitiveness). – Thomas Jun 12 '14 at 15:29
  • 2
    Too bad that implementations of Number do not implement Comparable, in other words what you suggest doesn't even compile, much less works. – Durandal Jun 12 '14 at 15:29
  • The OP does not mention that t1 is always Long – Arnaldo Ignacio Gaspar Véjar Jun 12 '14 at 15:30
  • @Durandal, you're right, my bad. I'll update the answer. – Thomas Jun 12 '14 at 15:31
  • Regarding your latest suggestion: have you tested it? I'm dubious. `Long.compareTo` requires another `Long` as a parameter (see [here](http://docs.oracle.com/javase/7/docs/api/java/lang/Long.html#compareTo(java.lang.Long))), and there won't be any automatic conversion of, say, `Integer` to `Long` since that happens only at compile time. So I'm doubtful that this will work right. – ajb Jun 12 '14 at 15:45
  • @ajb You're right, Long.compareTo() requires a Long argument. In fact all primitive wrappers only have a compareTo() taking their own type. There is no Comparable among any of the primitives wrappers in the JDK. – Durandal Jun 12 '14 at 15:49
  • @f1sh I've added the testing code which defines both Matrices as Matrix – Justin Jun 12 '14 at 16:41
  • I tried a similar case using a generic with `>`. I was able to create a program that compiled (with unchecked type warnings) that would attempt to call `Long.compareTo` with an `Integer`, but the result was `ClassCastException` at run time. So `compareTo` is not a solution that would allow different `Number` types to be compared. – ajb Jun 12 '14 at 17:49