Java коллекция пар значений? (кортежи?)


345

Мне нравится, как в Java есть карта, где вы можете, например, определить типы каждой записи на карте <String, Integer>.

То, что я ищу, - это тип коллекции, где каждый элемент коллекции представляет собой пару значений. Каждое значение в паре может иметь свой собственный тип (например, пример String и Integer выше), который определяется во время объявления.

Коллекция будет поддерживать свой заданный порядок и не будет обрабатывать одно из значений как уникальный ключ (как на карте).

По сути, я хочу иметь возможность определить массив типа <String,Integer>или любые другие 2 типа.

Я понимаю, что могу создать класс только с двумя переменными, но это кажется слишком многословным.

Я также понимаю, что мог бы использовать 2D-массив, но из-за различных типов, которые мне нужно использовать, мне пришлось бы делать их массивами OBJECT, а затем мне приходилось бы приводить все время.

Мне нужно только хранить пары в коллекции, поэтому мне нужно только два значения для каждой записи. Существует ли что-то подобное без прохождения классового маршрута? Спасибо!


Интересно, у Гуавы тоже может быть класс для этого.
Сикорски,

Guava довольно против Pair, и люди в Google зашли так далеко, что создали гораздо лучшую альтернативу - Auto / Value . Это позволяет легко создавать хорошо типизированные классы типов значений с правильной семантикой equals / hashCode. Вам никогда не понадобится Pairтип снова!
dimo414

Ответы:


255

Класс Pair является одним из тех обобщающих примеров типа gimme, который достаточно легко написать самостоятельно. Например, от макушки головы:

public class Pair<L,R> {

  private final L left;
  private final R right;

  public Pair(L left, R right) {
    assert left != null;
    assert right != null;

    this.left = left;
    this.right = right;
  }

  public L getLeft() { return left; }
  public R getRight() { return right; }

  @Override
  public int hashCode() { return left.hashCode() ^ right.hashCode(); }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Pair)) return false;
    Pair pairo = (Pair) o;
    return this.left.equals(pairo.getLeft()) &&
           this.right.equals(pairo.getRight());
  }

}

И да, это существует в нескольких местах в сети, с различной степенью полноты и функциональности. (Мой пример выше должен быть неизменным.)


17
Мне это нравится, но что вы думаете об обнародовании левого и правого полей? Совершенно очевидно, что классу Pair никогда не будет связана какая-либо логика, и всем клиентам потребуется доступ «влево» и «вправо», так почему бы не сделать это легко?
Outlaw Programmer

43
Хм ... нет, не будет. Поля помечены как окончательные, поэтому они не могут быть переназначены. И это не потокобезопасно, потому что «left» и «right» могут быть изменяемыми. Если getLeft () / getRight () не вернул защитные копии (бесполезные в данном случае), я не понимаю, в чём дело.
Outlaw Programmer

17
Обратите внимание, что hashCode (), как есть, дает одинаковое значение, если левый и правый поменялись местами. Возможно:long l = left.hashCode() * 2654435761L; return (int)l + (int)(l >>> 32) + right.hashCode();
Кармаказе

68
Так что, если я правильно понимаю, развертывание простого парного класса приводит к синтаксической ошибке, методу hashCode subpar, исключениям нулевого указателя, отсутствию метода CompareTo, вопросам проектирования ... и люди по-прежнему выступают за развертывание этого класса, пока он существует в общем достоянии Apache. Пожалуйста, просто скопируйте код, если вы не хотите включать JAR, но прекратите изобретать велосипед!
cquezel

38
Что вы имеете в виду "достаточно легко написать самостоятельно"? Это ужасная разработка программного обеспечения. Являются ли классы адаптеров N ^ 2 для преобразования между MyPair и SomeoneElsesPair достаточно легкими для написания самостоятельно?
Джечлин

299

AbstractMap.SimpleEntry

Спокойно вы ищете это:

java.util.List<java.util.Map.Entry<String,Integer>> pairList= new java.util.ArrayList<>();

Как вы можете заполнить его?

java.util.Map.Entry<String,Integer> pair1=new java.util.AbstractMap.SimpleEntry<>("Not Unique key1",1);
java.util.Map.Entry<String,Integer> pair2=new java.util.AbstractMap.SimpleEntry<>("Not Unique key2",2);
pairList.add(pair1);
pairList.add(pair2);

