为什么Java 8中的新java.util.Arrays方法没有为所有的基本types重载?

我正在评论Java 8的API更改,并且我注意到java.util.Arrays中的新方法对于所有基元都没有重载。 我注意到的方法是:

  • parallelSetAll
  • parallelPrefix
  • spliterator
  • stream

目前这些新的方法只能处理intlongdouble基元。

intlongdouble可能是使用最广泛的原语,所以如果他们必须限制API,他们会select这三个,但是为什么他们不得不限制API呢?

为了解决整个问题,而不仅仅是这个特定的情况,我想我们都想知道….

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

例如,在像C#这样的语言中,有一组预定义的函数types接受任意数量的参数,并带有一个可选的返回types( Func和Action),每个参数可以有多达16个不同types的参数T1T2T3 … , T16 ),但在JDK 8中,我们拥有的是一组不同的函数接口,具有不同的名称和不同的方法名称,其抽象方法表示众所周知的函数元组的子集(即空,一元,二元,三元,等等)。 然后,我们处理原始types的情况爆发,甚至还有其他情况导致更多的function接口的爆炸。

types擦除问题

所以,在某种程度上,两种语言都会遇到某种forms的界面污染(或C#中的代表污染)。 唯一的区别是在C#中它们都具有相同的名称。 在Java中,不幸的是,由于types擦除 , Function<T1,T2>Function<T1,T2,T3>Function<T1,T2,T3,...Tn>之间没有区别,所以显然,不要简单地用同样的方式来命名它们,我们必须为所有可能的function组合types提供创造性的名称。

不要认为专家组没有为这个问题而挣扎。 用Brian的邮件列表中的Brian Goetz的话来说:

[…]作为一个例子,我们来看一下函数types。 devoxx提供的lambda strawman具有函数types。 我坚持把他们删除,这让我不受欢迎。 但是我对函数types的反对并不是我不喜欢函数types – 我喜欢函数types – 但是函数types与Javatypes系统的一个现有方面进行了很好的对抗,擦除。 擦除的functiontypes是两个世界中最糟糕的。 所以我们从devise中删除了这个。

但是我不愿意说“Java永远不会有函数types”(尽pipe我认识到Java可能永远不会有函数types)。我相信为了获得函数types,我们必须首先处理擦除。 这可能,也可能不可能。 但是在一个具体化结构types的世界里,函数types开始变得更有意义[…]

这种方法的一个优点是我们可以用接受尽可能多的参数的方法来定义我们自己的接口types,我们可以用它们来创buildlambdaexpression式和方法引用。 换句话说,我们有更多新function界面污染世界的力量 。 我们也可以创buildlambdaexpression式,甚至可以在早期版本的JDK中使用接口,也可以为我们自己的定义类似SAMtypes的API的早期版本创buildlambdaexpression式。 现在我们有能力使用RunnableCallable作为功​​能接口。

然而,由于这些界面的名称和方法各不相同,因此这些界面变得难以记忆。

不过,我是那些想知道为什么他们不像Scala那样解决问题的人之一,定义了像Function0Function1Function2 ,…, FunctionN 。 也许,我唯一可以提出的论点是,他们想要最大限度地提供在早期版本的API中为接口定义lambdaexpression式的可能性。

缺乏价值types问题

所以,显然types删除是这里的一个动力。 但是如果你是为什么我们还需要所有这些具有相似名称和方法签名的唯一区别是使用基本types的附加function接口,那么让我提醒你,在Java中我们 缺乏值types那些像C#这样的语言。 这意味着generics类中使用的generics只能是引用types,而不能是原始types。

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

 List<int> numbers = asList(1,2,3,4,5); 

但我们确实可以这样做:

 List<Integer> numbers = asList(1,2,3,4,5); 

然而,第二个例子的花费是将装入的对象从原始types中来回移动或者将其拆箱。 在处理原始值集合的操作中,这会变得非常昂贵。 所以,专家组决定创build这个接口的爆炸式处理不同的场景。 为了使事情“不那么糟糕”,他们决定只处理三种基本types:int,long和double。

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

更一般地说:具有专业原始stream(例如IntStream)的哲学充满了讨厌的折衷。 一方面,代码重复性差,界面污染严重等。另一方面,对盒装操作的任何一种算术都很糟糕,而且没有任何减less重复的方法是非常糟糕的。 所以我们处于一个困难的angular落,我们正在努力不让事情变得更糟。

诀窍#1没有让更糟糕的是:我们并没有做所有八个原始types。 我们在做int,long和double; 所有其他人都可以通过这些模拟。 可以说我们也可以摆脱int,但我们不认为大多数Java开发人员已经准备好了。 是的,会有字符的调用,答案是“坚持在一个整数”。 (每个专业都预计到JRE足迹约100K。)

诀窍#2是:我们使用原始stream来展现在原始域(sorting,缩减)中最好的东西,而不是尝试复制在盒装域中可以做的所有事情。 例如,没有IntStream.into(),正如Aleksey指出的那样。 (如果有的话,下一个问题是“IntCollection?IntArrayList?IntConcurrentSkipListMap?”)意图是许多stream可以作为参考stream开始,最终成为原始stream,但不是反之亦然。减less了所需的转换次数(例如,int – > T没有映射的重载,int没有专门的Function – > T等等)[…]

我们可以看到,这对专家组来说是一个困难的决定。 我认为很less有人会同意这很酷,而我们大多数人很可能会认同这是必要的。

检查exception问题

有第三种驱动力可能使情况变得更糟 ,而且Java支持两种types的exception:被选中和未被选中。 编译器要求我们处理或显式声明已检查的exception,但不需要任何未检查的exception。 因此,这会产生一个有趣的问题,因为大多数function接口的方法签名没有声明抛出任何exception。 所以,例如,这是不可能的:

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

它不能完成,因为write操作抛出一个检查的exception(即IOException ),但Consumer方法的签名没有声明它抛出任何exception。 所以,解决这个问题的唯一办法就是创build更多的接口,一些声明exception,一些不行(或者在语言层面提供另一种exception透明的机制,再次,让专家“不那么糟糕”小组决定在这种情况下什么也不做。

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

[…]是的,你必须提供你自己的特殊的SAMs。 但是,然后lambda转换将正常工作。

EG为此讨论了额外的语言和图书馆支持,最终认为这是一个糟糕的成本/收益折衷。

基于图书馆的解决scheme导致了SAMtypes的2倍爆炸(exceptionvs不),与原始专业化的现有组合爆炸相互作用很差。

可用的基于语言的解决scheme是复杂性/价值权衡的失败者。 尽pipe还有其他解决scheme,但我们仍将继续探索 – 尽pipe显然不是8,也可能不是9。

同时,你有工具来做你想做的事情。 我认为你更喜欢我们为你提供最后一英里(其次,你的请求实际上是一个“你为什么不放弃已经检查过的exception”的简单请求),但是我认为当前状态可以让你完成了你的工作。 […]

所以,开发人员应该根据具体情况制定更多的接口爆发来处理这些问题。

 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); } }; } 

为了做到:

 Writer out = new StringWriter(); Consumer<String> printer = exceptionWrappingBlock(s -> out.write(s)); 

可能在将来(也许是JDK 9),当我们获得对Java和Reification中值types的支持时,我们将能够摆脱(或者至less不再需要使用)这些多个接口中的一些。

总之,我们可以看到,专家组在devise上遇到了一些困难。 为了保持向后兼容性的需要,要求或限制使事情变得困难,那么我们还有其他重要的条件,如缺less值types,types擦除和检查exception。 如果Java有第一个和第二个缺lessJDK 8的devise可能会有所不同。 所以,我们都必须明白,这是一个很多折衷的难题,EG必须在某个地方划一条线并作出决定。