web-dev-qa-db-fra.com

Est-ce que Java SE 8 a des paires ou des tuples?

Je joue avec des opérations fonctionnelles paresseuses dans Java SE 8, et je souhaite map un index i vers une paire/Tuple (i, value[i]), puis filter basé sur le deuxième élément value[i] et enfin ne produire que les indices.

Dois-je encore souffrir de ceci: Quel est l'équivalent de la paire C++ <L, R> en Java? dans la nouvelle ère audacieuse des lambdas et des jets?

Mise à jour: J'ai présenté un exemple plutôt simplifié, qui propose une solution intéressante proposée par @dkatzel dans l'une des réponses ci-dessous. Cependant, il ne généralise pas . Par conséquent, permettez-moi d'ajouter un exemple plus général:

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

}

Cela donne une sortie incorrecte de [0, 0, 0] qui correspond aux comptes pour les trois colonnes sont tous false. Ce dont j'ai besoin, ce sont les indices de ces trois colonnes. La sortie correcte doit être [0, 2, 4]. Comment puis-je obtenir ce résultat?

168
necromancer

UPDATE: Cette réponse répond à la question initiale, Est-ce que Java SE 8 a des paires ou Tuples? (Et implicitement, sinon, pourquoi pas?) Le PO a mis à jour la question avec un exemple plus complet, mais il semble que cela puisse être résolu sans utiliser aucune sorte de structure Pair. [Note de OP: voici l'autre réponse correcte .]


La réponse courte est non. Vous devez soit rouler le vôtre, soit importer l'une des nombreuses bibliothèques qui l'implémentent.

Avoir une classe Pair dans Java SE a été proposé et rejeté au moins une fois. Voir ce fil de discussion sur l’une des listes de diffusion OpenJDK. Les compromis ne sont pas évidents. D'une part, il existe de nombreuses implémentations de Pair dans d'autres bibliothèques et dans du code d'application. Cela démontre un besoin et ajouter une telle classe à Java SE augmentera la réutilisation et le partage. D'autre part, le fait d'avoir une classe Pair accroît la tentation de créer des structures de données complexes à partir de paires et de collections sans créer les types et les abstractions nécessaires. (C'est une paraphrase de le message de Kevin Bourillion de ce fil.)

Je recommande à tous de lire l'intégralité de ce fil de discussion. C'est remarquablement perspicace et sans flamage. C'est assez convaincant. Quand cela a commencé, je me suis dit: "Oui, il devrait y avoir une classe Pair dans Java SE", mais au moment où le fil de discussion atteignait sa fin, j'avais changé d'avis.

Notez cependant que JavaFX a la classe javafx.util.Pair . Les API JavaFX ont évolué séparément des API Java SE.

Comme on peut le voir à partir de la question liée Quel est l'équivalent de la paire C++ en Java? , il y a un assez grand espace de conception entourant ce qui est apparemment une API si simple. Les objets doivent-ils être immuables? Devraient-ils être sérialisables? Devraient-ils être comparables? Le cours devrait-il être final ou non? Faut-il commander les deux éléments? Devrait-il être une interface ou une classe? Pourquoi s'arrêter à deux? Pourquoi ne pas tripler, quadrupler ou N-tuples?

Et bien sûr, il y a l'inévitable dénomination bikeshed pour les éléments:

  • (un B)
  • (première seconde)
  • (gauche droite)
  • (voiture, cdr)
  • (foo, bar)
  • etc.

La relation entre les paires et les primitifs est à peine évoquée. Si vous avez une donnée (int x, int y) qui représente un point dans un espace 2D, le représentant sous la forme Pair<Integer, Integer> consomme trois objets au lieu de deux Mots 32 bits. De plus, ces objets doivent résider sur le tas et entraîneront une surcharge de GC.

Il semblerait évident que, à l'instar des Streams, il serait essentiel qu'il existe des spécialisations primitives pour les paires. Voulons-nous voir:

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

Même un IntIntPair aurait toujours besoin d'un objet sur le tas.

Celles-ci rappellent, bien sûr, la prolifération des interfaces fonctionnelles dans le package Java.util.function de Java SE 8. Si vous ne voulez pas d'une API gonflée, lesquelles laisseriez-vous de côté? Vous pouvez également dire que cela ne suffit pas et que les spécialisations pour, par exemple, Boolean devraient également être ajoutées.

Mon sentiment est que si Java avait ajouté depuis longtemps une classe Pair, elle aurait été simple, voire simpliste, et n'aurait pas permis de résoudre de nombreux cas d'utilisation envisagés à présent. Considérez que si Pair avait été ajouté dans le laps de temps JDK 1.0, il aurait probablement été mutable! (Regardez Java.util.Date.) Les gens en auraient-ils été contents? J'imagine que s'il existait une classe Pair en Java, ce serait un peu sorte d'utile et tout le monde utilisera toujours les leurs pour répondre à leurs besoins. Il y aura différentes implémentations Pair et Tuple dans des bibliothèques externes, et les gens discuteraient encore/discuteraient sur la façon de réparer la classe Pair de Java. En d'autres termes, en quelque sorte au même endroit où nous sommes aujourd'hui.