Это упрощает:

Entry<String,Integer> pair1=new SimpleEntry<>("Not Unique key1",1);
Entry<String,Integer> pair2=new SimpleEntry<>("Not Unique key2",2);
pairList.add(pair1);
pairList.add(pair2);

И, с помощью createEntryметода, может еще больше сократить детализацию до:

pairList.add(createEntry("Not Unique key1", 1));
pairList.add(createEntry("Not Unique key2", 2));

Поскольку ArrayListон не является окончательным, он может быть разделен на подклассы для предоставления ofметода (и вышеупомянутого createEntryметода), что приводит к синтаксическому сжатию:

TupleList<java.util.Map.Entry<String,Integer>> pair = new TupleList<>();
pair.of("Not Unique key1", 1);
pair.of("Not Unique key2", 2);

11
К вашему сведению: в этом предложении SimpleEntryесть setValueметод исключения класса родного брата, который должен быть неизменным. Таким образом, имя SimpleImmutableEntry.
Василий Бурк

3
Это лучше, чем самореализация. Тот факт, что его легко реализовать, не является оправданием для его реализации каждый раз.
pevogam

6
Я бы не стал считать это «стандартным». Entryподразумевается, что это пара ключ-значение, и это хорошо для этого. Но у него есть слабые места как настоящий кортеж. Например, hashcodeреализация SimpleEntryпросто записывает коды элементов, поэтому <a,b>хэширует то же значение, что и <b,a>. Реализации кортежей часто сортируются лексикографически, но SimpleEntry не реализуются Comparable. Так что будьте осторожны там ...
Джин

167

Java 9+

В Java 9 вы можете просто написать: Map.entry(key, value) создать неизменную пару.

Примечание: этот метод не позволяет ключам или значениям быть нулевыми. Если вы хотите , чтобы значения NULL, например, вы хотите изменить это: Map.entry(key, Optional.ofNullable(value)).


Java 8+

В Java 8 вы можете использовать более универсальные javafx.util.Pair для создания неизменяемой, сериализуемой пары. Этот класс имеет позволяют ключи нулевых значений и нулевые. (В Java 9 этот класс включен в javafx.baseмодуль). РЕДАКТИРОВАТЬ: Начиная с Java 11, JavaFX был отделен от JDK, поэтому вам потребуется дополнительный артефакт maven org.openjfx: javafx-base.


Java 6+

В Java 6 и выше вы можете использовать более подробный AbstractMap.SimpleImmutableEntry для неизменяемой пары или AbstractMap.SimpleEntryдля пары, значение которой можно изменить. Эти классы также допускают нулевые ключи и нулевые значения и являются сериализуемыми.


Android

Если вы пишете для Android, просто используйте Pair.create(key, value) для создания неизменной пары.


Apache Commons

Apache Commons Lang предоставляет полезную Pair.of(key, value) для создания неизменяемой, сопоставимой, сериализуемой пары.


Коллекции Затмения

Если вы используете пары, содержащие примитивы, Eclipse Collections предоставляет несколько очень эффективных классов примитивных пар, которые позволят избежать всех неэффективных автоматических и автоматических распаковок.

Например, вы могли бы использовать , PrimitiveTuples.pair(int, int)чтобы создать IntIntPairили PrimitiveTuples.pair(float, long)создать FloatLongPair.


Проект Ломбок

Используя Project Lombok , вы можете создать неизменный класс пары просто написав:

@Value
public class Pair<K, V> {
    K key;
    V value;
}

Ломбок заполнит конструктора, добытчик, equals(), hashCode()и toString()методы для вас автоматически сгенерированных байт - коды. Если вы хотите статический метод фабрики вместо конструктора, например, Pair.of(k, v)просто изменить аннотацию: @Value(staticConstructor = "of").


В противном случае

Если ни одно из вышеперечисленных решений не поддерживает вашу лодку, вы можете просто скопировать и вставить следующий код (который, в отличие от класса, указанного в принятом ответе, защищает от исключений NullPointerExceptions):

import java.util.Objects;

public class Pair<K, V> {

    public final K key;
    public final V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public boolean equals(Object o) {
        return o instanceof Pair && Objects.equals(key, ((Pair<?,?>)o).key) && Objects.equals(value, ((Pair<?,?>)o).value);
    }

    public int hashCode() {
        return 31 * Objects.hashCode(key) + Objects.hashCode(value);
    }

