Java SE 8是否具有对或元组?


183

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

我还必须忍受这一点吗:Java中的C ++ Pair <L,R>等效项是什么?在lambdas和stream的大胆新时代中?

更新:我提供了一个相当简化的示例,该示例在以下答案之一中提供了@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]该输出对应于all的三列的计数false。我需要的是这三列的索引。正确的输出应为[0, 2, 4]。我怎么能得到这个结果?


2
已经AbstractMap.SimpleImmutableEntry<K,V>有很多年了……但是,无论如何,不​​是仅映射i(i, value[i])进行过滤value[i]并映射回i为什么不value[i]首先进行过滤而不进行映射?
Holger 2014年

@Holger我需要知道数组的哪些索引包含与条件匹配的值。没有保留i在流中,我无法做到。我也需要value[i]标准。这就是为什么我需要(i, value[i])
necromancer 2014年

1
@necromancer是的,仅当从索引获取值便宜(例如数组,随机访问集合或便宜函数)时,它才有效。我想问题是您想提出一个简化的用例,但是它被过度简化了,因此屈服于一个特殊的情况。
Stuart Marks 2014年

1
@necromancer我对最后一段进行了一些编辑,以澄清我认为您要提出的问题。这样对吗?另外,这是关于有向图(非非循环图)的问题吗?(不是很重要。)最后,所需的输出应该是[0, 2, 4]吗?
斯图尔特·马克

1
我相信解决此问题的正确解决方案是在将来的Java版本中将元组作为返回类型(作为Object的特殊情况),并使lambda表达式能够直接将此类元组用作其参数。
托尔比约恩Ravn的安徒生

Answers:


205

更新:此答案是对原始问题的回答,即Java SE 8是否具有对或元组?(如果不是,则隐式地,为什么不这样做?)OP用一个更完整的示例更新了该问题,但似乎无需使用任何一种Pair结构即可解决该问题。[OP的注:这是另一个正确答案。]


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

Pair建议在Java SE中创建一个类,并至少拒绝一次。请参阅OpenJDK邮件列表之一上的讨论线程。权衡并不明显。一方面,其他库和应用程序代码中有许多Pair实现。这就表明有必要,将这样的类添加到Java SE中将增加重用和共享。另一方面,拥有Pair类增加了在Pairs和collections中创建复杂数据结构而不创建必要的类型和抽象的诱惑。(这是凯文·布尔里恩Kevin Bourillion)从该帖子发出的信息的解释。)

我建议大家阅读整个电子邮件主题。它非常有见地,没有发炎。这很有说服力。刚开始的时候我以为“是的,Java SE中应该有一个Pair类”,但是当线程到达末尾时,我已经改变了主意。

但是请注意,JavaFX具有javafx.util.Pair类。JavaFX的API与Jav​​a SE API分开发展。

从链接的问题中可以看出,Java中的C ++对相当于什么?很显然,如此简单的API周围有很大的设计空间。对象应该是不可变的吗?它们应该可序列化吗?它们应该具有可比性吗?上课应该是最后的吗?这两个元素应该排序吗?应该是接口还是类?为什么要结伴而行?为什么不使用三元组,四元组或N元组?

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

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

