Reputation: 4216
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
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
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
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
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