    public String toString() {
        return key + "=" + value;
    }
}

3
JavaFX, однако, предназначен только для настольных компьютеров, добавляя ненужную зависимость для не-настольных сред (например, серверов).
foo

2
Eclipse Collections также имеет Pairинтерфейс для объектов, который может быть создан с помощью вызова Tuples.pair(object1, object2). eclipse.org/collections/javadoc/9.2.0/org/eclipse/collections/…
Дональд Рааб

2
Это похоже на самый по-настоящему Java-esque ответ. ВСЕ доступные реализации. Очень тщательно, большое спасибо!
Майк

Привет @ Ханс, я использую список List<Pair<Integer, String> listOfTuple. Как я могу использовать listOfTuple.add()? Если я сделаю - listOfTuple.add(Pair<someInteger, someString>);это не сработает. Заранее спасибо.
Me_developer

версия для Android недоступна в модульном тесте, что делает его не очень полезным ...
OznOg


31

Apache common lang3 имеет класс Pair и несколько других библиотек, упомянутых в этой теме. Что является эквивалентом пары C ++ <L, R> в Java?

Пример, соответствующий требованию из вашего исходного вопроса:

List<Pair<String, Integer>> myPairs = new ArrayList<Pair<String, Integer>>();
myPairs.add(Pair.of("val1", 11));
myPairs.add(Pair.of("val2", 17));

//...

for(Pair<String, Integer> pair : myPairs) {
  //following two lines are equivalent... whichever is easier for you...
  System.out.println(pair.getLeft() + ": " + pair.getRight());
  System.out.println(pair.getKey() + ": " + pair.getValue());
}

1
Это самый простой вариант, если доступен Apache Commons.
Кип


15

А как насчет Pairкласса «Apache Commons Lang 3» и относительных подклассов?

    import org.apache.commons.lang3.tuple.ImmutablePair;
    import org.apache.commons.lang3.tuple.Pair;
    ...
    @SuppressWarnings("unchecked")
    Pair<String, Integer>[] arr = new ImmutablePair[]{
            ImmutablePair.of("A", 1),
            ImmutablePair.of("B", 2)};

    // both access the 'left' part
    String key = arr[0].getKey();
    String left = arr[0].getLeft();

    // both access the 'right' part
    Integer value = arr[0].getValue();
    Integer right = arr[0].getRight();

ImmutablePairявляется конкретным подклассом, который не позволяет изменять значения в паре, но существуют другие реализации с другой семантикой. Это координаты Maven, если они вам нужны.

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>

11

Вы можете написать общий класс Pair <A, B> и использовать его в массиве или списке. Да, вы должны написать класс, но вы можете использовать один и тот же класс для всех типов, так что вам нужно сделать это только один раз.


Я хотел бы увидеть пример этого!
DivideByHero

1
Дэн, плохая вещь в том, что невозможно принять, например, только Pair <String, Integer> s из-за стирания типа, нет? Но так же Java ...
Йоханнес Вайс


Я не думаю, что это будет работать для простых массивов, но определенно будет работать для других коллекций.
Outlaw Programmer

@Outlaw Programmer: Хорошо, вы можете использовать его с массивами, но это уродливо, так как вам приходится использовать хак для создания универсальных массивов.
Дэн Дайер

7

Расширяя другие ответы, общая неизменная пара должна иметь статический метод, чтобы избежать загромождения вашего кода при вызове конструктора:

class Pair<L,R> {
      final L left;
      final R right;

      public Pair(L left, R right) {
        this.left = left;
        this.right = right;
      }

      static <L,R> Pair<L,R> of(L left, R right){
          return new Pair<L,R>(left, right);
      }
}

если вы называете статический метод "of" или "pairOf", код становится свободным, поскольку вы можете написать либо:

    list.add(Pair.of(x,y)); // my preference
    list.add(pairOf(x,y)); // use with import static x.y.Pair.pairOf

очень жаль, что базовые библиотеки java настолько редки, что вам приходится использовать commons-lang или другие третьи стороны для выполнения таких базовых задач. Еще одна причина, чтобы перейти на Скала ...


6

Я собирался спросить, не хотите ли вы просто использовать List<Pair<T, U>>? но тогда, конечно, JDK не имеет класса Pair <>. Но быстрый Google нашел его как в Википедии , так и на forums.sun.com . ура


