为什么Math.max(double a, double b)不支持可变参数?

6

为什么Math.max的实现不是可变参数函数(variadic function)

可以像这样实现:

public class Main {
    public static double max(double... values) {
        double max = Double.NEGATIVE_INFINITY;
        for (double tmp : values) {
            max = max < tmp ? tmp : max;
        }
        return max;
    }

    public static void main(String[] args) {
        // This works fine:
        System.out.println(max(-13, 12, 1337, 9));

        // This doesn't work:
        // System.out.println(Math.max(-13, 12, 1337));
    }
}

有什么原因不能像这样实现吗?


4
Java 1.0引入了数学功能,而可变参数在1.5中加入... - Kai
6个回答

5

java.lang.Math是在JDK 1.0引入的,早于Java 5中引入变参函数。

此外,效率是一个问题:如果你大多数情况下只需要两个元素,直接将它们“内联”传递比创建中间数组来保存它们更快。这也避免了在实现中设置循环的成本。


5
虽然其他人已经回答了为什么Math.max不是可变参数的问题,但他们没有回答为什么在引入可变参数函数时不创建这样的方法。
我甚至不知道为什么(有一个未解决的错误报告),所以我只能猜测:
虽然Math中没有实现它,但如果我们查看Collections,会发现以下方法:
public static <T extends Object & Comparable<? super T>> T max(
    Collection<? extends T> coll) {
  ...
}

尽管类型签名看起来很丑(它需要足够灵活以处理协变和逆变),但可以轻松地与Collections.max(Arrays.asList(-13, 12, 1337, 9));一起使用。在所有函数实现完成后,只需将其放置在不同的位置即可。
更好的是:此方法不仅可以处理double,还可以处理实现Comparable接口的所有类型。
尽管您提出的解决方案和Collections中的解决方案都不是面向对象的,它们只是静态方法。幸运的是,在JDK8中,这种情况将会改变。
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

int max(List<Integer> list) {
  Optional<Integer> opt = list.stream().max((a,b) -> a-b);
  return opt.orElse(Integer.MAX_VALUE);
}

max(Arrays.asList(-13, 12, 1337, 9)); // 1337
max(Arrays.asList()); // 2147483647

在即将发布的版本中,收集库在Lambda项目中进行了重新设计,以更加面向对象。在上面的示例中,Lambdas被用来提供一种简单易懂的方法来确定最大元素。以下代码也可以实现相同的功能:

import static java.util.Comparators.naturalOrder;

Arrays.asList(-13, 12, 1337, 9)
  .stream()
  .max(naturalOrder())
  .ifPresent(System.out::println); // 1337

可以使用高阶函数reduce代替max

Arrays.asList(-13, 12, 1337, 9)
  .stream()
  .reduce((a,b) -> a > b ? a : b)
  .ifPresent(System.out::println); // 1337

另外一个细节是使用 Optional。它是一种类型,可以通过高阶函数的组合来简化错误处理,就像上面的示例所示。

lambda 提案有几个优点,使得不需要实现 Math.max 的可变形式:

  1. 它是面向对象的
  2. 它是多态的。这意味着它可以与每种类型的集合(List、Set、Stream、Iterator 等)一起使用
  3. 它表达能力强,易于理解
  4. 可以在运行时并行化。只需将 .stream() 改为 .parallelStream()

为什么在空输入时返回 MAX_VALUE?为什么不是 MIN_VALUE - inetphantom

2

Java 8 实现了对数字流的操作,非常灵活。以下是一个示例:

DoubleStream.of( -13, 12, 1337, 9 ).max().getAsDouble()

不像homebrew那么简单,但仍然直接,快速,并且更加灵活。
例如,利用多核只需要一个函数调用:
stream.parallel().max().getAsDouble()

在这种情况下,这意义不大,因为即使使用双精度,找到最大值也非常快 - 您需要数百万个双精度才能看到毫秒级别的差异。

但如果有其他处理,则可以快速加速它们。

或者您还可以一次性使用系统类找到最小值、平均值、总和等:

DoubleSummaryStatistics stat = DoubleStream.of( -13, 12, 1337, 9 ).summaryStatistics();
System.out.println( stat.getMin() );
System.out.println( stat.getAverage() );
System.out.println( stat.getMax() );
System.out.println( stat.getCount() );
System.out.println( stat.getSum() );

2
因为它存在的时间比 Java 中的可变参数函数要长(在 Java 5 中引入),而且没有太多需求来更新它,因为正如你刚才展示的那样,自己实现这个功能非常简单。
此外,可变参数方法中存在隐藏的性能损耗,因为会在后台从您的参数创建一个数组(double[])。

你知道Java的哪个版本支持可变参数函数吗? 另一个问题:如果有max(double... values)max(double a, double b)两个函数,Java是如何决定使用哪一个的呢?我已经试过了,它会选择非可变参数函数,但我在JLS中没有找到相关内容。 - Martin Thoma
@moose - Java 5(我已经包含了一个链接)。另外,正如dasblinkenlight所说,可变参数方法实际上是接受数组的方法 - 在这种情况下是double[],因此对于仅有2个参数的情况,非可变参数方法更匹配。 - radai

1

Math.max自JDK 1.0以来就存在了,比可变参数语法引入的时间要早得多。这并不意味着该方法不能按照您的建议进行更新。有时会更改库方法的定义或实现,但这是很少见的。大多数情况下,类中添加新方法而不是修改现有方法。

您的新Max实现实际上是一种方法重载,因为现有方法和新的var args方法可以同时存在于同一类中。因此,虽然它可以替换现有方法,但也可以只是添加到Math类中。所以我认为应该添加它。我们可以保留现有方法,这样就消除了新实现可能引起的任何性能问题。

Java n和Java n+1之间可以更改的文化正在发生变化。例如,文件访问类和java.sql.Connection从Java 6到Java 7进行了更改,因为在Java 7中它们现在实现了AutoCloseable。 Java 9实际上将从阻碍项目拼图的类中删除一些方法。

我想不出Math.max没有被更新的任何有效理由。也许直到现在还没有人提出建议。Mark Reinhold,你看到这篇文章了吗?


0

Math.max()可以追溯到JDK 1.0,而可变参数函数直到Java 5才出现。


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