6

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

它可以像这样实现:

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

6 回答 6

5

JDK 1.0中java.lang.Math已经引入了,早在 Java 5 中的语言中引入可变参数函数之前。

此外,效率是一个问题:如果您大部分时间需要两个元素,那么“内联”传递它们会快得多,而无需创建中间数组来保存它们。这也避免了在实现内部设置循环的成本。

于 2013-02-23T11:46:45.390 回答
3

虽然其他人已经回答了为什么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));在所有函数实现之后,只是在不同的地方。

更好的是:此方法不仅可以处理双精度数,还可以处理实现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

对于即将发布的版本,集合库在Project Lambda中进行了重新设计,使其更加面向对象。在上面的示例中,Lambda 用于提供一种简单易读的方法来确定最大元素。以下内容也可以:

import static java.util.Comparators.naturalOrder;

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

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

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. 它是多态的。这意味着它可以用于每种类型的集合(ListSetStreamIterator
  3. 它富有表现力且易于理解
  4. 它允许即时并行化。只需更改.stream().parallelStream()
于 2013-02-23T16:04:15.857 回答
2

因为它存在的时间比 java 中存在的可变参数函数(在java 5中引入)要长,并且没有太多更新它的需求,因为正如您刚刚展示的那样,自己动手做是微不足道的。

此外,可变参数方法中存在隐藏的性能损失,因为数组 (double[]) 将在幕后根据您的参数创建

于 2013-02-23T11:44:52.083 回答
2

Java 8 已经用流实现了数字操作,非常灵活。一个例子:

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

不像自制软件那么简单,但仍然直截了当、快速且更灵活。

例如,使用多核只需要一个函数调用:

stream.parallel().max().getAsDouble()

在这种情况下毫无意义,因为即使使用 double 找到 max 也非常快 - 你需要数百万个 double 才能看到毫秒的差异。但如果有其他处理,那么它可以快速加快它们。

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

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() );
于 2014-08-29T06:17:04.597 回答
1

Math.max 自 JDK 1.0 以来就已经存在,早在引入变量 # of argument 语法之前。这并不是说该方法无法按照您建议的方式进行更新。有时库方法定义或实现会更改,但这种情况很少见。大多数时候,新方法被添加到类中,而不是修改现有方法。

您对 Max 的新实现实际上是方法重载的一种情况,因为现有方法和新的 var args 方法可能在同一个类中并存。因此,虽然它当然可以替换现有方法,但它也可能只是 Math 类的补充。所以我觉得应该加上。我们可以不理会现有方法这一事实消除了对新实现可能引起的性能的任何担忧。

Java n 和 Java n+1 之间可以改变的文化无论如何都在改变。例如,文件访问类java.sql.Connection从 Java 6 更改为 Java 7,因为在 Java 7 中它们现在实现了AutoCloseable. Java 9 实际上会从类中删除一些妨碍项目拼图的方法。

我想不出 Math.max 没有更新的正当理由。也许直到现在还没有人建议它。你在看这个吗,马克·莱因霍尔德

于 2013-02-23T12:09:42.373 回答
0

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

于 2013-02-23T11:47:23.120 回答