Java中与C++的Pair<L,R>等价的是什么?

713
有没有很好的理由解释为什么Java中没有Pair<L,R>?在Java中有何等效于C++构造函数的内容?我宁愿避免重新实现我自己的构造函数。
似乎Java 1.6提供了类似的内容(AbstractMap.SimpleEntry<K,V>),但这看起来相当复杂。

10
为什么 AbstractMap.SimpleEntry 看起来很复杂? - CurtainDog
29
由于命名的原因,任意地将一个键和一个值进行命名。 - Enerccio
2
请参见https://dev59.com/22025IYBdhLWcg3wAg7p。 - Raedwald
2
@sffc JavaFX不在JDK7的默认类路径中,使用它需要手动添加JFX运行时库。 - Cord Rehn
4
您的意思是,“第一”和“第二”并非随意的,而“键”和“值”却是随意的?那么这就是SDK中没有此类的一个很好的理由。将会有一个永无止境的争议关于“正确”的命名。 - fdreger
显示剩余3条评论
37个回答

424
一篇有关comp.lang.java.help的帖子中,Hunter Gratzner提出了一些反对Java中存在Pair构造的论点。主要的论据是Pair类没有传达任何关于两个值之间关系的语义(你怎么知道“first”和“second”是什么意思?)。
一个更好的做法是为您将使用Pair类的每个应用编写一个非常简单的类,就像Mike建议的那样。 Map.Entry是一个携带名称含义的pair的示例。
总之,在我看来,最好拥有一个Position(x,y)类,一个Range(begin,end)类和一个Entry(key,value)类,而不是一个不告诉我任何有关其所需功能的通用Pair(first,second)类。

184
Gratzner在挑毛病。我们很乐意返回一个基本类型或内置类的单个值,而不需要将其封装在一个类中。如果我们返回了一组包含十几个元素的元组,没有人会反对它需要有自己的类。在中间某处存在一个模糊的分界线。我认为我们的“爬行动物大脑”足以轻松处理成对(Pair)这种情况。 - Ian
37
我同意Ian的观点。Java允许您返回int类型,而不强制您每次使用时都创建int类型的别名。Pair(一种数据结构)与此并没有太大区别。 - Clément
5
如果我们可以直接将一对值解包到您的本地变量中,或将其转发给接受两个参数的方法,那么Pair将是一个有用的类。由于我们无法像这样解包它,因此创建一个有意义的类并将值保持在一起看起来并不太糟糕。如果您真的想要一对值,尽管有局限性,总还有Object[2] + 强制转换 :-) - marcus
问题在于,如果您不同意Gratzner的观点,那么有几个地方都有Pair实现。我记得Apache Commons和Guava都有。使用它们。但是将某些东西放入主要的Java库中意味着这是一种高尚且经过批准的做事方式(带有大写字母),由于人们对此意见不一,我们不应该将其放在那里。旧库中已经有足够多的垃圾了,让我们不要不必要地再添加更多。 - Haakon Løtveit
2
@Dragas 当我需要一对值时,这不是Java...认真的吗? - 463035818_is_not_a_number
显示剩余3条评论

167

这是Java。您需要制作自己的定制化Pair类,使用描述性的类和字段名称,并且不必担心通过编写hashCode()/equals()或实现Comparable来重复造轮子。


24
如果你指向了包含Pair类的Apache Commong Lang,Java-mockery就没问题了。 - haylem
6
你可以使用 SimpleImmutableEntry - CurtainDog
最常见的IDE会为您生成适当的HashCode()/equals。 - java-addict301
3
我同意。“因为这是Java”是一个好的答案。请记住,Java语言有意剥夺了某些(咳咳,C++)会令普通程序员感到困惑的特性。这就是为什么Java不允许你重新定义运算符。它也不允许多重继承。归根结底,如果某些愚蠢的程序员可能滥用它,那么Java将使其难以实现。 - John Henckel
1
@JohnHenckel 这就像考试时老师出简单题目,因为他想让每个人都通过一样愚蠢。 - stillanoob
@TimGoodman穿着斯巴达装,大喊着将新生踢进标准名称路径的坑里。 - Swift - Friday Pie

109

HashMap 兼容的 Pair 类:

public class Pair<A, B> {
    private A first;
    private B second;

    public Pair(A first, B second) {
        super();
        this.first = first;
        this.second = second;
    }

    public int hashCode() {
        int hashFirst = first != null ? first.hashCode() : 0;
        int hashSecond = second != null ? second.hashCode() : 0;

        return (hashFirst + hashSecond) * hashSecond + hashFirst;
    }

