web-dev-qa-db-fra.com

Comment renvoyer plusieurs objets d'une méthode Java?

Je souhaite renvoyer deux objets à partir d'une méthode Java et je me demandais quel serait le meilleur moyen de le faire.

Les moyens possibles auxquels je peux penser sont les suivants: renvoyer une HashMap (puisque les deux objets sont liés) ou renvoyer une ArrayList sur Object objets.

Pour être plus précis, les deux objets que je veux renvoyer sont (a) List d’objets et (b) des noms identiques, séparés par des virgules.

Je souhaite renvoyer ces deux objets à partir d'une méthode car je ne souhaite pas parcourir la liste d'objets pour obtenir les noms séparés par des virgules (ce que je peux faire dans la même boucle avec cette méthode).

D'une manière ou d'une autre, renvoyer un HashMap n'a pas l'air d'être un moyen très élégant de le faire.

160
Jagmal

Si vous souhaitez renvoyer deux objets, vous souhaitez généralement renvoyer un seul objet qui encapsule les deux objets à la place.

Vous pouvez retourner une liste d'objets NamedObject comme ceci:

public class NamedObject<T> {
  public final String name;
  public final T object;

  public NamedObject(String name, T object) {
    this.name = name;
    this.object = object;
  }
}

Ensuite, vous pouvez facilement renvoyer un List<NamedObject<WhateverTypeYouWant>>.

Aussi: pourquoi voudriez-vous renvoyer une liste de noms séparés par des virgules au lieu d'un List<String>? Ou mieux encore, renvoyez un Map<String,TheObjectType> avec les clés comme noms et les valeurs les objets (à moins que vos objets aient un ordre spécifié, auquel cas un NavigableMap pourrait être ce que vous voulez.

121
Joachim Sauer

Si vous savez que vous allez renvoyer deux objets, vous pouvez également utiliser une paire générique:

public class Pair<A,B> {
    public final A a;
    public final B b;

    public Pair(A a, B b) {
        this.a = a;
        this.b = b;
    }
};

Edit Une implémentation plus complète de ce qui précède:

package util;

public class Pair<A,B> {

    public static <P, Q> Pair<P, Q> makePair(P p, Q q) {
        return new Pair<P, Q>(p, q);
    }

    public final A a;
    public final B b;

    public Pair(A a, B b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((a == null) ? 0 : a.hashCode());
        result = prime * result + ((b == null) ? 0 : b.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        @SuppressWarnings("rawtypes")
        Pair other = (Pair) obj;
        if (a == null) {
            if (other.a != null) {
                return false;
            }
        } else if (!a.equals(other.a)) {
            return false;
        }
        if (b == null) {
            if (other.b != null) {
                return false;
            }
        } else if (!b.equals(other.b)) {
            return false;
        }
        return true;
    }

    public boolean isInstance(Class<?> classA, Class<?> classB) {
        return classA.isInstance(a) && classB.isInstance(b);
    }

    @SuppressWarnings("unchecked")
    public static <P, Q> Pair<P, Q> cast(Pair<?, ?> pair, Class<P> pClass, Class<Q> qClass) {

        if (pair.isInstance(pClass, qClass)) {
            return (Pair<P, Q>) pair;
        }

        throw new ClassCastException();

    }

}

Notes, principalement autour de la rouille avec Java & génériques:

  • a et b sont immuables.
  • makePair La méthode statique vous aide à dactylographier la plaque de la chaudière, ce que l'opérateur diamant de Java 7 rendra moins ennuyeux. Il y a encore du travail à faire pour que ce soit vraiment génial, mais ça devrait aller maintenant. (cf. PECS)
  • hashcode et equals sont générés par Eclipse.
  • le temps de compilation dans la méthode cast est correct, mais ne semble pas très correct.
  • Je ne suis pas sûr que les caractères génériques dans isInstance soient nécessaires.
  • Je viens d'écrire ceci en réponse à des commentaires, à des fins d'illustration uniquement.
63
David Hanak

Si la méthode que vous appelez est privée ou appelée depuis un seul emplacement, essayez

return new Object[]{value1, value2};

L'appelant ressemble à:

Object[] temp=myMethod(parameters);
Type1 value1=(Type1)temp[0];  //For code clarity: temp[0] is not descriptive
Type2 value2=(Type2)temp[1];

L'exemple de Pair de David Hanak ne présente aucun avantage syntaxique et se limite à deux valeurs.

return new Pair<Type1,Type2>(value1, value2);

Et l'appelant ressemble à:

Pair<Type1, Type2> temp=myMethod(parameters);
Type1 value1=temp.a;  //For code clarity: temp.a is not descriptive
Type2 value2=temp.b;
24
Kyle Lahnakoski

Vous pouvez utiliser l’une des méthodes suivantes:

private static final int RETURN_COUNT = 2;
private static final int VALUE_A = 0;
private static final int VALUE_B = 1;
private static final String A = "a";
private static final String B = "b";

1) Utilisation de tablea