几乎没有提到的一个大问题是成对与基元的关系。如果您有一个(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

即使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,也不会进入任何地方,但是它确实显示了有关此主题的最新思路。


3
@necromancer具有原始类型的Factory方法无济于事Pair<T,U>。由于泛型必须是引用类型。存储任何原语时,它们都会被装箱。要存储原语,您确实需要其他类。
Stuart Marks 2014年

3
@necromancer是的,回想起来,装箱的原始构造函数不应该是公开的,valueOf应该是获取装箱实例的唯一方法。但是自Java 1.0以来,它们就已经存在了,此时可能不值得尝试进行更改。
Stuart Marks 2014年

3
显然,应该只有一个公共PairTuple带有工厂方法的类在后台透明地创建必要的专业类(具有优化的存储)。最后,lambda正是这样做的:它们可以捕获任意数量的任意类型的变量。现在,图像支持语言,允许在由invokedynamic指令触发的运行时创建适当的元组类…
Holger 2014年

3
@Holger如果将值类型改型到现有的JVM上,类似的事情可能会起作用,但是Value Types提案(现在是“ Project Valhalla”)更加激进。特别是,其值类型不必一定是堆分配的。同样,与今天的对象不同,与今天的图元一样,值将没有身份。
Stuart Marks 2014年

2
@Stuart Marks:这不会造成干扰,因为我描述的类型可能是这种值类型的“盒装”类型。对于invokedynamic与lambda创建类似的基础工厂,这种后续的改装将没有问题。顺便说一句,lambda也没有身份。正如明确指出的那样,您今天可能会意识到的身份是当前实现的产物。
Holger 2014年


22

可悲的是,Java 8没有引入对或元组。当然,您总是可以使用org.apache.commons.lang3.tuple(我个人确实将其与Java 8结合使用),也可以创建自己的包装器。或使用地图。或类似的内容,如您所链接的问题的可接受答案所解释。


更新: JDK 14引入了记录作为预览功能。这些不是元组,但可以用来解决许多相同的问题。在上面的特定示例中,可能看起来像这样:

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

在使用JDK 14进行编译并使用该--enable-preview标志运行时(在撰写本文时,这是一个早期访问版本),您将得到以下结果:

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

实际上,@ StuartMarks的答案之一使我无需使用元组即可解决该问题,但是由于它似乎无法一概而论,因此我最终可能会需要它。
necromancer

@necromancer是的,这是一个很好的答案。apache库有时仍然可以派上用场,但是所有这些都可以归结为Java语言设计。基本上,元组必须是原语(或类似语言)才能像在其他语言中一样工作。
blalasaadri 2014年

1
如果您没有注意到它,答案将包括以下非常有用的链接:cr.openjdk.java.net/~jrose/values/values-0.html关于此类元组(包括元组)的需求和前景。
necromancer

17

似乎完整的示例可以在不使用任何对结构的情况下解决。关键是过滤列索引,用谓词检查整个列,而不是将列索引映射到该列中的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()));

这导致输出[0, 2, 4]这是我觉得在OP请求正确的结果。

还请注意将值boxed()装箱intInteger对象中的操作。这使人们可以使用预先存在的toList()收集器,而不必写出自己进行装箱的收集器功能。


1
+1 ace :)这仍然不能一概而论,对吧?那是问题的更实质性的方面,因为我希望面对这样的方案不起作用的其他情况(例如,不超过3个值的列true)。因此,我将接受您的其他答案是正确的,但也要指出这一点!非常感谢:)
necromancer

这是正确的,但接受同一用户的其他答案。(请参阅上面和其他地方的评论。)
necromancer

1
@necromancer是的,在您想要索引的情况下,这种技术并不完全通用,但是无法使用索引来检索或计算数据元素。(至少不容易。)例如,考虑一个问题,您正在从网络连接中读取文本行,并且想要查找与某种模式匹配的第N行的行号。最简单的方法是将每条线映射为对或某些复合数据结构以对线进行编号。尽管没有新的数据结构,但可能有一种怪异的副作用方法。
Stuart Marks 2014年

@StuartMarks,一对是<T,U>。三元组<T,U,V>。等等。您的示例是列表,而不是一对。
Pacerier

7

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;

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


某些版本的vavr在引擎盖下使用偷偷摸摸的抛出。注意不要使用那些。
托尔比约恩Ravn的安德森


6

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

     int[] value =  ...


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

+1为实用的解决方案。但是,我不确定它是否可以推广到我即时生成值的情况。我将问题作为一个数组提出,以提供一个简单的案例来思考,您确实提出了一个很好的解决方案。
necromancer

5

是。

Map.Entry可以用作Pair

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

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

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


2

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

Tuples工厂可以创建的实例Pair,并且PrimitiveTuples工厂可以用来创建原始/对象对所有组合。

我们在Java 8发布之前添加了这些功能。它们对于为我们的原始映射实现键/值迭代器很有用,我们在所有原始/对象组合中也支持这些迭代器。

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.