    public boolean equals(Object other) {
        if (other instanceof Pair) {
            Pair otherPair = (Pair) other;
            return 
            ((  this.first == otherPair.first ||
                ( this.first != null && otherPair.first != null &&
                  this.first.equals(otherPair.first))) &&
             (  this.second == otherPair.second ||
                ( this.second != null && otherPair.second != null &&
                  this.second.equals(otherPair.second))) );
        }

        return false;
    }

    public String toString()
    { 
           return "(" + first + ", " + second + ")"; 
    }

    public A getFirst() {
        return first;
    }

    public void setFirst(A first) {
        this.first = first;
    }

    public B getSecond() {
        return second;
    }

    public void setSecond(B second) {
        this.second = second;
    }
}

145
你可能希望删除设置器,将first和second变为final,从而使这个pair成为不可变对象。(如果在将其用作散列键后更改了组件,则会发生奇怪的事情)。 - Thilo
22
在toString()方法中,"return "(" + first.toString() + ", " + second.toString() + ")" 可能会抛出NullPointerException(空指针异常)。更好的写法是:return "(" + first + ", " + second + ")"。请注意不要改变原来的意思。 - Juha Syrjälä
6
请问需要翻译的是什么语言呢? - sargas
9
抱歉问一个初学者的随机问题,但是为什么在构造函数中要调用super()呢? - Ibrahim
6
在这种情况下,super()是多余的——如果你将super()去掉,行为完全相同。通常情况下,如果选项是可选的,像这里一样,我会直接删除它。 - C. K. Young
显示剩余4条评论

58

我能想到的最短的一对代码如下,使用Lombok

@Data
@AllArgsConstructor(staticName = "of")
public class Pair<F, S> {
    private F first;
    private S second;
}

它具有@arturh的答案所有好处(除了可比性),它具有hashCodeequalstoString和一个静态“构造函数”。



32

另一种实现Pair的方法。

  • 使用不可变公共字段,即简单数据结构。
  • 可比较的。
  • 使用简单的哈希和相等性判断。
  • 简单工厂模式,使您不必提供类型。例如:Pair.of("hello", 1);

public class Pair<FIRST, SECOND> implements Comparable<Pair<FIRST, SECOND>> {

    public final FIRST first;
    public final SECOND second;

    private Pair(FIRST first, SECOND second) {
        this.first = first;
        this.second = second;
    }

    public static <FIRST, SECOND> Pair<FIRST, SECOND> of(FIRST first,
            SECOND second) {
        return new Pair<FIRST, SECOND>(first, second);
    }

    @Override
    public int compareTo(Pair<FIRST, SECOND> o) {
        int cmp = compare(first, o.first);
        return cmp == 0 ? compare(second, o.second) : cmp;
    }

    // todo move this to a helper class.
    private static int compare(Object o1, Object o2) {
        return o1 == null ? o2 == null ? 0 : -1 : o2 == null ? +1
                : ((Comparable) o1).compareTo(o2);
    }

    @Override
    public int hashCode() {
        return 31 * hashcode(first) + hashcode(second);
    }

    // todo move this to a helper class.
    private static int hashcode(Object o) {
        return o == null ? 0 : o.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Pair))
            return false;
        if (this == obj)
            return true;
        return equal(first, ((Pair) obj).first)
                && equal(second, ((Pair) obj).second);
    }

    // todo move this to a helper class.
    private boolean equal(Object o1, Object o2) {
        return o1 == null ? o2 == null : (o1 == o2 || o1.equals(o2));
    }

    @Override
    public String toString() {
        return "(" + first + ", " + second + ')';
    }
}

10
我喜欢静态工厂方法of。它让我想起了Google Guava的不可变集合。 - Jarek Przygódzki
7
在某个点上,你将 o1 强制转换为 Comparable,尽管没有任何迹象表明它实际上会实现该接口。如果这是一个要求,那么 FIRST 类型参数应该为 FIRST extends Comparable<?> - G_H
3
31作为hashCode的常数很差。例如,如果您使用以Pair<Integer,Integer>为键的HashMap来表示2D地图,会发生许多哈希冲突。相比之下,使用(a*65497)^b会更合适。 - Michał Zieliński
@MichałZieliński 我认为31在这里是因为31是Eclipse最喜欢的质数(当您使用“generate hashCode() and equals()”时会出现)。然而,对于这样一个通用类来说,计算幂将会太昂贵了。 - Mario Carneiro
1
@MarioCarneiro ^ 是异或,不是乘方 - Michał Zieliński
显示剩余4条评论

28

你觉得 http://www.javatuples.org/index.html 怎么样呢?我发现它非常有用。

javatuples 提供了 1 到 10 个元素的 tuple 类。