private static String[] methodWithArrayResult() {
    //...
    return new String[]{"valueA", "valueB"};
}

private static void usingArrayResultTest() {
    String[] result = methodWithArrayResult();
    System.out.println();
    System.out.println("A = " + result[VALUE_A]);
    System.out.println("B = " + result[VALUE_B]);
}

2) Utilisation de ArrayList

private static List<String> methodWithListResult() {
    //...
    return Arrays.asList("valueA", "valueB");
}

private static void usingListResultTest() {
    List<String> result = methodWithListResult();
    System.out.println();
    System.out.println("A = " + result.get(VALUE_A));
    System.out.println("B = " + result.get(VALUE_B));
}

3) Utilisation de HashMap

private static Map<String, String> methodWithMapResult() {
    Map<String, String> result = new HashMap<>(RETURN_COUNT);
    result.put(A, "valueA");
    result.put(B, "valueB");
    //...
    return result;
}

private static void usingMapResultTest() {
    Map<String, String> result = methodWithMapResult();
    System.out.println();
    System.out.println("A = " + result.get(A));
    System.out.println("B = " + result.get(B));
}

4) Utilisation de votre classe de conteneur personnalisée

private static class MyContainer<M,N> {
    private final M first;
    private final N second;

    public MyContainer(M first, N second) {
        this.first = first;
        this.second = second;
    }

    public M getFirst() {
        return first;
    }

    public N getSecond() {
        return second;
    }

    // + hashcode, equals, toString if need
}

private static MyContainer<String, String> methodWithContainerResult() {
    //...
    return new MyContainer("valueA", "valueB");
}

private static void usingContainerResultTest() {
    MyContainer<String, String> result = methodWithContainerResult();
    System.out.println();
    System.out.println("A = " + result.getFirst());
    System.out.println("B = " + result.getSecond());
}

5) Utilisation de AbstractMap.simpleEntry

private static AbstractMap.SimpleEntry<String, String> methodWithAbstractMapSimpleEntryResult() {
    //...
    return new AbstractMap.SimpleEntry<>("valueA", "valueB");
}

private static void usingAbstractMapSimpleResultTest() {
    AbstractMap.SimpleEntry<String, String> result = methodWithAbstractMapSimpleEntryResult();
    System.out.println();
    System.out.println("A = " + result.getKey());
    System.out.println("B = " + result.getValue());
}

6) Utilisation de Pair sur Apache Commons

private static Pair<String, String> methodWithPairResult() {
    //...
    return new ImmutablePair<>("valueA", "valueB");
}

private static void usingPairResultTest() {
    Pair<String, String> result = methodWithPairResult();
    System.out.println();
    System.out.println("A = " + result.getKey());
    System.out.println("B = " + result.getValue());
}
18
Viacheslav Vedenin

Je finis presque toujours par définir des classes n-Tuple lorsque je code en Java. Par exemple:

public class Tuple2<T1,T2> {
  private T1 f1;
  private T2 f2;
  public Tuple2(T1 f1, T2 f2) {
    this.f1 = f1; this.f2 = f2;
  }
  public T1 getF1() {return f1;}
  public T2 getF2() {return f2;}
}

Je sais que c'est un peu moche, mais ça marche et il vous suffit de définir vos types de tuple une fois. Les tuples sont quelque chose qui manque vraiment Java.

EDIT: L’exemple de David Hanak est plus élégant, car il évite de définir les getters tout en maintenant l’objet immuable.

15
Ulrik Rasmussen

Avant Java 5, je conviendrais en quelque sorte que la solution Map n'est pas idéale. Cela ne vous donnerait pas la vérification du type de compilation, ce qui pourrait causer des problèmes lors de l'exécution. Cependant, avec Java 5, nous avons des types génériques.

Donc, votre méthode pourrait ressembler à ceci:

public Map<String, MyType> doStuff();

