为什么Java 8中的新java.util.Arrays方法没有为所有原始类型重载?

Mat*_*w M 54 java arrays java-8

我正在审查Java 8的API更改,我注意到新方法java.util.Arrays并没有为所有原语重载.我注意到的方法是:

目前,这些新的方法只能处理int,longdouble原语.

int,longdouble,可能是最广泛使用的原语,所以有意义的是,如果他们必须限制API,他们会选择这三个,但为什么他们必须限制API?

Edw*_*rzo 81

为了解决整个问题,而不仅仅是这个特定场景,我想我们都想知道....

为什么Java 8中存在接口污染

例如,在如C#语言,有一组预定义的函数类型接受任何数量的参数与可选的返回类型的(函数功能操作每一个上升到不同类型的16个参数T1,T2,T3,..., T16),但是在JDK 8中我们所拥有的是一组不同的功能接口,具有不同的名称和不同的方法名称,其抽象方法代表众所周知的函数arities的子集(即nullary,unary,binary,trinary等).然后我们处理原始类型的案例爆炸,甚至还有其他场景导致更多功能接口爆炸.

类型擦除问题

因此,在某种程度上,两种语言都受到某种形式的界面污染(或代表C#中的污染).唯一的区别是在C#中它们都具有相同的名称.遗憾的是,在Java中,由于类型擦除,Function<T1,T2>Function<T1,T2,T3>/ 之间没有区别Function<T1,T2,T3,...Tn>,所以我们不能简单地将它们命名为相同的方式,我们必须为所有可能类型的函数组合提供创意名称.

不要以为专家组没有解决这个问题.用lambda邮件列表中的Brian Goetz的话来说:

[...]作为一个例子,我们来看一下函数类型.devoxx提供的lambda strawman具有功能类型.我坚持要删除它们,这让我不受欢迎.但我对函数类型的反对并不是我不喜欢函数类型 - 我喜欢函数类型 - 但是函数类型与Java类型系统的现有方面,擦除有很大关系.删除的功能类型是两个世界中最糟糕的.所以我们从设计中删除了它.

但我不愿意说"Java永远不会有函数类型"(虽然我认识到Java可能永远不会有函数类型.)我相信为了获得函数类型,我们必须先处理擦除.这可能是,也可能是不可能的.但在一个具体化的结构类型的世界中,函数类型开始变得更有意义[...]

这种方法的一个优点是我们可以使用接受尽可能多的参数的方法定义我们自己的接口类型,并且我们可以使用它们来创建我们认为合适的lambda表达式和方法引用.换句话说,我们有能力用更新的功能界面污染世界.此外,我们甚至可以为早期版本的JDK中的接口创建lambda表达式,也可以为定义SAM类型的早期版本的API创建lambda表达式.所以现在我们有能力使用RunnableCallable作为功​​能接口.

然而,这些界面变得更难以记忆,因为它们都具有不同的名称和方法.

不过,我那些不知道他们为什么没有解决的问题,如Scala中的一个,像定义接口Function0,Function1,Function2,..., FunctionN.也许,我能提出的唯一论点就是他们想要最大化在早期版本的API中为接口定义lambda表达式的可能性,如前所述.

缺乏价值类型问题

因此,显然类型擦除是这里的一个驱动力.但是如果你是其中一个想知道为什么我们还需要所有这些具有相似名称和方法签名的附加功能接口,并且唯一的区别是使用原始类型,那么让我提醒你,在Java中我们 缺少类似的值类型那些用C#这样的语言.这意味着我们的泛型类中使用的泛型类型只能是引用类型,而不是基本类型.

换句话说,我们不能这样做:

List<int> numbers = asList(1,2,3,4,5);
Run Code Online (Sandbox Code Playgroud)

但我们确实可以这样做:

List<Integer> numbers = asList(1,2,3,4,5);
Run Code Online (Sandbox Code Playgroud)

然而,第二个例子导致包装对象从原始类型来回打包和拆箱的成本.在处理原始值集合的操作中,这可能变得非常昂贵.因此,专家组决定创建这种界面爆炸来处理不同的场景.为了使事情"变得更糟",他们决定只处理三种基本类型:int,long和double.

lambda邮件列表中引用Brian Goetz的话:

[...]更一般地说:拥有专门的原始流(例如,IntStream)背后的哲学充满了令人讨厌的权衡.一方面,它有很多丑陋的代码重复,界面污染等.另一方面,盒装操作上的任何类型的算术都很糟糕,并且没有减少过量的故事会很糟糕.所以我们处在一个艰难的角落,我们试图不让它变得更糟.

不让它变得更糟的技巧#1是:我们没有做所有八种原始类型.我们做的是int,long和double; 所有其他人都可以通过这些来模拟.可以说我们也可以摆脱int,但我们认为大多数Java开发人员都没有为此做好准备.是的,会有对Character的调用,答案是"坚持使用int".(每个专业化预计约为JRE足迹的100K.)

特技#2是:我们使用原始流来揭示在原始域中最好的事情(排序,减少)但不尝试复制你在盒装域中可以做的所有事情.例如,正如Aleksey指出的那样,没有IntStream.into().(如果有的话,下一个问题是"IntCollection在哪里?IntArrayList?IntConcurrentSkipListMap?"意图是许多流可以作为参考流开始并最终作为原始流,但反之亦然.那没关系,那就是减少了所需的转换次数(例如,没有int的重载 - > T,没有int的特殊化 - > T等等)[...]

我们可以看到这对专家组来说是一个艰难的决定.我想很少有人会同意这很酷,我们大多数人都很同意这是必要的.

已检查的例外问题

还有第三种驱动力可能会使事情变得更糟,事实上Java支持两种类型的例外:已检查和未检查.编译器要求我们处理或显式声明已检查的异常,但对于未经检查的异常,它不需要任何内容​​.因此,这会产生一个有趣的问题,因为大多数功能接口的方法签名都不会声明抛出任何异常.所以,例如,这是不可能的:

Writer out = new StringWriter();
Consumer<String> printer = s -> out.write(s); //oops! compiler error
Run Code Online (Sandbox Code Playgroud)

它无法完成,因为该write操作会抛出一个已检查的异常(即IOException),但该Consumer方法的签名并未声明它会抛出任何异常.因此,解决这个问题的唯一方法就是创建更多的接口,一些声明异常而另一些则没有(或者在语言层面提出另一种机制来实现异常透明性.再一次,让事情"变得更糟"专家在这种情况下,小组决定不做任何事.

lambda邮件列表中的Brian Goetz的话来说:

[...]是的,您必须提供自己的特殊SAM.但是lambda转换对它们来说可以正常工作.

EG讨论了这个问题的附加语言和库支持,最后认为这是一个糟糕的成本/收益权衡.

基于库的解决方案导致SAM类型爆炸2倍(异常与非异常),与原始专业化的现有组合爆炸相互作用很严重.

可用的基于语言的解决方案是复杂性/价值权衡的输家.虽然有一些替代解决方案我们将继续探索 - 虽然显然不是8,也可能不是9.

在此期间,您可以使用工具来执行您想要的操作.我得到你喜欢我们提供最后一英里你(和,其次,你的要求是真正的"你为什么不干脆放弃对检查的异常已经是"一个自动精简含蓄的要求),但我认为目前的状态让你完成了你的工作.[...]

因此,开发人员可以根据具体情况制定更多的界面爆炸来处理这些问题:

interface IOConsumer<T> {
   void accept(T t) throws IOException;
}

static<T> Consumer<T> exceptionWrappingBlock(IOConsumer<T> b) {
   return e -> {
    try { b.accept(e); }
    catch (Exception ex) { throw new RuntimeException(ex); }
   };
}
Run Code Online (Sandbox Code Playgroud)

为了做到:

Writer out = new StringWriter();
Consumer<String> printer = exceptionWrappingBlock(s -> out.write(s));
Run Code Online (Sandbox Code Playgroud)

可能在未来(可能是JDK 9),当我们获得Java和Reification中的值类型支持时,我们将能够摆脱(或至少不再需要使用)这些多个接口中的一些.

总之,我们可以看到专家组在解决几个设计问题时遇到了困难.保持向后兼容性的需要,要求或约束使事情变得困难,然后我们还有其他重要条件,例如缺少值类型,类型擦除和检查异常.如果Java有第一个而缺少其他两个,那么JDK 8的设计可能会有所不同.所以,我们都必须明白,这些是很多权衡的难题,而且EG必须在某个地方划一条线并作出决定.

  • 当我看到了Java 8设有几个月前,我想:"好吧,八年后超过世界其他地区,但似乎像Java正在成为现代:lambda表达式,第一类函数,一个LINQ般的API ... "但是现在......这个答案说明了为什么Java根本没有未来,必须完全重新设计.几乎所有的Java 8的"现代"的特点遭受相关愚蠢dessign决定其Java有问题:检查异常,一切都是一个接口/类层次结构,无值类型,类型擦除等真诚的Java对我听起来像一个笑话 (9认同)
  • @ Manu343726我敢说8年后不准确.为了实现这一点,lambda表达式应该是8年前发明的,但它们是本书中最古老的技巧.我们可以说在Alonso Church的Lambda微积分之后大约84年,或者自McCarthy等人开发lisp后大约56年,这可能是第一个拥有它们的人:-) (5认同)
  • @ Manu343726我想你会发现这很有趣:[与Neal Gafter讨论Java的未来](http://www.infoq.com/articles/neal-gafter-on-java) (4认同)
  • 这使事情过于复杂化。`int[]、`long[]` 和 `double[]` 有流操作。所以是的,自然应该有其他原始类型的操作。我觉得它实际上很幽默——Java 是最好的!哈哈。 (3认同)