添加任意Number类vars

时间:2019-01-28 12:12:37

标签: java java-11

我想对Number对象的列表求和,并且每个对象仅使用实数值(如果它是Integer,我只想使用.intValue()方法而不是{{1 }}例如。),我不想使用.doubleValue

返回值必须为instanceof类型。

我该如何使用双重调度或策略模式或类似方法?

我无法扩展Number的每个实现类,也不能对两个Number变量求和。

Number中只有6个.xValue()方法,我想相应地使用它们。

2 个答案:

答案 0 :(得分:5)

由于实际返回的类型与调用者相关,并且由于声明的类型Number而仍然不明显时,对于调用者没有太大用处,因此它应在调用者的控制下并与通用类型签名,允许调用者实际使用特定的返回类型。例如

public static <N extends Number, R extends Number> R sum(
        List<? extends N> input, Function<? super N, ? extends R> cast,
        BinaryOperator<R> addition) {

    return input.stream().<R>map(cast).reduce(addition).orElse(null);
}
public static <N extends Number> N sum(
        List<? extends N> input, BinaryOperator<N> addition) {

    return sum(input, Function.identity(), addition);
}

这允许请求计算在输入类型之内,例如

List<Integer> list = Arrays.asList(1, 2, 3, 4);
Integer iSum1 = sum(list, Integer::sum);
Integer iSum2 = sum(list, Math::addExact);//throw on overflow

但在总结之前也要拓宽类型:

Long lSum = sum(list, Integer::longValue, Long::sum);

同样,您可以处理LongDouble输入类型:

List<Long> list = Arrays.asList(1L, 2L, 3L, 4L);
Long lSum1 = sum(list, Long::sum);
Long lSum2 = sum(list, Math::addExact);//throw on overflow
// without precision loss:
BigInteger biSum = sum(list, BigInteger::valueOf, BigInteger::add);
List<Double> list = Arrays.asList(1.0, 2.0, 3.0, 4.0);
Double dSum = sum(list, Double::sum);
// without precision loss:
BigDecimal bdSum = sum(list, BigDecimal::valueOf, BigDecimal::add);

或处理混合类型:

List<Number> list = Arrays.asList(1, 2L, 3.0, 4F);
Double dSum = sum(list, Number::doubleValue, Double::sum);
BigDecimal bdSum = sum(list, n -> new BigDecimal(n.toString()), BigDecimal::add);

请注意,Java的Number类型层次结构不能反映原始类型的类型转换规则。因此,虽然intlong的混合值可以作为long处理,而将intdouble混合使用则需要使用double以防止丢失从精度上讲,IntegerLong的混合与IntegerDouble的混合之间没有区别,两者都是不同Number子类型的混合。因此,无论哪种情况,您都需要进行Number::xxxValue转换,并且无论实际组合如何,任何Number::xxxValue转换都将在没有警告的情况下进行编译,即使这暗示着精度下降。

由于大的long值在转换为double时可能会失去精度,因此最后一个示例使用中间的String值来确保存在long时和double输入值,到BigDecimal的所有转换都是无损的。

答案 1 :(得分:1)

仅是总结评论/聊天中讨论的内容。

免责声明::请勿在生产中使用此代码,或者只是确保了解它的实际作用。绝不是解决方案或最佳实践。返回Number的类型已被改编且呼叫者未意识到的迟早会给您带来麻烦。如果您想以呼叫者友好的方式解决问题,请查看Holgers answer。这里的答案只是按照他的要求解决了OP的问题。它没有任何真正的好处。基本上,这里只是说明按照请求的方式解决它可能是一个坏主意;-)。话虽如此,让我们开始...

一种定义策略的方法:

class Strategy {
    Predicate<Number> predicate;
    UnaryOperator<Number> transformation;
    Strategy(Predicate<Number> predicate, UnaryOperator<Number> transformation) {
        this.predicate = predicate;
        this.transformation = transformation;
    }
    boolean applies(Number number) {
        return predicate.test(number);
    }

    Number transformNumber(Number number) {
        return transformation.apply(number);
    }
}

然后可能的策略列表如下

List<Strategy> strategies = Arrays.asList(
        new Strategy(n -> n.byteValue() == n.doubleValue(), Number::byteValue),
        new Strategy(n -> n.shortValue() == n.doubleValue(), Number::shortValue),
        new Strategy(n -> n.intValue() == n.doubleValue(), Number::intValue),
        new Strategy(n -> n.longValue() == n.doubleValue(), Number::longValue),                                                // please read the disclaimer again...  
        new Strategy(n -> n.floatValue() == n.doubleValue(), Number::floatValue),                                              // please spare your comments :-)
        new Strategy(n -> true, Number::doubleValue)                                                                           // ... lets continue!
);

一个简单的总结和策略的应用:

Optional<Number> sum(Number... numbers) {
    return Arrays.stream(numbers)
            .reduce(this::sumBasedOnStrategy);
}
Number sumBasedOnStrategy(Number one, Number two) {
    Number result = one.doubleValue() + two.doubleValue();
    return strategies.stream()
            .filter(s -> s.applies(result))
            .map(s -> s.transformNumber(result))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("No known strategy for the given number"));
}

现在测试求和策略:

Stream.of(1, 256, 66000, 3000000000L, 1.1f, 3.4f, Double.MAX_VALUE)
            .map(n -> sum(n, 1))
            .map(Optional::get)
            .map(Object::getClass)
            .forEach(System.out::println);

您期望什么?

class java.lang.Byte
class java.lang.Short
class java.lang.Integer
class java.lang.Long
class java.lang.Double // really?
class java.lang.Float
class java.lang.Double

这是对应的总和结果...

2
257
66001
3000000001
2.100000023841858 // thank you for your attention ;-)
4.4
1.7976931348623157E308

请注意,还有其他星座会导致错误的结果。再说一次:将两个double值相加后得到Integer有什么帮助?正如Holger在他的评论(引用)中所示:

  

基于结果值的选择仅适用于声明的返回类型为Number,因此调用者甚至不会注意到更改的类型,这将导致问题,而无益。想想Number n1 = 0.5 + 0.5, n2 = sum(0.5, 0.5);,其中n1.equals(n2)会产生false的原因是1Integer)不等于1.0Double)。