Java SE 8是否有Pairs或Tuples?

nec*_*cer 174 java lambda functional-programming java-8 java-stream

我正在玩Java SE 8中的惰性函数操作,我想要一个对/元组map的索引,然后基于第二个元素,最后只输出索引.i(i, value[i])filtervalue[i]

我是否仍然要忍受这个问题:Java中的C++ Pair <L,R>相当于什么?在lambdas和溪流的大胆新时代?

更新:我提供了一个相当简化的示例,其中有一个由@dkatzel提供的简洁解决方案,其中一个答案如下.但是,它没有概括.因此,让我添加一个更一般的例子:

package com.example.test;

import java.util.ArrayList;
import java.util.stream.IntStream;

public class Main {

  public static void main(String[] args) {
    boolean [][] directed_acyclic_graph = new boolean[][]{
        {false,  true, false,  true, false,  true},
        {false, false, false,  true, false,  true},
        {false, false, false,  true, false,  true},
        {false, false, false, false, false,  true},
        {false, false, false, false, false,  true},
        {false, false, false, false, false, false}
    };

    System.out.println(
        IntStream.range(0, directed_acyclic_graph.length)
        .parallel()
        .mapToLong(i -> IntStream.range(0, directed_acyclic_graph[i].length)
            .filter(j -> directed_acyclic_graph[j][i])
            .count()
        )
        .filter(n -> n == 0)
        .collect(() -> new ArrayList<Long>(), (c, e) -> c.add(e), (c1, c2) -> c1.addAll(c2))
    );
  }

}
Run Code Online (Sandbox Code Playgroud)

这给出了不正确的输出,[0, 0, 0]该输出对应于所有三列的计数false.我需要的是这三列的索引.应该是正确的输出[0, 2, 4].我怎样才能得到这个结果?

Stu*_*rks 192

更新:这个答案是对原始问题的回答,Java SE 8是否有Pairs或Tuples?(并且隐含地,如果不是,为什么不呢?)OP已经用更完整的示例更新了问题,但似乎可以在不使用任何类型的Pair结构的情况下解决它.[OP注意:这是另一个正确答案.]


最简洁的答案是不.您要么必须自己动手,要么引入实现它的几个库中的一个.

Pair在Java SE中有一个类被提议并且至少被拒绝一次.在OpenJDK的一个邮件列表上查看此讨论主题.权衡并不明显.一方面,在其他库和应用程序代码中有许多Pair实现.这表明了一种需求,将这样的类添加到Java SE将增加重用和共享.另一方面,拥有一个Pair类会增加从Pairs和集合中创建复杂数据结构的诱惑,而不会创建必要的类型和抽象.(这是Kevin Bourillion从那个帖子中传出的消息.)

我建议所有人都阅读整个电子邮件主题.它非常富有洞察力,没有任何损害.这很有说服力.当它开始时,我想,"是的,Java SE中应该有一个Pair类"但是当线程到达终点时我改变了主意.

但请注意,JavaFX具有javafx.util.Pair类.JavaFX的API与Jav​​a SE API分开演变.

从链接问题可以看出,Java中C++对的等价物是什么?有一个相当大的设计空间围绕着显然是如此简单的API.对象应该是不可变的吗?它们应该可序列化吗?他们应该具有可比性吗 班级应该是最终的吗?这两个元素应该订购吗?它应该是一个接口还是一个类?为什么要成对停止?为什么不是三元组,四元组或N元组?

当然,元素的命名也是不可避免的:

  • (a,b)
  • (第一秒)
  • (左右)
  • (汽车,司机)
  • (foo,bar)
  • 等等

几乎没有提到的一个大问题是Pairs与原始的关系.如果您有一个(int x, int y)表示2D空间中某个点的基准面,则将其表示为Pair<Integer, Integer>消耗三个对象而不是两个32位字.此外,这些对象必须驻留在堆上,并且会产生GC开销.

看起来很清楚,像Streams一样,对于原始的专业化对于Pairs来说是至关重要的.我们想看到:

Pair
ObjIntPair
ObjLongPair
ObjDoublePair
IntObjPair
IntIntPair
IntLongPair
IntDoublePair
LongObjPair
LongIntPair
LongLongPair
LongDoublePair
DoubleObjPair
DoubleIntPair
DoubleLongPair
DoubleDoublePair
Run Code Online (Sandbox Code Playgroud)

即使是IntIntPair仍然需要堆上的一个对象.

当然,这些都让人联想到java.util.functionJava SE 8中软件包中功能接口的激增.如果你不想要一个膨胀的API,你会遗漏哪些?您也可以认为这还不够,而且Boolean应该添加专业化.

我的感觉是,如果Java很久以前就已经添加了一个Pair类,那么它本来就很简单,甚至是简单的,并且它不会满足我们现在想象的许多用例.考虑如果在JDK 1.0时间框架中添加了Pair,它可能是可变的!(看看java.util.Date.)人们会对此感到高兴吗?我的猜测是,如果在Java中有一个Pair类,它将是有点排序 - 并非真正有用,并且每个人仍然会自己滚动以满足他们的需求,在外部库中会有各种Pair和Tuple实现,人们仍然在争论/讨论如何修复Java的Pair类.换句话说,就像我们今天在同一个地方.