Pendant ce temps, des travaux sont en cours pour résoudre le problème fondamental, à savoir un meilleur support dans la JVM (et éventuellement dans le langage Java) pour les types de valeur . Voir ceci Etat des valeurs document. Il s’agit d’un travail préliminaire, spéculatif, qui aborde uniquement les problèmes du point de vue de la JVM, mais qui a déjà suscité de nombreuses réflexions. Bien sûr, rien ne garantit que cela va entrer dans Java 9, ni jamais entrer nulle part, mais cela indique la direction actuelle de la réflexion sur ce sujet.

187
Stuart Marks

Vous pouvez consulter ces classes intégrées:

37
senerh

Malheureusement, Java 8 n'a pas introduit de paires ou de n-uplets. Vous pouvez toujours utiliser org.Apache.commons.lang3.Tuple bien sûr (que j'utilise personnellement en combinaison avec Java 8) ou vous pouvez créer vos propres wrappers. Ou utilisez des cartes. Ou des choses comme ça, comme expliqué dans le réponse acceptée à la question à laquelle vous avez lié.

20
blalasaadri

Il semble que l'exemple complet puisse être résolu sans utiliser de structure de type Pair. La clé consiste à filtrer les index de colonne, le prédicat vérifiant toute la colonne au lieu de mapper les index de colonne sur le nombre d'entrées false de cette colonne.

Le code qui fait cela est ici:

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

Cela aboutit à la sortie de [0, 2, 4] qui correspond, à mon avis, au résultat correct demandé par l'OP.

Notez également l'opération boxed() qui encadre les valeurs int dans des objets Integer. Cela permet d'utiliser le collecteur préexistant toList() au lieu de devoir écrire les fonctions de collecteur qui effectuent la mise en boîte elles-mêmes.

14
Stuart Marks

Comme vous ne vous souciez que des index, vous n'avez pas du tout besoin de mapper des n-uplets. Pourquoi ne pas simplement écrire un filtre qui utilise les éléments de recherche de votre tableau?

     int[] value =  ...


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

Oui.

Map.Entry peut être utilisé comme un Pair.

Malheureusement, cela n'aide pas avec les flux Java 8 car le problème est que, même si lambdas peut accepter plusieurs arguments, le langage Java ne permet de renvoyer qu'une seule valeur (type d'objet ou primitif). . Cela implique que chaque fois que vous avez un flux, vous vous retrouvez avec un objet unique issu de l'opération précédente. Ceci est un manque dans le langage Java, parce que si plusieurs valeurs de retour étaient prises en charge ET que les flux les supportaient, nous pourrions avoir beaucoup plus de tâches non-triviales effectuées par les flux.

Jusque-là, il n'y a que peu d'utilisation.

EDIT 2018-02-12: Pendant que je travaillais sur un projet, j'ai écrit une classe d'assistance qui permet de gérer le cas particulier d'avoir un identificateur plus tôt dans le flux dont vous avez besoin ultérieurement, mais la partie de flux située entre les deux ne le sait pas. Jusqu'à ce que je sois prêt à le publier lui-même, il est disponible sur IdValue.Java avec un test unitaire sur IdValueTest.Java

Vavr (anciennement appelé Javaslang) ( http://www.vavr.io ) fournit des n-uplets (taille maximale de 8) ainsi que. Voici le javadoc: https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/Tuple.html .

Ceci est un exemple simple:

Tuple2<Integer, String> entry = Tuple.of(1, "A");

Integer key = entry._1;
String value = entry._2;

Pourquoi JDK n’est pas venu avec un simple type de tuples jusqu’à présent, c’est un mystère pour moi. L’écriture de cours d’emballage semble être une affaire quotidienne.

4
wumpz

Depuis Java 9, vous pouvez créer des instances de _Map.Entry_ plus facilement qu'auparavant:

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

Map.entry renvoie un Entry non modifiable et interdit les valeurs nulles.

3
ZhekaKozlov

Eclipse Collections contient Pair et toutes les combinaisons de paires primitive/objet (pour les huit primitives).

La fabrique Tuples peut créer des instances de Pair et la fabrique PrimitiveTuples peut être utilisée pour créer toutes les combinaisons de paires primitive/objet.

Nous avons ajouté ces éléments avant la sortie de Java 8. Ils ont été utiles pour implémenter des itérateurs clé/valeur pour nos cartes primitives, que nous prenons également en charge dans toutes les combinaisons primitives/objets.

Si vous souhaitez ajouter une surcharge supplémentaire à la bibliothèque, vous pouvez utiliser la solution acceptée par Stuart et collecter les résultats dans une primitive IntList pour éviter la boxe. Nous avons ajouté de nouvelles méthodes dans Eclipse Collections 9. afin de permettre la création de Int/Long/Double collections à partir de Int/Long/Double Streams.

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

Remarque: je suis un partisan des collections Eclipse.

2
Donald Raab

Je ne suis pas un expert en Java, je suis juste un étudiant, mais j'ai créé ma solution de contournement pour simuler les n-uplets comme en python:

class C1 { 
    String name; int X,Y;
    C1(List l){name = ""+l.get(0); X=(int)l.get(1); Y=(int)l.get(2);}
}

class MyClass{
    List l2= List.of(List.of("a",7,9), List.of("b",8,4), List.of("c",3,6)); //here
    List l3=new ArrayList();
    for (Object li: l2) l3.add(new C1((List)li));
}

J'espère que cela t'aides.

0
Carlos Leandro