使用泛型删除未经检查的转换警告

Removing unchecked cast warnings with generics

我刚刚接触到泛型 Java,所以我为自己建立了一个小项目。我想制作一个矢量/点,您可以在其中指定 Number(例如 DoubleIntegerLong 等)。

我最终得到了一个像样的 class 对象,但是注意到一些关于方法的问题。

import java.math.BigDecimal;

@SuppressWarnings("WeakerAccess") // Suppresses weaker access warnings
public class Vector<T extends Number> {

    private T x;
    private T y;

    public Vector() {}

    public Vector(T x, T y) {
        this.x = x;
        this.y = y;
    }

    public T getX() {
        return x;
    }

    public void setX(T x) {
        this.x = x;
    }

    public T getY() {
        return y;
    }

    public void setY(T y) {
        this.y = y;
    }

    public void dislocate(T offsetX, T offsetY) {
        this.setX(addNumbers(getX(), offsetX));
        this.setY(addNumbers(getY(), offsetY));
    }

    public void dislocate(Vector vector) {
        this.setX(addNumbers(getX(), vector.getX()));
        this.setY(addNumbers(getY(), vector.getY()));
    }

    @SuppressWarnings("unchecked") // Suppresses cast unchecked warnings
    private T addNumbers(Number... numbers) {
        BigDecimal bd = new BigDecimal(0);

        for(Number number : numbers) {
            bd = bd.add(new BigDecimal(number.toString()));
        }

        return (T) bd;
    }
}

最后一个方法,即加法,会抛出一个未经检查的转换警告。在我做了一些研究之后,我发现它由于泛型而表现得很奇怪,我对泛型还比较陌生,无法正确排除故障。

return (T) bd; 会产生警告吗? T 必须是 Number 的实例,所以它应该可以转换为 BigDecimal,对吧?

所以我创建了我的小测试方法,

Vector<Double> vec = new Vector<>(1.0, 3.0);
Vector<Double> vec2 = new Vector<>(2.2, 3.9);
vec.dislocate(1.0, 2.7);
System.out.println(vec.getX() + " " + vec.getY());
vec.dislocate(vec2);
System.out.println(vec.getX() + " " + vec.getY());

效果很好,打印出 2.0 5.74.2 9.6

那么问题是,当我使用 Double 中的方法时,例如 Double#isNaN()。然后抛出 ClassCastException,Exception in thread "main" java.lang.ClassCastException: java.base/java.math.BigDecimal cannot be cast to java.base/java.lang.Double.

这似乎与人们遇到的其他问题很常见,但是,尽管查阅了资源,我还是不明白为什么 使用 Double 方法。投射后的对象应该是Double吧?

在Java你基本上不能做这样的事情。当且仅当 TBigDecimal 本身时,(T) someBigDecimal 才有效。擦除的工作方式可能会暂时向您隐藏这一点,但是 Number 没有 能够将两个数字相加或将一个数字转换为另一个数字的特殊魔法。

一般来说,任何 方法都无法在 Java 中对不同类型的数字进行泛化,然后用它们进行数值计算。

The object should be a Double after the cast, right?

从不,因为转换(有或没有泛型)永远不会改变运行时类型。它只是更改您操作的声明类型。

addNumbers() 中,您实际上执行了取消检查转换:BigDecimalT
编译器警告您 uncheck 转换但接受它,因为 BigDecimal 与具有上限通配符的 T 兼容:Number.
通用 class 实例包含的元素:

private T x;
private T y;

现在参考 BigDecimal 类型,不再参考 Double 类型。

要解决这个问题,您需要提供一些添加 Ts 的方法。

例如,一个 BinaryOperator<T> 是包含两个 T 的东西,而 returns 是一个 T。所以,你可以定义一个用于添加,例如:

BinaryOperator<Double> addDoubles = (a, b) -> a+b;
BinaryOperator<BigDecimal> addBigDecimals = (a, b) -> a.add(b);

现在,您实际上需要在创建 Vector 时为其提供一个实例,例如作为构造函数参数:

public Vector(BinaryOperator<T> adder) {
  this.adder = adder; // define a field, too.
}

现在使用 BiFunction 添加数字:

private T addNumbers(T a, T b) {
  return adder.apply(a, b); // or you could just invoke this directly.
}

我简化了你的 addNumbers 总是带两个参数,因为你只用两个参数调用。要做到这一点,您要么需要提供一个 "generic zero",即 T 类型的值,该类型为零,要么只是从可变参数数组中的第一个元素开始。