【问题标题】:Adding arbitrary Number class vars添加任意 Number 类变量
【发布时间】:2019-01-28 12:12:36
【问题描述】:

我想总结 Number 对象的列表,并且每个对象只使用实际值(如果它是 Integer 我只想使用 .intValue() 方法而不是 .doubleValue e.x...),我不想使用instanceof

返回值必须是Number类型。

如何使用双重调度或策略模式或类似的方式来做到这一点?

我无法扩展 Number 的每个实现类,也无法将两个 Number 变量相加。

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

【问题讨论】:

  • 当您需要将包含 Double 和 Long 的 List 相加时,“真正的价值”是什么?
  • 我想new BigDecimal(number.toString()) 适用于所有类型。结果将是BigDecimal(即Number)。
  • @MTilsted: longValue()double 不起作用(非常好)。
  • 这开始看起来像一个 X-Y 问题。你真正需要做什么?
  • 你为什么还要关心实际的返回类型是什么?如果你返回Number...好吧...BigDecimalNumber...剩下的就是细节...对于调用者获得特定的东西有什么好处,然后又隐藏在后面Number?

标签: java java-11


【解决方案1】:

由于实际返回的类型与调用者相关,并且由于声明的类型为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 以防止精度损失,混合 Integer 之间没有区别和Long 与混合IntegerDouble,两者都只是不同Number 子类型的混合。因此,无论哪种情况,您都需要在两者之间进行Number::xxxValue 转换,并且无论实际组合如何,任何Number::xxxValue 转换都将在没有警告的情况下编译,即使它意味着精度损失。

由于较大的long 值在转换为double 时可能会丢失精度,所以最后一个示例使用中间的String 值,以确保在存在longdouble 输入值的情况下,所有到BigDecimal 的转换是无损的。

【讨论】:

  • 我认为 OP 宁愿有类似以下的东西:doubleValue + doubleValue = Long,如果双精度值可以表示为 long 本身而没有精度损失。 (也许也是 longValue + longValue = int?)......至少这是我在他写关于 number.longValue() == number.doubleValue() 以决定是否应该返回 longValue 时从聊天中得到的......最后他想申请一个合适的设计模式来解决所有问题 :-) 尽管如此,这仍然是一个不错的解决方案 :-)
  • 为什么你对 Java 和 JVM 了解这么多?或者你的来源是什么? :-) 我受到您的 cmets/answers 的启发和钦佩
  • @Roland 在保持好奇心并阅读有关该主题的所有已发表论文的同时,它帮助从事 Java 软件 20 多年。甚至Oracle’s Java documentation starting point 确实已经引出了一些有趣的读物。 Stackoverflow 本身也是一个来源,提供指向有趣主题的指针,以便进一步调查。
  • @Holger 你只是在这里非常谦虚......从字面上看。我与拥有 20 多年 Java 和编码经验的人一起工作,但没有人达到你的水平。嘿,我已经做了 10 年了,阅读你的一些答案让我觉得我还没有开始。总体而言,这个地方和用户从您的光临中受益匪浅(还有运气!)...
  • @Holger 我必须同意尤金的观点;这里同样适用......但老实说,当我第一次看到你的回答时(从那时起定期),我才开始关注 JLS。你的风格和简洁启发了我。 “阅读有关该主题的所有已发表论文”……就是这样;-) 拥有大量资源,我可能永远也赶不上;不是我需要,但我想 ;-) 你提供的链接确实是一个很好的资源,出于好奇,我已经进入了一个话题;-) 我很高兴你在这里并且你分享你的智慧......谢谢
【解决方案2】:

只是总结一下 cmets/chat 中也讨论的内容。

免责声明:不要在生产中使用此代码,或者确保了解它的实际作用。这种解决方案绝不是可行的方法或最佳实践。返回一个 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

请注意,还有其他星座会导致错误的结果。再说一遍:对两个双精度值求和后得到 Integer 有什么帮助?正如 Holger 在他的评论中所展示的(引用):

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

【讨论】:

  • 非常感谢!你和@Holger 教会了我很多东西!还有2个问题:1.为什么sum(1.1f, 1)变成2.100000023841858? 2、你为什么用Optional
  • 我不关心内存消耗等等......我想做的就是实现类似“方案”的行为。一方面我想保持准确性,让代码决定返回 Number 的哪个生命,以便 System.out.println() 很好地打印它
  • @RoyAsh。因为1.1f 已经是1.100000023841858。并非所有小数都可以表示为二进制小数。
  • floatdouble 都有其独特的目的,并且彼此之间的交互可能不太好......我认为一个很好的例子是 this answer to "Convert float to double without losing precision" showing the binary representation of each(请注意转换为double 或调用Float.doubleValue() 基本相同)
  • 关于“准确性”:我不会依赖于使用这种方法;-) 不知道您所说的“类似方案”是什么意思,但如果您想要准确,请切换到BigDecimal 并完全跳过 longValue() == doubleValue()-comparison。我的回答实际上只是演示了您可能会遇到哪些陷阱。它只显示了许多中的一个。溢出指日可待:调用者没有任何好处!!更糟糕的是......很多问题,不是那么容易理解的代码,潜在的意外行为(将 2 个双精度相加并得到一个整数?;-))
猜你喜欢
  • 1970-01-01
  • 2022-01-21
  • 2017-10-09
  • 2020-06-21
  • 2017-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多