Uma coleção Java de pares de valores? (tuplas?)

345

Eu gosto de como o Java tem um mapa onde você pode definir os tipos de cada entrada no mapa, por exemplo <String, Integer>.

O que estou procurando é um tipo de coleção em que cada elemento da coleção é um par de valores. Cada valor no par pode ter seu próprio tipo (como o exemplo String e Inteiro acima), que é definido no momento da declaração.

A coleção manterá sua ordem determinada e não tratará um dos valores como uma chave exclusiva (como em um mapa).

Essencialmente, eu quero poder definir uma ARRAY do tipo <String,Integer>ou quaisquer outros 2 tipos.

Percebo que posso fazer uma aula com nada além das 2 variáveis, mas isso parece excessivamente detalhado.

Também percebo que eu poderia usar uma matriz 2D, mas, devido aos diferentes tipos que preciso usar, precisaria transformá-los em matrizes de OBJECT e, em seguida, precisaria converter o tempo todo.

Eu só preciso armazenar pares na coleção, então só preciso de dois valores por entrada. Existe algo assim sem seguir a rota da classe? Obrigado!

DivideByHero
fonte
Gostaria de saber que o Goiaba também pode ter uma aula para isso.
Sikorski
A goiaba é bastante anti- Pair, e o pessoal do Google chegou ao ponto de criar uma alternativa muito melhor - Auto / Valor . Permite criar facilmente classes de tipo de valor bem tipadas , com semântica igual a / hashCode. Você nunca precisará de um Pairtipo novamente!
precisa saber é o seguinte

Respostas:

255

A classe Pair é um daqueles exemplos genéricos "gimme" que são fáceis de escrever por conta própria. Por exemplo, em cima da minha cabeça:

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

}

E sim, isso existe em vários locais da Internet, com diferentes graus de completude e recursos. (Meu exemplo acima pretende ser imutável.)

Paul Brinkley
fonte
17
Eu gosto disso, mas o que você acha de tornar públicos os campos esquerdo e direito? É bastante claro que a classe Pair nunca terá nenhuma lógica associada e todos os clientes precisarão acessar 'left' e 'right', então por que não facilitar?
Outlaw Programmer
43
Hum ... não, não seria. Os campos são marcados como finais, portanto, não podem ser reatribuídos. E não é seguro, porque 'left' e 'right' podem ser mutáveis. A menos que getLeft () / getRight () retorne cópias defensivas (inúteis neste caso), não vejo qual é o problema.
Outlaw Programmer
17
Observe que hashCode () como está fornece o mesmo valor se esquerda e direita forem trocadas. Talvez:long l = left.hashCode() * 2654435761L; return (int)l + (int)(l >>> 32) + right.hashCode();
karmakaze
68
Portanto, se eu entendi corretamente, a implementação de uma classe de pares simples gera um erro de sintaxe, um método subpar hashCode, exceções de ponteiro nulo, método compareTo, perguntas de design ... e as pessoas ainda defendem a implementação dessa classe enquanto ela existe no Apache commons. Por favor, copie o código se você não quiser incluir o JAR, mas pare de reinventar a roda!
Cquezel # 18/13
38
O que você quer dizer com "fácil o suficiente para escrever por conta própria"? Isso é terrível engenharia de software. As classes de adaptador N ^ 2 para converter entre MyPair e SomeoneElsesPair também são consideradas fáceis de escrever por conta própria?
precisa saber é o seguinte
299

AbstractMap.SimpleEntry

Fácil você está procurando isso:

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

Como você pode preenchê-lo?

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

Isso simplifica para:

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

E, com a ajuda de um createEntrymétodo, pode reduzir ainda mais a verbosidade para:

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

Como ArrayListnão é final, pode ser subclassificado para expor um ofmétodo (e o createEntrymétodo mencionado acima ), resultando na síntese sintática:

TupleList<java.util.Map.Entry<String,Integer>> pair = new TupleList<>();
pair.of("Not Unique key1", 1);
pair.of("Not Unique key2", 2);
JavaHelp4u
fonte
11
FYI: Essa sugestão SimpleEntrytem uma classe de irmãos que omite o setValuemétodo como imutável. Assim o nome SimpleImmutableEntry.
Basil Bourque 04/04
3
Isso é melhor do que a auto-implementação. O fato de ser fácil de implementar não é uma desculpa para implementá-lo sempre.
Pevogam
6
Eu não consideraria isso "padrão". Entrydeve ser um par de valor-chave, e é bom para isso. Mas tem pontos fracos como uma tupla verdadeira. Por exemplo, a hashcodeimplementação SimpleEntryapenas comporta os códigos dos elementos, portanto faz o <a,b>hash com o mesmo valor que <b,a>. As implementações de tuplas geralmente são classificadas lexicograficamente, mas SimpleEntry não são implementadas Comparable. Portanto, tenha cuidado lá fora ...
Gene
167

