比较“T extends Number”是否相等

4
我是一个可以帮助翻译文本的工具。下面是您需要翻译的内容:

我有一个矩阵类,它接收一个范型对象,其扩展了Number类型。

例如:

public class Matrix<T extends Number>

我想要比较两个具有相同值的矩阵:

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

并且

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

在Matrix类中,我有一个equals方法,它看起来像这样:
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;
}

这一行出现了错误:

t1.equals(t2)

即使两个数相等,也会显示不同。例如:"273"和"273"。
当我调试equals方法时,它失败了,因为它假定数字是Long类型的:
这来自于Java SDK Long.class:
public boolean equals(Object obj) {
if (obj instanceof Long) {
    return value == ((Long)obj).longValue();
}
return false;
}

基本上,它失败是因为obj不是Long的实例。
我可以轻松地更改我的equals方法如下:
        if (t1.longValue()!=t2.longValue())
            return false;

但是我想知道在这种情况下检查相等的正确方法是什么,以及为什么泛型T上的equals方法假定它是Long。

编辑:

我的测试代码定义了“整数矩阵泛型类型”,这使得相等性测试(使用Long进行比较)对我来说很奇怪。

测试代码:

    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;
    }

这里是矩阵代码的链接,但没有定义equals()方法。我还没有检查equals()方法的代码。


API 要求 Long.equals 只能与另一个 Long 相等,似乎存在缺陷,而且没有一个相等函数可以将其与任何 Number(或至少是 JDK 提供的已知 Number 类)进行比较。不幸的是,在 Apache Commons 中我也没有看到这样的相等函数。 - ajb
您没有展示正在比较的两个Matrix<T>对象的声明。它们都是Matrix<Long>吗?如果是,比较应该可以工作。我强烈怀疑您有两个具有不同类型的TMatrix<T>对象。如果是这种情况,那么比较将失败,因为它将使用Object#equals()进行比较。关键在于Number没有自己的equals()方法,因为它无法知道如何将两个任意数字强制转换为支持比较的形式。 - Jim Garrison
请参见https://dev59.com/ZHjZa4cB1Zd3GeqPZQwL - Raedwald
关于为什么在只有Matrix<Integer>的情况下使用Long进行比较的问题:我认为我们需要看一下matrix成员是如何声明的,它在构造函数中是如何赋值的,以及可能是set方法的具体实现。你代码中的某些部分可能会导致编译器创建一个Long来存储matrix[i]中的某个i - ajb
@Alex 这不是代码审查 :-) 但是谢谢,这是一个好建议。 - Justin
显示剩余7条评论
4个回答

3
程序使用Long.equals的原因是,尽管你的所有测试代码都使用Matrix<Integer>,但你实际上存储了一个Long。该代码具有以下内容:
public class Matrix<T extends Number> {

    private T[] matrix = null;

然后在构造函数中:

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

当然会创建一个由null引用组成的数组。但是当使用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);

这里是set的示例:

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

事实是,即使您尝试在结果上进行 (T) 强制转换,它也不起作用。请注意,语言不允许在 LongInteger 类型之间进行强制转换:
Long x = 3L;
Integer y = 4;
x = (Long)y;     // illegal
y = (Integer)x;  // illegal

由于类型擦除,编译器不会尝试检查对 (T) 的强制转换,但如果您不抑制警告,则会显示未经检查的警告。(它会检查您要转换的内容是否为一些 Number,但仅限于此。)它不会生成任何执行转换的代码。因此,即使在 Matrix<Integer> 上调用了乘法方法,代码也不会尝试将您的 Long 转换为 Integer。强制转换没有效果,结果是一个指向 matrix 数组中存储的 Long 的引用。(这可以正常运行,因为由于类型擦除,代码无法检查类型是否与 T 相同。)因此,稍后当您使用 matrix[i].equals 时,由于 matrix 中存储的是一个 Long,所以会调用 Long.equals
不幸的是,我认为没有好的方法将数字转换为某个在编译时未知的 Number 类对象。您可以将 T 的类作为参数传递给构造函数,然后使用反射来尝试找到接受 long 参数的该类的构造函数,但这很丑陋。