6

Как вы уже описали, предпочтительное решение - это список пар (т. Е. Список).

Для этого вы должны создать класс Pair для использования в вашей коллекции. Это полезный служебный класс для добавления в базу кода.

Ближайшим классом в Sun JDK, обеспечивающим функциональность, аналогичную типичному классу Pair, является AbstractMap.SimpleEntry. Вы можете использовать этот класс, а не создавать свой собственный класс Pair, хотя вам придется жить с некоторыми неловкими ограничениями, и я думаю, что большинство людей нахмурились бы, что это не совсем предназначенная роль SimpleEntry. Например, SimpleEntry не имеет метода setKey () и конструктора по умолчанию, поэтому вы можете счесть его слишком ограничивающим.

Имейте в виду, что коллекции предназначены для элементов одного типа. Связанные служебные интерфейсы, такие как Map, на самом деле не являются коллекциями (т. Е. Map не реализует интерфейс Collection). Пара также не реализует интерфейс Collection, но, очевидно, является полезным классом для построения больших структур данных.


Я считаю, что это решение лучше, чем «выигрышное» с общей парой <K, V>. Он делает все запрошенное и выходит из коробки. Я использую этот для моей реализации.
Sauer

5

Это основано на коде JavaHelp4u.

Менее многословно и показывает, как сделать в одну строку и как зацикливаться на вещах.

//======>  Imports
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;

//======>  Single Entry
SimpleEntry<String, String> myEntry = new SimpleEntry<String, String>("ID", "Text");
System.out.println("key: " + myEntry.getKey() + "    value:" + myEntry.getValue());
System.out.println();

//======>  List of Entries
List<Entry<String,String>> pairList = new ArrayList<>();

//-- Specify manually
Entry<String,String> firstButton = new SimpleEntry<String, String>("Red ", "Way out");
pairList.add(firstButton);

//-- one liner:
pairList.add(new SimpleEntry<String,String>("Gray", "Alternate route"));  //Ananomous add.

//-- Iterate over Entry array:
for (Entry<String, String> entr : pairList) {
    System.out.println("Button: " + entr.getKey() + "    Label: " + entr.getValue());
}


4

просто создайте класс как

class tuples 
{ 
int x;
int y;
} 

затем создайте список этих объектов кортежей

List<tuples> list = new ArrayList<tuples>();

так что вы также можете реализовать другие новые структуры данных таким же образом.


4

Я имею в виду, что хотя Pairв Java нет классов, есть что-то довольно похожее:Map.Entry

Map.Entry Документация

Это (немного упрощает) что HashMap, или на самом деле любойMap магазины.

Вы можете создать экземпляр для Mapхранения ваших значений в нем и получить набор записей. Вы закончите сSet<Map.Entry<K,V>> то, что вам нужно.

Так:

public static void main(String []args)
{    
    HashMap<String, Integer> values = new HashMap<String,Integer>();
    values.put("A", 235);//your custom data, the types may be different
    //more data insertions....
    Set<Map.Entry<String,Integer>> list = values.entrySet();//your list 
    //do as you may with it
}

Эта опция имеет проблему уникальных ключей. Допустим, у вас есть атрибуты лиц (например, {(person1, "голубоглазый"), (person1, "рыжеволосый", (person2, "близорукий"), (person2, "быстрый мыслитель")}) вы бы не сможет хранить их как пары на карте, так как каждый человек является ключом к карте и допускает только один атрибут на человека.
manuelvigarcia


0

А как насчет com.sun.tools.javac.util.Pair?


7
Этот тип находится в tools.jar - части реализации Sun для компилятора javac. Он распространяется только как часть JDK, может отсутствовать в реализациях других поставщиков и вряд ли будет частью общедоступного API.
Макдауэлл

0

Первое, о чем я думаю, когда говорю о парах ключ / значение, это класс свойств, в котором вы можете сохранять и загружать элементы в поток / файл.


0

В проекте Reactor (io.projectreactor: реактор-актив) имеется расширенная поддержка n-Tuples:

Tuple2<String, Integer> t = Tuples.of("string", 1)

Там вы можете получить, t.getT1(), t.getT2(), ...особенно с Stream или Flux, вы даже можете отобразить элементы кортежа:

Stream<Tuple2<String, Integer>> s;
s.map(t -> t.mapT2(i -> i + 2));
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.