Java 9 ou superior

No Java 9, você pode simplesmente escrever: Map.entry(key, value) para criar um par imutável.

Nota: este método não permite que chaves ou valores sejam nulos. Se você quiser permitir valores nulos, por exemplo, que você gostaria de mudar isso para: Map.entry(key, Optional.ofNullable(value)).


Java 8+

No Java 8, você pode usar o propósito mais geral javafx.util.Pairpara criar um par serializável e imutável. Esta classe não permitir chaves nulos e valores nulos. (No Java 9, essa classe está incluída no javafx.basemódulo). EDIT: A partir do Java 11, o JavaFX foi dissociado do JDK, portanto, você precisa do artefato maven adicional org.openjfx: javafx-base.


Java 6+

No Java 6 e superior, você pode usar o mais detalhado AbstractMap.SimpleImmutableEntrypara um par imutável ou AbstractMap.SimpleEntrypara um par cujo valor pode ser alterado. Essas classes também permitem chaves e valores nulos e são serializáveis.


Android

Se você está escrevendo para Android, use Pair.create(key, value)para criar um par imutável.


Apache Commons

Apache Commons Langfornece o útil Pair.of(key, value)para criar um par imutável, comparável e serializável.


Coleções Eclipse

Se você estiver usando pares que contêm primitivas, o Eclipse Collections fornecerá algumas classes de pares primitivos muito eficientes que evitarão todo o ineficiente auto-boxe e auto-unboxing.

Por exemplo, você pode usar PrimitiveTuples.pair(int, int)para criar um IntIntPairou PrimitiveTuples.pair(float, long)para criar um FloatLongPair.


Projeto Lombok

Usando o Projeto Lombok , você pode criar uma classe de pares imutável simplesmente escrevendo:

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

Lombok vai encher No construtor, getters, equals(), hashCode(), e toString()métodos para você automaticamente no bytecode gerado. Se você quer um método de fábrica estático em vez de um construtor, por exemplo, um Pair.of(k, v), basta alterar a anotação para: @Value(staticConstructor = "of").


De outra forma

Se nenhuma das soluções acima flutuar no seu barco, você pode simplesmente copiar e colar o código a seguir (que, diferentemente da classe listada na resposta aceita, protege contra 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;
    }
}
Hans Brende
fonte
3
No entanto, o JavaFX é somente para desktop, adicionando uma dependência desnecessária para ambientes que não são de desktop (por exemplo, servidores).
foo
2
O Eclipse Collections também possui uma Pairinterface para objetos, que pode ser instanciada chamando Tuples.pair(object1, object2). eclipse.org/collections/javadoc/9.2.0/org/eclipse/collections/…
Donald Raab
2
Parece a resposta mais verdadeiramente Java-esque. TODAS as implementações disponíveis. Muito completo, muito obrigado!
Mike
Oi @ Hans Estou usando uma lista List<Pair<Integer, String> listOfTuple. Como eu uso listOfTuple.add()? Se eu fizer - listOfTuple.add(Pair<someInteger, someString>);, isso não vai funcionar. Desde já, obrigado.
Me_developer
a versão android não está disponível no teste de unidade, o que a torna realmente útil ...
OznOg
64

Map.Entry

Essas classes internas também são uma opção. Ambos implementam a Map.Entryinterface.

Diagrama UML da interface Map.Entry com par de classes de implementação

Johannes Weiss
fonte
10
Eles não são apenas uma opção, são a resposta certa. Eu acho que algumas pessoas simplesmente preferem reinventar a roda.
CurtainDog
31

O Apache common lang3 possui a classe Pair e poucas outras bibliotecas mencionadas neste segmento Qual é o equivalente do par C ++ <L, R> em Java?

Exemplo que corresponde ao requisito da sua pergunta original:

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());
}
mudou
fonte
11
Essa é a opção mais simples se o Apache Commons estiver disponível.
Kip
18

Para quem estiver desenvolvendo para Android, você pode usar android.util.Pair . :)

XåpplI'-I0llwlg'I -
fonte
15

E a Pairclasse "Apache Commons Lang 3" e as subclasses relativas?

    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é uma subclasse específica que não permite que os valores do par sejam modificados, mas existem outras implementações com semântica diferente. Essas são as coordenadas do Maven, se você precisar delas.

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

Você pode escrever uma classe Pair <A, B> genérica e usá-la em uma matriz ou lista. Sim, você precisa escrever uma turma, mas pode reutilizar a mesma turma para todos os tipos, portanto, apenas uma vez.