同时,正在进行一些工作来解决基本问题,即在JVM(最终是Java语言)中为值类型提供更好的支持.请参阅此值的状态文档.这是初步的,推测性的工作,它只涵盖了JVM角度的问题,但它背后已有相当多的思考.当然,不能保证这会进入Java 9,或者无论何时进入Java 9,但它确实显示了当前关于这个主题的思考方向.

  • @necromancer带有原语的工厂方法对`Pair <T,U>`没有帮助.由于泛型必须是引用类型.任何基元在存储时都会被装箱.要存储基元,您确实需要一个不同的类. (3认同)
  • @necromancer回想起来,是的,盒装的原始构造函数不应该是公共的,而`valueOf`应该是获取盒装实例的唯一方法.但是那些自Java 1.0以来一直在那里,并且可能在这一点上不值得尝试改变. (3认同)
  • 显然,应该只有*一个*public` Pair`或`Tuple`类,其工厂方法在后台透明地创建必要的特化类(具有优化存储).最后,lambdas就是这样做的:它们可以捕获任意数量的任意类型的变量.现在映像一个语言支持,允许在运行时由`invokedynamic`指令触发创建适当的元组类... (3认同)
  • 这样@Holger的东西,如果一个被改造的值类型到现有的JVM可能会奏效,但值类型的提案(现["项目瓦尔哈拉"(http://mail.openjdk.java.net/pipermail/discuss/2014- June/003445.html))更加激进.特别是,它的值类型不一定是堆分配的.而且,与今天的对象不同,并且像今天的原语一样,值也没有身份. (3认同)
  • @Stuart Marks:这不会干涉,因为我描述的类型可能是这种值类型的"盒装"类型.使用基于`invokedynamic'的工厂类似于lambda创建,这样后来的改造就没有问题了.顺便说一句,lambdas也没有身份.如明确所述,您今天可能认识到的身份是当前实现的工件. (2认同)
  • Scala 和 Spark(甚至 Java Spark API)都展示了 Pair 或 Tuple 概念的强大之处。重点不是将其作为对数据需求的简单抽象,而是基于这些 Pair 对它进行泛化的 Stream / RDD api,尤其是 groupBy reduceBy 之类的东西。`RDD&lt;Pair&lt;K,V&gt;&gt;` --&gt; `groupByKey` --&gt; `RDD&lt;Pair&lt;K,List&lt;V&gt;&gt;`。可以在 Java 8 中模仿的东西,但实现成本很高。我们所有的 Java 8 Streams 都缺少必要的 K/V 处理。 (2认同)

sen*_*erh 42

您可以查看这些内置类:

  • 对于成对的内置功能,这是正确的答案.请注意,`SimpleImmutableEntry`仅保证存储在`Entry`中的引用不会改变,而不是链接的`key`和`value`对象(或它们链接到的对象的对象)的字段不会改变. (3认同)

bla*_*dri 20

遗憾的是,Java 8没有引入对或元组.您当然可以使用org.apache.commons.lang3.tuple(我个人也会与Java 8结合使用),或者您可以创建自己的包装器.或使用地图.或类似的东西,正如你所接受的那个问题的公认答案中所解释的那样.


Zhe*_*lov 18

从 Java 9 开始,您可以Map.Entry比以前更容易地创建实例:

Entry<Integer, String> pair = Map.entry(1, "a");
Run Code Online (Sandbox Code Playgroud)

Map.entry返回不可修改Entry并禁止空值。


Stu*_*rks 14

似乎可以在不使用任何类型的Pair结构的情况下解决完整示例.关键是过滤列索引,谓词检查整个列,而不是将列索引映射到该列中的false条目数.

执行此操作的代码在此处:

    System.out.println(
        IntStream.range(0, acyclic_graph.length)
            .filter(i -> IntStream.range(0, acyclic_graph.length)
                                  .noneMatch(j -> acyclic_graph[j][i]))
            .boxed()
            .collect(toList()));
Run Code Online (Sandbox Code Playgroud)

这导致其输出[0, 2, 4]是我认为OP请求的正确结果.

还要注意boxed()的操作,箱int值到Integer对象.这使得人们可以使用预先存在的toList()收集器而不必写出自己进行装箱的收集器功能.


dka*_*zel 6

由于您只关心索引,因此根本不需要映射到元组.为什么不编写一个使用数组中查找元素的过滤器?

     int[] value =  ...


IntStream.range(0, value.length)
            .filter(i -> value[i] > 30)  //or whatever filter you want
            .forEach(i -> System.out.println(i));
Run Code Online (Sandbox Code Playgroud)


wum*_*mpz 6

Vavr(以前称为Javaslang)http://www.vavr.io)也提供元组(直到大小为8)。这是Javadoc:https : //static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/Tuple.html

这是一个简单的示例:

Tuple2<Integer, String> entry = Tuple.of(1, "A");

Integer key = entry._1;
String value = entry._2;
Run Code Online (Sandbox Code Playgroud)

为什么JDK本身至今没有简单的元组,这对我来说还是个谜。编写包装器类似乎每天都在做。


Tho*_*sen 5

是。

Map.Entry可以用作一个Pair

不幸的是,它对Java 8流没有帮助,因为问题是即使lambda可以接受多个参数,但Java语言仅允许返回单个值(对象或原始类型)。这意味着,每当您有流时,最终都会从上一个操作传递单个对象。这是Java语言中的一种不足,因为如果支持多个返回值并且流支持它们,那么流可以完成更好的非平凡任务。

在那之前,只有很少的用途。

EDIT 2018-02-12:在处理项目时,我编写了一个帮助器类,该类有助于处理在稍后需要的流中尽早拥有标识符的特殊情况,但介于两者之间的流部分对此一无所知。直到我避开释放它自身的它是在IdValue.java与单元测试IdValueTest.java