Mon type est bien sûr le type d'objet que vous retournez.

Fondamentalement, je pense que le retour d'une carte est la bonne solution dans ce cas, car c'est exactement ce que vous voulez renvoyer, à savoir le mappage d'une chaîne sur un objet.

9
kipz

Nous devrions oublier les petites efficacités, disons environ 97% du temps: l'optimisation prématurée est la racine de tout mal.

D. Knuth

8
gizmo

Sinon, dans les cas où je souhaite renvoyer un certain nombre d'éléments d'une méthode, j'utilise parfois un mécanisme de rappel au lieu d'un conteneur. Cela fonctionne très bien dans les situations où je ne peux pas spécifier à l'avance combien d'objets seront générés.

Avec votre problème particulier, cela ressemblerait à quelque chose comme ceci:

public class ResultsConsumer implements ResultsGenerator.ResultsCallback
{
    public void handleResult( String name, Object value )
    {
        ... 
    }
}

public class ResultsGenerator
{
    public interface ResultsCallback
    {
        void handleResult( String aName, Object aValue );
    }

    public void generateResults( ResultsGenerator.ResultsCallback aCallback )
    {
        Object value = null;
        String name = null;

        ...

        aCallback.handleResult( name, value );
    }
}
6
Cadet Pirx

En ce qui concerne le problème des valeurs de retour multiples en général, j’utilise généralement une petite classe d’aide qui encapsule une valeur de retour unique et est transmise en tant que paramètre à la méthode:

public class ReturnParameter<T> {
    private T value;

    public ReturnParameter() { this.value = null; }
    public ReturnParameter(T initialValue) { this.value = initialValue; }

    public void set(T value) { this.value = value; }
    public T get() { return this.value; }
}