Dan Dyer
fonte
Eu adoraria ver um exemplo disso!
DivideByHero 06/02/09
11
Dan, o ruim disso é que não é possível aceitar, por exemplo, apenas Pair <String, Inteiro> s por causa do tipo de apagamento, não? Mas assim é Java ...
Johannes Weiss
Eu não acho que isso funcione para matrizes simples, mas definitivamente funcionará para outras coleções.
Outlaw Programmer
@ Outlaw Programmer: Bom ponto, você pode usá-lo com matrizes, mas é feio, pois você precisa usar um hack para criar matrizes genéricas.
6609 Dan Dyer
7

Expandindo as outras respostas, um par imutável genérico deve ter um método estático para evitar sobrecarregar seu código com a chamada ao construtor:

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

se você nomear o método estático "de" ou "pairOf", o código se tornará fluente, pois você pode escrever:

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

é uma pena que as principais bibliotecas java sejam tão escassas em coisas que você precisa usar o commons-lang ou outros terceiros para fazer essas coisas básicas. mais um motivo para atualizar para o scala ...

simbo1905
fonte
6

Eu ia perguntar se você não gostaria de usar apenas um List<Pair<T, U>>? mas é claro que o JDK não tem uma classe Pair <>. Mas um rápido Google encontrou um na Wikipedia e no forums.sun.com . Felicidades

JMD
fonte
6

A solução preferida, como você descreveu, é uma lista de pares (ou seja, lista).

Para fazer isso, você criaria uma classe Pair para uso em sua coleção. Esta é uma classe de utilitário útil para adicionar à sua base de código.

A classe mais próxima no Sun JDK que fornece funcionalidade semelhante a uma classe Pair típica é AbstractMap.SimpleEntry. Você poderia usar essa classe em vez de criar sua própria classe Pair, embora tenha que conviver com algumas restrições embaraçosas e acho que a maioria das pessoas se desaprova como não é o papel pretendido do SimpleEntry. Por exemplo, o SimpleEntry não possui o método "setKey ()" e nenhum construtor padrão; portanto, você pode achar isso muito limitador.

Lembre-se de que as coleções são projetadas para conter elementos de um único tipo. As interfaces de utilidade relacionadas, como o Mapa, não são realmente Coleções (ou seja, o Mapa não implementa a interface Coleção). Um par também não implementaria a interface Collection, mas é obviamente uma classe útil na construção de estruturas de dados maiores.

Jeremy Rishel
fonte
Acho essa solução muito melhor do que a solução "vencedora" com o par genérico <K, V>. Faz tudo o que é solicitado e sai da caixa. Eu uso este para minha implementação.
Sauer
5

Isso é baseado no código do JavaHelp4u.

Menos detalhado e mostra como fazer em uma linha e como fazer um loop sobre as coisas.

//======>  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());
}
Leo Ufimtsev
fonte
4

basta criar uma classe como

class tuples 
{ 
int x;
int y;
} 

então crie a Lista desses objetos de tuplas

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

para que você também possa implementar outras novas estruturas de dados da mesma maneira.

user93
fonte
4

Quero dizer, mesmo que não haja Pairclasse em Java, há algo bastante semelhante:Map.Entry

Documentação de Map.Entry

Isto é (simplificando bastante) o que HashMap, ou realmente qualquer Maploja.

Você pode criar uma instância de Maparmazenar seus valores nela e obter o conjunto de entradas. Você vai acabar com um Set<Map.Entry<K,V>>que efetivamente é o que você deseja.

Assim:

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
}
SomeDude
fonte
Esta opção tem o problema das chaves exclusivas. Digamos que você tenha atributos de pessoas (por exemplo, {(pessoa1, "olhos azuis"), (pessoa1, "ruivo", (pessoa2, "míope"), (pessoa2, "pensador rápido")}) você não ser capaz de armazená-los como pares em um mapa, uma vez que cada pessoa é a chave para o mapa e só permitiria um atributo por pessoa.
manuelvigarcia
2

Spring tem um Pair<S,T>tipo no pacote Data Utilsorg.springframework.data.util

Pair<String,Integer> pair = Pair.of("Test", 123);
System.out.println(pair.getFirst());
System.out.println(pair.getSecond());
muttonUp
fonte
0

E o com.sun.tools.javac.util.Pair?

Rene H.
fonte
7
Esse tipo está em tools.jar - parte da implementação "Sun" do compilador javac. Ele é distribuído apenas como parte do JDK, pode não estar presente nas implementações de outros fornecedores e é improvável que faça parte da API pública.
McDowell
0

A primeira coisa que penso em pares de chave / valor é a Classe de Propriedades, na qual é possível salvar e carregar itens em um fluxo / arquivo.

muka90
fonte
0

No projeto Reator (io.projectreactor: reator-core), há suporte avançado para n-Tuplas:

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

Lá você pode obter t.getT1(), t.getT2(), ...Especialmente com Stream ou Flux, você pode até mapear os elementos da tupla:

Stream<Tuple2<String, Integer>> s;
s.map(t -> t.mapT2(i -> i + 2));
Pneus
fonte