2

很遗憾,目前没有办法检查数字实现的值是否相等。

你可以做的最接近通用方法是:

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

这种方法适用于所有基本包装类型的合理结果,但不适用于BigDecimal、BigInteger和其他提供超出long/double限制精度的实例。

比较longValue和doubleValue的原因是,如果只使用longValue,则1L和1.1D会被检测为相等(由于Double.longValue()中的截断),而如果只使用doubleValue,则有多个不同的long值匹配一个double(例如,范围在2^53到2^63之间的整数值可以完全表示为long,但当表示为double时,它们会在最低有效位上四舍五入)。


1

由于您已经知道这些值并不总是Long的实例,因此您必须自己比较数字值,就像您已经发现的那样。

回答您的问题:“我想知道为什么泛型T的equals方法假定它是一个Long”: 因为它确实是一个Long。当使用t1作为Long调用t1.equals(t2)时,等式检查是由Long.equals(Object)完成的。如果参数类型不同,则结果为false。

由于您无法确定在equals方法中“到达”的类型,因此应该实现一个可以处理所有可能类型的比较器。例如:如何将整数与双精度浮点数进行比较?两者都是Number的子类。


在测试代码中,t1和t2被定义为整数类型。 - Justin

-1
“...为什么泛型 T 上的 equals 方法假定它是 Long 类型。”
原因很简单:假设您正在测试的矩阵是类型为 Matrix<Long>,那么 t1 实际上是 Long 的一个实例(泛型类型只允许您在此处使用 Long,并且在运行时没有任何相关性),因此将调用 Long.equals()
在以下情况下应调用 Integer.equals()
Matrix<Integer> m1 = ...;
Matrix<Long> m2 = ...;

m1.equals( m2 ); 

由于m1的成员是Integer类型,调用t1.equals(t2)将具有签名Integer.equals(Long)

那么你该怎么做才能让两个不同类型但值相等的矩阵相等呢?

一般问题是你应该使用compareTo()来检查值的相等性(因为在某些情况下,例如BigDecimal中,数学上相等的值如2.0和2.00不会导致equals()返回true)。

不幸的是,使用T extends Number & Comparable<T>不是一个选项(请参见引用的评论,以及这里:为什么java.lang.Number没有实现Comparable接口?),因为你不能以这种方式调用Long.compareTo(Integer)

因此,你必须回到原始值并区分整数和浮点数的值(因此调用t1.longValue()t1.doubleValue()),或者使用一个Comparator<Number>,其实现compareTo(Number lhs, Number rhs)会处理它。 (应该有像这样的可用的Comparatorshttp://www.jidesoft.com/javadoc/com/jidesoft/comparator/NumberComparator.html)。

如果您想支持更大的数字,如BigIntegerBigDecimal,您还可以考虑使用BigDecimal,并为每个值创建一个实例。 这应该会带来一些灵活性,但也会产生一些性能成本。(免责声明:这只是一个未经测试的想法,因此不要仅将其作为提供给您自己思考过程的输入。)


@ArnaldoIgnacioGasparVéjar,就像f1sh所说的那样,会调用Long.equals()方法,并且根据Long的定义,只有与具有相同值的另一个Long相等(由于传递性)。 - Thomas
2
很遗憾,Number的实现并没有实现Comparable<Number>,换句话说,你所建议的甚至无法编译,更不用说运行了。 - Durandal
OP没有提到t1始终是Long类型。 - Arnaldo Ignacio Gaspar Véjar
@Durandal,你是正确的,我的错。我会更新答案。 - Thomas
关于您最新的建议:您测试过了吗?我有所怀疑。Long.compareTo需要另一个Long作为参数(请参见此处),而且不会自动转换,例如,IntegerLong,因为这只发生在编译时。因此,我怀疑这将无法正常工作。 - ajb
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接