(pour les types de données primitifs, j'utilise des variantes mineures pour stocker directement la valeur)

Une méthode qui veut renvoyer plusieurs valeurs serait alors déclarée comme suit:

public void methodThatReturnsTwoValues(ReturnParameter<ClassA> nameForFirstValueToReturn, ReturnParameter<ClassB> nameForSecondValueToReturn) {
    //...
    nameForFirstValueToReturn.set("...");
    nameForSecondValueToReturn.set("...");
    //...
}

L’inconvénient majeur est peut-être que l’appelant doit préparer les objets de retour à l’avance au cas où il souhaite les utiliser (et la méthode devrait vérifier les pointeurs nuls)

ReturnParameter<ClassA> nameForFirstValue = new ReturnParameter<ClassA>();
ReturnParameter<ClassB> nameForSecondValue = new ReturnParameter<ClassB>();
methodThatReturnsTwoValues(nameForFirstValue, nameForSecondValue);

Avantages (par rapport aux autres solutions proposées):

  • Il n'est pas nécessaire de créer une déclaration de classe spéciale pour les méthodes individuelles et ses types de retour.
  • Les paramètres ont un nom et sont donc plus faciles à différencier quand on regarde la signature de la méthode
  • Tapez safety pour chaque paramètre
5
koma

Apache Commons a tuple et triple pour cela:

  • ImmutablePair<L,R> Un couple immuable composé de deux éléments Object.
  • ImmutableTriple<L,M,R> Un triple immuable composé de trois éléments Object.
  • MutablePair<L,R> Une paire modifiable constituée de deux éléments Object.
  • MutableTriple<L,M,R> Un triple modifiable constitué de trois éléments Object.
  • Pair<L,R> Un couple constitué de deux éléments.
  • Triple<L,M,R> Un triple composé de trois éléments.

Source: https://commons.Apache.org/proper/commons-lang/apidocs/org/Apache/commons/lang3/Tuple/package-summary.html

5
jacob

Utilisation de l'objet Entrée suivant Exemple:

public Entry<A,B> methodname(arg)
{
.......

return new AbstractMap.simpleEntry<A,B>(instanceOfA,instanceOfB);
}
4
Sreeja Thummala

Dans votre cas, le commentaire peut être un bon moyen d’aller. Sous Android, vous pouvez utiliser Pair . Simplement

return new Pair<>(yourList, yourCommaSeparatedValues);
4
serv-inc

Toutes les solutions possibles seront un kludge (comme des objets conteneurs, votre idée de HashMap, des "valeurs de retour multiples" réalisées via des tableaux). Je recommande de régénérer la liste séparée par des virgules à partir de la liste renvoyée. Le code finira par être beaucoup plus propre.

3
Bombe

Restez simple et créez une classe pour plusieurs situations de résultats. Cet exemple accepte une liste de tableaux et un texte de message provenant d'un hôte de base de données getInfo.

Où vous appelez la routine qui renvoie plusieurs valeurs que vous codez:

multResult res = mydb.getInfo(); 

Dans la routine getInfo vous code:

ArrayList<String> list= new ArrayList<String>();
add values to the list...
return new multResult("the message", list);

et définir une classe multResult avec:

public class multResult {
    public String message; // or create a getter if you don't like public
    public ArrayList<String> list;
    multResult(String m, ArrayList<String> l){
        message = m;
        list= l;
}

}

2
Martin

Peut faire quelque chose comme un tuple en langage dynamique (Python)

public class Tuple {
private Object[] multiReturns;

private Tuple(Object... multiReturns) {
    this.multiReturns = multiReturns;
}

public static Tuple _t(Object... multiReturns){
    return new Tuple(multiReturns);
}

public <T> T at(int index, Class<T> someClass) {
    return someClass.cast(multiReturns[index]);
}
}

et utiliser comme ça

public Tuple returnMultiValues(){
   return Tuple._t(new ArrayList(),new HashMap())
}


Tuple t = returnMultiValues();
ArrayList list = t.at(0,ArrayList.class);
2
Deva

J'ai suivi une approche similaire à celle décrite dans les autres réponses avec quelques ajustements en fonction de l'exigence que j'avais, à la base j'ai créé les classes suivantes (juste au cas où, tout est en Java):

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

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

    public <T> T get(Class<T> param) {
        return (T) (param == this.left.getClass() ? this.left : this.right);
    }

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

Ensuite, mon exigence était simple: dans la classe de référentiel qui atteint la base de données, pour les méthodes Get qui récupéraient les données de la base de données, je devais vérifier si cela échouait ou si je réussissais. Ensuite, si je réussissais, je devais jouer avec la liste de renvoi. , en cas d’échec, arrêtez l’exécution et notifiez l’erreur.

Ainsi, par exemple, mes méthodes sont les suivantes:

public Pair<ResultMessage, List<Customer>> getCustomers() {
    List<Customer> list = new ArrayList<Customer>();
    try {
    /*
    * Do some work to get the list of Customers from the DB
    * */
    } catch (SQLException e) {
        return Pair.of(
                       new ResultMessage(e.getErrorCode(), e.getMessage()), // Left 
                       null);  // Right
    }
    return Pair.of(
                   new ResultMessage(0, "SUCCESS"), // Left 
                   list); // Right
}

Où ResultMessage est juste une classe avec deux champs (code/message) et Client est une classe avec un tas de champs provenant de la base de données.

Ensuite, pour vérifier le résultat, je fais juste ceci:

void doSomething(){
    Pair<ResultMessage, List<Customer>> customerResult = _repository.getCustomers();
    if (customerResult.get(ResultMessage.class).getCode() == 0) {
        List<Customer> listOfCustomers = customerResult.get(List.class);
        System.out.println("do SOMETHING with the list ;) ");
    }else {
        System.out.println("Raised Error... do nothing!");
    }
}
2
Marco Vargas

À mon avis, il y a vraiment trois choix et la solution dépend du contexte. Vous pouvez choisir d'implémenter la construction du nom dans la méthode qui produit la liste. C'est le choix que vous avez choisi, mais je ne pense pas que ce soit le meilleur. Vous créez un couplage dans la méthode de production avec la méthode de consommation qui n'a pas besoin d'exister. D'autres appelants peuvent ne pas avoir besoin des informations supplémentaires et vous calculeriez des informations supplémentaires pour ces appelants.

Vous pouvez également demander à la méthode d’appel de calculer le nom. Si un seul appelant a besoin de cette information, vous pouvez vous arrêter ici. Vous n'avez pas de dépendances supplémentaires et, malgré quelques calculs supplémentaires, vous avez évité de rendre votre méthode de construction trop spécifique. C'est un bon compromis.

Enfin, vous pourriez avoir la liste elle-même responsable de la création du nom. C'est la voie que j'irais si le calcul devait être fait par plus d'un appelant. Je pense que cela confère la responsabilité de la création des noms à la classe la plus étroitement liée aux objets eux-mêmes.

Dans ce dernier cas, ma solution serait de créer une classe List spécialisée qui retourne une chaîne de noms séparés par des virgules contenant les noms des objets. Rendez la classe suffisamment intelligente pour construire la chaîne de nom à la volée au fur et à mesure que des objets sont ajoutés et supprimés. Renvoyez ensuite une instance de cette liste et appelez la méthode de génération de noms si nécessaire. Bien qu'il puisse être presque aussi efficace (et plus simple) de simplement retarder le calcul des noms jusqu'à la première utilisation de la méthode et de l'enregistrer ensuite (chargement différé). Si vous ajoutez/supprimez un objet, il vous suffit de supprimer la valeur calculée et de la recalculer lors du prochain appel.

2
tvanfosson

Cela ne répond pas exactement à la question, mais comme chaque solution proposée ici présente des inconvénients, je suggère d'essayer de refactoriser votre code afin que vous n'ayez à renvoyer qu'une seule valeur.

Cas 1

Vous avez besoin de quelque chose à l'intérieur comme à l'extérieur de votre méthode. Pourquoi ne pas le calculer à l’extérieur et le transmettre à la méthode?

Au lieu de:

[thingA, thingB] = createThings(...);  // just a conceptual syntax of method returning two values, not valid in Java

Essayer:

thingA = createThingA(...);
thingB = createThingB(thingA, ...);

Cela devrait couvrir la plupart de vos besoins, car dans la plupart des situations, une valeur est créée avant l’autre et vous pouvez les créer en les divisant de deux manières. L'inconvénient est que la méthode createThingsB a un paramètre supplémentaire comparé à createThings, et vous passez peut-être exactement la même liste de paramètres deux fois à différentes méthodes.


Cas deux.

La solution la plus évidente de tous les temps et une version simplifiée du premier cas. Ce n'est pas toujours possible, mais peut-être que les deux valeurs peuvent être créées indépendamment l'une de l'autre?

Au lieu de:

[thingA, thingB] = createThings(...);  // see above

Essayer:

thingA = createThingA(...);
thingB = createThingB(...);

Pour le rendre plus utile, ces deux méthodes peuvent partager une logique commune:

public ThingA createThingA(...) {
    doCommonThings(); // common logic
    // create thing A
}
public ThingB createThingB(...) {
    doCommonThings(); // common logic
    // create thing B
}
1
X. Wo Satuk

Pourquoi ne pas créer un objet WhateverFunctionResult contenant vos résultats, et la logique nécessaire pour analyser ces résultats, effectuer une nouvelle itération, etc. Il me semble que l'une des choses suivantes:

  1. Ces objets de résultats sont intimement liés/liés et appartiennent ensemble, ou:
  2. ils ne sont pas liés, auquel cas votre fonction n'est pas bien définie en termes de ce qu'elle essaie de faire (c'est-à-dire faire deux choses différentes)

Je vois ce genre de problème surgir encore et encore. N'ayez pas peur de créer vos propres classes de conteneur/résultat contenant les données et les fonctionnalités associées pour gérer cela. Si vous passez simplement le contenu dans un HashMap ou similaire, vos clients doivent alors séparer cette carte et en afficher le contenu chaque fois qu'ils souhaitent utiliser les résultats.

1
Brian Agnew

En C++ (STL), il existe une classe de paires pour lier deux objets. Dans Java Generics, une classe de paires n'est pas disponible, bien qu'il en existe demande . Vous pouvez facilement le mettre en œuvre vous-même si.

Je suis toutefois d’accord avec d’autres réponses pour dire que si vous devez renvoyer deux objets ou plus d’une méthode, il serait préférable de les encapsuler dans une classe.

1
kgiannakakis
public class MultipleReturnValues {

    public MultipleReturnValues() {
    }

    public static void functionWithSeveralReturnValues(final String[] returnValues) {
        returnValues[0] = "return value 1";
        returnValues[1] = "return value 2";
    }

    public static void main(String[] args) {
        String[] returnValues = new String[2];
        functionWithSeveralReturnValues(returnValues);
        System.out.println("returnValues[0] = " + returnValues[0]);
        System.out.println("returnValues[1] = " + returnValues[1]);
    }

}
1
tsug303

Passez une liste à votre méthode et remplissez-la, puis renvoyez la chaîne avec les noms, comme ceci:

public String buildList(List<?> list) {
    list.add(1);
    list.add(2);
    list.add(3);
    return "something,something,something,dark side";
}

Puis appelez ça comme ça:

List<?> values = new ArrayList<?>();
String names = buildList(values);
0
Triqui