Unit<A> (1 element)
Pair<A,B> (2 elements)
Triplet<A,B,C> (3 elements)
Quartet<A,B,C,D> (4 elements)
Quintet<A,B,C,D,E> (5 elements)
Sextet<A,B,C,D,E,F> (6 elements)
Septet<A,B,C,D,E,F,G> (7 elements)
Octet<A,B,C,D,E,F,G,H> (8 elements)
Ennead<A,B,C,D,E,F,G,H,I> (9 elements)
Decade<A,B,C,D,E,F,G,H,I,J> (10 elements)

7
有趣,但至少有5个班级我从未想过会用到。 - maaartinus
3
至少比我用的多10个。 - Boann
7
@Boann: 好的,我改正了。我曾经使用 Pair,也许50年才用一次 Triplet。现在我使用 Lombok 并每次需要一对时创建一个仅有4行的小类。因此,“10太多”是确切的。 - maaartinus
6
我们需要一个“底部(0元素)”类吗? :) - Earth Engine
2
哇,这太丑了。我知道他们试图使其明确,但是像C#中那样具有重载参数的元组会更好。 - arviman
显示剩余6条评论

14

Android 提供了 Pair 类 (http://developer.android.com/reference/android/util/Pair.html),以下是其实现:

public class Pair<F, S> {
    public final F first;
    public final S second;

    public Pair(F first, S second) {
        this.first = first;
        this.second = second;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Pair)) {
            return false;
        }
        Pair<?, ?> p = (Pair<?, ?>) o;
        return Objects.equal(p.first, first) && Objects.equal(p.second, second);
    }

    @Override
    public int hashCode() {
        return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
    }

    public static <A, B> Pair <A, B> create(A a, B b) {
        return new Pair<A, B>(a, b);
    }
}

1
"Objects.equal(..)" 需要使用 Guava 库。 - Markus L
4
将其改为Objects.equals(...),该方法自2011年(1.7版本)起已经存在于Java中。 - AndrewF

12

这取决于你想用它来做什么。通常的原因是为了遍历映射,你只需这样做即可(Java 5+):

Map<String, Object> map = ... ; // just an example
for (Map.Entry<String, Object> entry : map.entrySet()) {
  System.out.printf("%s -> %s\n", entry.getKey(), entry.getValue());
}

1
我不确定自定义类在这种情况下是否有帮助 :) - Nikita Rybak
40
真的吗?这样做的典型原因是为了遍历地图。 - Bennett McElwee

9
最大的问题可能是不能确保 A 和 B 的不可变性(参见 如何确保类型参数是不可变的),因此在将 Pair 插入集合之后,hashCode() 可能会为相同的 Pair 产生不一致的结果(这将导致未定义的行为,请参见 以可变字段定义等价关系)。对于特定的(非泛型)Pair 类,程序员可以通过仔细选择 A 和 B 来确保其不可变性。

无论如何,从 @PeterLawrey 的答案(Java 1.7)中消除泛型警告:

public class Pair<A extends Comparable<? super A>,
                    B extends Comparable<? super B>>
        implements Comparable<Pair<A, B>> {

    public final A first;
    public final B second;

    private Pair(A first, B second) {
        this.first = first;
        this.second = second;
    }

    public static <A extends Comparable<? super A>,
                    B extends Comparable<? super B>>
            Pair<A, B> of(A first, B second) {
        return new Pair<A, B>(first, second);
    }

    @Override
    public int compareTo(Pair<A, B> o) {
        int cmp = o == null ? 1 : (this.first).compareTo(o.first);
        return cmp == 0 ? (this.second).compareTo(o.second) : cmp;
    }

    @Override
    public int hashCode() {
        return 31 * hashcode(first) + hashcode(second);
    }

    // TODO : move this to a helper class.
    private static int hashcode(Object o) {
        return o == null ? 0 : o.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Pair))
            return false;
        if (this == obj)
            return true;
        return equal(first, ((Pair<?, ?>) obj).first)
                && equal(second, ((Pair<?, ?>) obj).second);
    }

    // TODO : move this to a helper class.
    private boolean equal(Object o1, Object o2) {
        return o1 == o2 || (o1 != null && o1.equals(o2));
    }

    @Override
    public String toString() {
        return "(" + first + ", " + second + ')';
    }
}

欢迎添加/更正内容 :) 具体来说,我对于使用 Pair<?, ?> 不是很确定。

如需了解此语法的更多信息,请参见确保对象实现Comparable;有关详细说明,请参见如何在Java中实现通用的max(Comparable a, Comparable b)函数?


由于Java整数是32位的,将第一个哈希码乘以31不会导致溢出吗?使用异或运算符是否更好? - Dan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接