Java - 绑定到特定子类的{Generic Number方法签名

时间:2018-06-07 09:13:16

标签: java generics enums

我想编写一个方法,它将泛型Number作为参数并返回另一个Number,其类型可能与第一个不同,并作为第二个参数传递。像这样:

public <N1 extends Number, N2 extends Number> N2 op(N1 num, Class<N2> retType) {
    //Sample operation, where type conflicts arise.
    return retType.cast(num + 3);
}

当然,如果他们是N1N2,我就不能将Integer投放到Long。但是,Number提供了doubleValue()intValue(),...,这些方法可以让我通过switch / case语句部分解决问题。这会限制我xxxValue()类公开的Number方法的返回类型,从而切断AtomicIntegerAtomicLongBigInteger和{{1} }。

对于我想到的应用程序,这仍然是可以接受的,但是如果没有BigDecimal语句,我的方法将无法正确处理Number类的任何自定义或未来的官方扩展。 switch / case块应该随意决定调用哪个default方法,或抛出异常(我想避免)。我可以使用枚举来按类型封装参数,但我担心我的代码会变得过于复杂和难以使用。

this question的答案提供了进一步的见解,即为两个参数声明一个通用类型(它不会强制两个参数在运行时实际上是相同的类型),这肯定是值得一提的是。

我想要实现的目标是:

  • 使用一些泛型(广义上)xxxValue()参数声明一个方法,可能有更多带有不同参数的方法(例如一个带有两个参数的方法,一个带有三个参数的方法)但在每一个中仍然是通用的。
  • 参数必须仅限于Number的一些众所周知的扩展名(例如NumberInteger)。
  • 参数可能有多种组合,可能包括Double(Double, Integer)(Double, Double)(Integer, Double)

我不想用不同的签名定义多个方法。即使在修复返回类型时,例如到(Integer, Integer)时,随着更多参数和类型的添加,方法的数量会爆炸。

当然,我可能总是诉诸于每种方法的特定的临时实现,因为我可能不需要每种可能的类型组合。我仍然希望我的设计足够灵活,同时强制执行这些类型约束。

1 个答案:

答案 0 :(得分:1)

这是我最好的解决方法,它调用接受String作为参数的构造函数,如果失败则返回null(编辑删除printStackTrace并删除一个不必要的分支)

public static <R extends Number> R op(Number num, Class<R> retType) {
    BigDecimal three = BigDecimal.valueOf(3);
    BigDecimal bdNum = new BigDecimal(num.toString());

    //add three
    BigDecimal bdResult = bdNum.add(three);

    String strResult = bdResult.toString();
    Constructor[] cons = retType.getDeclaredConstructors();
    for (Constructor con: cons) {
        if (con.getParameterCount() == 1) {
            if (con.getGenericParameterTypes()[0] == String.class) {
                try {
                    return (R)con.newInstance(strResult);
                } catch (InstantiationException | IllegalAccessException | NumberFormatException e) {
                } catch (InvocationTargetException e) {
                    //if here then either the decimal place is causing a problem
                    // when converting to integral type,
                    // or the value is too large for the target type

                    // so let's try to remove the decimal point by truncating it.
                    strResult = bdResult.toBigInteger().toString();
                    try {
                        return (R)con.newInstance(strResult);
                    } catch (NumberFormatException | IllegalAccessException | InstantiationException | InvocationTargetException e1) {
                    }
                }
                //if here, then the most likely the integral type is too large
                //like trying to put 3,000,000,000 into an int
                // when largest int possible is 2,147,483,647
            }
            //if here, then no constructors with 1 String parameter
        }
    }

    return null;
}