我正在Java SE 8中使用惰性函数操作,我想将一个索引I映射到一个对/元组(I,值[I]),然后根据第二个值[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))
    );
  }

}

这给出了不正确的输出[0,0,0],它对应于三列的计数都是假的。我需要的是这三列的下标。正确的输出应该是[0,2,4]。我怎样才能得到这个结果?


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


更新:JDK 14将记录类作为预览特性引入,JDK 16将它们作为标准语言特性引入。它们不是元组,但可以用来解决许多相同的问题。在上面的具体例子中,可能是这样的:

public class Jdk14Example {
    record CountForIndex(int index, long count) {}

    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()
                        .mapToObj(i -> {
                            long count = IntStream.range(0, directed_acyclic_graph[i].length)
                                            .filter(j -> directed_acyclic_graph[j][i])
                                            .count();
                            return new CountForIndex(i, count);
                        }
                        )
                        .filter(n -> n.count == 0)
                        .collect(() -> new ArrayList<CountForIndex>(), (c, e) -> c.add(e), (c1, c2) -> c1.addAll(c2))
        );
    }
}

当使用——enable-preview标志或JDK 16或更高版本编译并运行JDK 14时,你会得到以下结果:

[CountForIndex[index=0, count=0], CountForIndex[index=2, count=0], CountForIndex[index=4, count=0]]

因为您只关心索引,所以根本不需要映射到元组。为什么不直接编写一个过滤器来使用数组中的查找元素呢?

     int[] value =  ...


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

更新:这个答案是在回应最初的问题,Java SE 8有对或元组吗?(如果没有,那为什么不呢?)OP已经用一个更完整的例子更新了这个问题,但它似乎可以在不使用任何形式的Pair结构的情况下解决。[OP注:这是另一个正确答案。]


简短的回答是否定的。您要么必须滚出您自己的,要么引入实现它的几个库之一。

Having a Pair class in Java SE was proposed and rejected at least once. See this discussion thread on one of the OpenJDK mailing lists. The tradeoffs are not obvious. On the one hand, there are many Pair implementations in other libraries and in application code. That demonstrates a need, and adding such a class to Java SE will increase reuse and sharing. On the other hand, having a Pair class adds to the temptation of creating complicated data structures out of Pairs and collections without creating the necessary types and abstractions. (That's a paraphrase of Kevin Bourillion's message from that thread.)

我建议大家阅读整封邮件。这是非常深刻的见解,没有火焰。这很有说服力。当它开始时,我想,“是的,在Java SE中应该有一个Pair类”,但当线程到达它的结尾时,我改变了我的想法。

但是请注意,JavaFX有JavaFX .util. pair类。JavaFX的api是从Java SE api独立发展而来的。

从链接的问题中可以看到,在Java中什么是等价的c++对?显然,如此简单的API有相当大的设计空间。对象应该是不可变的吗?它们应该是可序列化的吗?它们是否具有可比性?这门课到底要不要结束?这两个元素应该排序吗?应该是接口还是类?为什么止步于结对?为什么不是三元组、四元组或n元组?

当然,元素不可避免地会被命名为bikeshed:

(a, b) (一、二) (左,右) (汽车,cdr) (foo, bar) 等。

一个很少被提及的大问题是pair与原语的关系。如果有一个(int x, int y)数据表示2D空间中的一个点,表示为Pair<Integer, Integer>占用三个对象,而不是两个32位的单词。此外,这些对象必须驻留在堆上,并且会引起GC开销。

很明显,就像Streams一样,pair必须有基本的专门化。我们想看到:

Pair
ObjIntPair
ObjLongPair
ObjDoublePair
IntObjPair
IntIntPair
IntLongPair
IntDoublePair
LongObjPair
LongIntPair
LongLongPair
LongDoublePair
DoubleObjPair
DoubleIntPair
DoubleLongPair
DoubleDoublePair

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

当然,这让人联想到Java SE 8中的Java .util.function包中函数接口的激增。如果你不想要一个臃肿的API,你会舍弃哪些?您也可以认为这还不够,还应该添加布尔值的专门化。

My feeling is that if Java had added a Pair class long ago, it would have been simple, or even simplistic, and it wouldn't have satisfied many of the use cases we are envisioning now. Consider that if Pair had been added in the JDK 1.0 time frame, it probably would have been mutable! (Look at java.util.Date.) Would people have been happy with that? My guess is that if there were a Pair class in Java, it would be kinda-sort-not-really-useful and everybody will still be rolling their own to satisfy their needs, there would be various Pair and Tuple implementations in external libraries, and people would still be arguing/discussing about how to fix Java's Pair class. In other words, kind of in the same place we're at today.

与此同时,一些工作正在着手解决基本问题,即JVM(最终是Java语言)更好地支持值类型。请参阅“值的状态”文档。这是初步的、推测性的工作,它只涵盖了JVM角度的问题,但它背后已经有了相当多的思考。当然,并不能保证Java 9中会出现这种情况,或者任何情况下都会出现这种情况,但它确实显示了当前对这个主题的思考方向。


看起来完整的示例可以在不使用任何类型的Pair结构的情况下求解。关键是对列索引进行筛选,用谓词检查整个列,而不是将列索引映射到该列中错误条目的数量。

实现这一功能的代码如下:

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

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

还要注意boxed()操作,它将int值装箱到Integer对象中。这样就可以使用预先存在的toList()收集器,而不必写出本身进行装箱的收集器函数。


Yes.

地图。条目可以作为Pair使用。

Unfortunately it does not help with Java 8 streams as the problem is that even though lambdas can take multiple arguments, the Java language only allows for returning a single value (object or primitive type). This implies that whenever you have a stream you end up with being passed a single object from the previous operation. This is a lack in the Java language, because if multiple return values was supported AND streams supported them we could have much nicer non-trivial tasks done by streams.

在那之前,它几乎没有什么用处。

Java 16带来了记录,这是解决这个问题和其他问题的一个很好的解决方案。一个非常强烈的理由瞄准即将到来的Java 17 LTS


你可以看看这些内置类:

AbstractMap。SimpleEntry AbstractMap。SimpleImmutableEntry


Vavr(以前称为Javaslang) (http://www.vavr.io)也提供元组(til大小为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;

为什么JDK本身直到现在还没有提供简单的元组,这对我来说是一个谜。编写包装器类似乎是一项日常工作。


Eclipse Collections具有Pair和原语/对象对的所有组合(对于所有8个原语)。

Tuples工厂可以创建Pair的实例,PrimitiveTuples工厂可以用于创建所有原语/对象对的组合。

我们在Java 8发布之前添加了这些。它们对于实现基元映射的键/值迭代器非常有用,我们也支持所有基元/对象组合。

如果您愿意增加额外的库开销,您可以使用Stuart公认的解决方案,并将结果收集到一个原始IntList中,以避免装箱。我们在Eclipse Collections 9.0中添加了新方法,以允许从Int/Long/Double流创建Int/Long/Double集合。

IntList list = IntLists.mutable.withAll(intStream);

注意:我是Eclipse Collections的提交者。


从Java 9开始,您可以创建Map的实例。比以前更容易进入:

Entry<Integer, String> pair = Map.entry(1, "a");

地图。entry返回一个不可修改的entry并禁止为空。


许多第三方库支持元组。例如jOOλ,支持0到16度的元组,例如:

// Assuming this static import
import static org.jooq.lambda.tuple.Tuple.*;

// Write:
var t = tuple(1, "a", 2L);
Integer i = t.v1;
String s = t.v2;
Long l = t.v3;

其他也有元组的库,例如:

反应堆 Javatuples Apache Flink Vavr