Как вернуть несколько объектов из метода Java? - PullRequest
160 голосов
/ 19 января 2009

Я хочу вернуть два объекта из метода Java, и мне было интересно, что может быть хорошим способом сделать это?

Возможные способы, которые я могу придумать: вернуть HashMap (поскольку два объекта связаны) или вернуть ArrayList из Object объектов.

Если быть более точным, два объекта, которые я хочу вернуть, это (a) List объектов и (b) разделенные запятыми имена одного и того же.

Я хочу вернуть эти два объекта из одного метода, потому что я не хочу перебирать список объектов, чтобы получить имена через запятую (что я могу сделать в том же цикле в этом методе).

Почему-то возвращение HashMap не выглядит очень элегантным способом сделать это.

Ответы [ 25 ]

121 голосов
/ 19 января 2009

Если вы хотите вернуть два объекта, вы обычно хотите вернуть один объект, который вместо этого инкапсулирует два объекта.

Вы можете вернуть список NamedObject объектов, таких как:

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

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

Тогда вы можете легко вернуть List<NamedObject<WhateverTypeYouWant>>.

Также: Почему вы хотите вернуть список имен через запятую вместо List<String>? Или еще лучше, вернуть Map<String,TheObjectType> с ключами, являющимися именами и значениями объектов (если ваши объекты не имеют заданного порядка, в этом случае NavigableMap может быть тем, что вы хотите.

63 голосов
/ 19 января 2009

Если вы знаете, что собираетесь вернуть два объекта, вы также можете использовать общую пару:

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

Редактировать Более полно сформулированная реализация выше:

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

    }

}

Примечания, в основном о ржавчине с Java и дженерики:

  • и a и b являются неизменными.
  • makePair Статический метод помогает вам при наборе текста на плите, что делает оператор Diamond в Java 7 менее раздражающим. Есть некоторая работа, чтобы сделать этот действительно хороший re: generics, но теперь все должно быть в порядке. (см. PECS)
  • hashcode и equals генерируются затмением.
  • приведение времени компиляции в методе cast в порядке, но не совсем правильно.
  • Я не уверен, нужны ли подстановочные знаки в isInstance.
  • Я только что написал это в ответ на комментарии, только для иллюстрации.
23 голосов
/ 04 апреля 2013

Если метод, который вы вызываете, является частным или вызывается из одного места, попробуйте

return new Object[]{value1, value2};

Звонящий выглядит так:

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

Пример пары Дэвида Ханака не имеет синтаксического преимущества и ограничен двумя значениями.

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

А звонящий выглядит так:

Pair<Type1, Type2> temp=myMethod(parameters);
Type1 value1=temp.a;  //For code clarity: temp.a is not descriptive
Type2 value2=temp.b;
16 голосов
/ 18 февраля 2016

Вы можете использовать любой из следующих способов:

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) Использование Array

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) Использование 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) Использование 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) Использование вашего пользовательского контейнера класса

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) Использование 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) Использование пары из 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());
}
15 голосов
/ 19 января 2009

Я почти всегда определяю классы n-Tuple, когда пишу код на Java. Например:

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

Я знаю, что это немного уродливо, но это работает, и вам просто нужно один раз определить типы кортежей. В кортежах Java чего-то действительно не хватает.

РЕДАКТИРОВАТЬ: пример Дэвида Ханака является более элегантным, поскольку он избегает определения геттеров и по-прежнему сохраняет объект неизменным.

9 голосов
/ 19 января 2009

До Java 5 я бы согласился с тем, что решение Map не является идеальным. Это не даст вам проверку типа времени компиляции, поэтому может вызвать проблемы во время выполнения. Однако в Java 5 у нас есть универсальные типы.

Итак, ваш метод может выглядеть так:

public Map<String, MyType> doStuff();

MyType - тип объекта, который вы возвращаете.

По сути, я думаю, что возвращение карты является правильным решением в этом случае, потому что это именно то, что вы хотите вернуть - отображение строки в объект.

8 голосов
/ 19 января 2009

Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла.

D. Кнут

6 голосов
/ 19 января 2009

В качестве альтернативы, в ситуациях, когда я хочу вернуть несколько вещей из метода, я иногда буду использовать механизм обратного вызова вместо контейнера. Это очень хорошо работает в ситуациях, когда я не могу указать заранее, сколько объектов будет сгенерировано.

С вашей конкретной проблемой это будет выглядеть примерно так:

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 );
    }
}
5 голосов
/ 12 декабря 2013

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

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

(для примитивных типов данных я использую небольшие вариации для непосредственного хранения значения)

Метод, который хочет вернуть несколько значений, будет затем объявлен следующим образом:

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

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

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

Преимущества (по сравнению с другими предлагаемыми решениями):

  • Вам не нужно создавать специальное объявление класса для отдельных методов и его возвращаемых типов
  • Параметры получают имя и, следовательно, их легче различить, глядя на сигнатуру метода
  • Тип безопасности для каждого параметра
5 голосов
/ 04 февраля 2016

Apache Commons имеет кортеж и тройку для этого:

  • ImmutablePair<L,R> Неизменная пара, состоящая из двух Объектов. элементы.
  • ImmutableTriple<L,M,R> Неизменная тройка, состоящая из три объекта объекта.
  • MutablePair<L,R> Изменчивая пара, состоящая из два объекта объекта.
  • MutableTriple<L,M,R> Изменчивая тройка состоящий из трех элементов Object.
  • Pair<L,R> Пара, состоящая из два элемента.
  • Triple<L,M,R> Тройка, состоящая из трех элементов.

Источник: https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/tuple/package-summary.html

...