如何从 Java 方法返回多个对象?

我想从 Java 方法返回两个对象,并且想知道什么是这样做的好方法?

我能想到的可能的方法是: 返回一个 HashMap(因为这两个对象是相关的)或返回一个 Object对象的 ArrayList

更准确地说,我想返回的两个对象是(a)对象的 List和(b)相同对象的逗号分隔的名称。

我想从一个方法返回这两个 Object,因为我不想迭代对象列表以获得逗号分隔的名称(我可以在这个方法的同一个循环中这样做)。

不知何故,返回一个 HashMap看起来不是一个非常优雅的方式这样做。

279488 次浏览

如果要返回两个对象,通常需要返回一个封装这两个对象的对象。

您可以像下面这样返回 NamedObject对象的 List:

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可能就是您想要的。

所有可能的解决方案都将是一个组装(就像容器对象、 HashMap 想法、通过数组实现的“多个返回值”一样)。我建议从返回的 List 中重新生成逗号分隔的列表。代码最后会干净很多。

在 Java5之前,我有点同意 Map 解决方案并不理想。它不会提供编译时类型检查,因此会在运行时引起问题。但是,在 Java5中,我们有泛型类型。

所以你的方法是这样的:

public Map<String, MyType> doStuff();

MyType 当然是您要返回的对象的类型。

基本上,我认为在这种情况下返回 Map 是正确的解决方案,因为这正是您想要返回的——字符串到对象的映射。

在我看来,实际上有三种选择,解决方案取决于上下文。可以选择在生成列表的方法中实现名称的构造。这是你的选择,但我不认为这是最好的选择。您正在生产者方法中创建一个不需要存在的耦合到消费方法。其他呼叫者可能不需要额外的信息,您将为这些呼叫者计算额外的信息。

或者,您可以让调用方法计算名称。如果只有一个呼叫者需要这个信息,那么您可以在此停止。您没有额外的依赖项,虽然有一点额外的计算,您已经避免使您的构造方法过于具体。这是一个很好的交易。

最后,您可以让列表本身负责创建名称。如果需要多个调用者进行计算,我会选择这条路线。我认为这就把创建名称的责任放在了与对象本身关系最密切的类上。

在后一种情况下,我的解决方案是创建一个专门的 List 类,它返回一个逗号分隔的字符串,其中包含对象的名称。使类足够智能,以便在添加和删除对象时动态构造名称字符串。然后返回此列表的一个实例,并根据需要调用名称生成方法。尽管简单地将名称的计算延迟到第一次调用方法并存储它(延迟加载)可能几乎同样有效(并且更简单)。如果添加/删除一个对象,只需删除计算出的值,并在下一次调用时重新计算。

在 C + + (STL)中有一个用于捆绑两个对象的成对类。在 JavaGenerics 中,一个成对类是不可用的,尽管它有一些 需求。但是您可以很容易地自己实现它。

然而,我同意其他一些回答,如果需要从一个方法返回两个或更多对象,最好将它们封装在一个类中。

如果知道要返回两个对象,也可以使用通用对:

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 和泛型的生锈:

  • ab都是不可变的。
  • makePair静态方法可以帮助您进行锅炉板类型化,Java7中的菱形操作符可以减少这种类型化的麻烦。还有一些工作要做,使这个非常好的 re: 泛型,但它现在应该是 OK-ish。(参考 PECS)
  • hashcodeequals是由 Eclipse 生成的。
  • cast方法中的编译时强制转换是可以的,但似乎不太正确。
  • 我不确定是否需要 isInstance中的通配符。
  • 我写这篇文章是为了回应评论,仅仅是为了说明问题。

当我用 Java 编写代码时,我几乎总是定义 n-Tuple 类。例如:

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 真正缺乏的东西。

编辑: DavidHanak 的示例更加优雅,因为它避免了定义 getter,并且仍然保持对象不变。

或者,在我想从一个方法返回一些东西的情况下,我有时会使用回调机制而不是容器。在我不能提前指定将生成多少对象的情况下,这种方法非常有效。

对于你的特殊问题,它看起来是这样的:

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

为什么不创建一个包含结果的 WhateverFunctionResult对象,还有解析这些结果所需的逻辑,然后迭代等等。在我看来:

  1. 这些结果对象紧密地联系在一起/相关并且属于一起,或者:
  2. 它们是不相关的,在这种情况下,你的函数没有很好地定义它要做的事情(即做两件不同的事情)

我看到这类问题一次又一次地出现。不要害怕创建自己的容器/结果类,这些类包含处理这些数据和相关功能的数据。如果您只是简单地以 HashMap或类似的形式传递这些内容,那么您的客户端必须将这个映射分开,并在每次想要使用结果时获取内容。

传递一个散列到方法中,然后将其普及... ..。

Public void buildResponse (String data,Map response) ;

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


}

使用以下 Entry 对象 例如:

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


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

在 C 语言中,可以通过将结果的占位符作为参数传递给指针来实现:

void getShoeAndWaistSizes(int *shoeSize, int *waistSize) {
*shoeSize = 36;
*waistSize = 45;
}
...
int shoeSize, waistSize;
getShoeAndWaistSize(&shoeSize, &waistSize);
int i = shoeSize + waistSize;

让我们试试类似的东西,在 Java 中。

void getShoeAndWaistSizes(List<Integer> shoeSize, List<Integer> waistSize) {
shoeSize.add(36);
waistSize.add(45);
}
...
List<Integer> shoeSize = new List<>();
List<Integer> waistSize = new List<>();
getShoeAndWaistSizes(shoeSize, waistSize);
int i = shoeSize.get(0) + waistSize.get(0);

如果要调用的方法是私有的,或者从一个位置调用,请尝试

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

DavidHanak 的 Pair 示例没有语法优势,仅限于两个值。

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;

可以在动态语言(Python)中执行类似 tuple 的操作

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

像这样使用

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




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

关于多个返回值的问题,我通常使用一个小的 helper 类,它包装一个返回值,并作为参数传递给方法:

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

优势(相对于提出的其他解决方案) :

  • 您不必为各个方法及其返回类型创建特殊的类声明
  • 参数获得一个名称,因此在查看方法签名时更容易区分
  • 每个参数的类型安全性

将一个列表传递给方法并填充它,然后返回包含名称的 String,如下所示:

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

那就这么说吧:

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

我遵循了与其他答案中描述的类似的方法,基于我的需求做了一些调整,基本上我创建了以下类(以防万一,一切都是 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);
}
}

然后,我的要求很简单,在到达数据库的存储库类中,对于获取方法而不是从数据库中检索数据,我需要检查它是否失败或成功,然后,如果成功,我需要使用返回列表,如果失败,停止执行并通知错误。

例如,我的方法是这样的:

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
}

其中 ResultMessage 只是一个包含两个字段(代码/消息)的类,而 Customer 是包含来自 DB 的大量字段的任何类。

然后,为了检查结果,我这样做:

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

这并不能完全回答这个问题,但是由于这里给出的每个解决方案都有一些缺点,我建议尝试对代码进行一些重构,这样您只需返回一个值。

第一个案子。

你需要在你的方法之内和之外的一些东西。为什么不在外面计算,然后传递给方法呢?

而不是:

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

试试:

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

这应该可以满足您的大部分需求,因为在大多数情况下,一个值先于另一个值创建,您可以分别用两种方法创建它们。缺点是,与 createThings相比,createThingsB方法有一个额外的参数,并且您可能将完全相同的参数列表两次传递给不同的方法。


案例二。

有史以来最明显的解决方案和案例一的简化版本。这并不总是可能的,但是也许这两个值可以相互独立地创建?

而不是:

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

试试:

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

为了使其更加有用,这两种方法可以共享一些共同的逻辑:

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

Apache Commons 为此提供了 tuple 和 triple:

  • 由两个 Object 组成的不可变对 元素。
  • 不可变的三元组 三个 Object 元素。
  • 一个可变对,由 两个 Object 元素。
  • 一个可变的三元组 由三个 Object 元素组成。
  • 一对由 两种元素。
  • 由三个元素组成的三元组。

资料来源: https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/tuple/package-summary.html

你可使用以下任何一种方法:

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)使用 数组

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)使用 数组列表

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)使用 简单入口

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

而在你的情况下,评论可能是一个很好的方法,在 Android 中,你可以使用 Pair

return new Pair<>(yourList, yourCommaSeparatedValues);

保持简单,并为多个结果情况创建一个类。此示例接受数据库 helper getInfo 中的 ArrayList 和消息文本。

调用返回多个编码值的例程:

multResult res = mydb.getInfo();

在例程 getInfo 中编写:

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

定义一个类 multResult:

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

}

您可以使用 HashMap<String, Object>,如下所示

public HashMap<String, Object> yourMethod()
{


.... different logic here


HashMap<String, Object> returnHashMap = new HashMap<String, Object>();
returnHashMap.put("objectA", objectAValue);
returnHashMap.put("myString", myStringValue);
returnHashMap.put("myBoolean", myBooleanValue);


return returnHashMap;
}

然后,在不同的作用域中调用方法时,可以将每个对象强制转换回其初始类型:

// call the method
HashMap<String, Object> resultMap = yourMethod();
                

// fetch the results and cast them
ObjectA objectA = (ObjectA) resultMap.get("objectA");
String myString = (String) resultMap.get("myString");
Boolean myBoolean = (Boolean) resultMap.get("myBoolean");

我注意到没有非自定义类、 n 长度、非强制转换、类型安全的答案来返回多个值。

我要开始了:

import java.util.Objects;


public final class NTuple<V, T extends  NTuple<?, ?>> {
private final V value;
private final T next;


private NTuple(V value, T next) {
this.value = value;
this.next = next;
}


public static <V> NTuple<V, ?> of(V value) {
return new NTuple<>(value, null);
}


public static <V,  T extends  NTuple<?, ?>> NTuple<V, T> of(V value, T next) {
return new NTuple<>(value, next);
}


public V value() {
return value;
}


public T next() {
return next;
}


public static <V> V unpack0(NTuple<V, ?> tuple) {
return Objects.requireNonNull(tuple, "0").value();
}


public static <V, T extends NTuple<V, ?>> V unpack1(NTuple<?, T> tuple) {
NTuple<?, T> tuple0 = Objects.requireNonNull(tuple, "0");
NTuple<V, ?> tuple1 = Objects.requireNonNull(tuple0.next(), "1");
return tuple1.value();
}


public static <V, T extends NTuple<?, NTuple<V, ?>>> V unpack2(NTuple<?, T> tuple) {
NTuple<?, T> tuple0 = Objects.requireNonNull(tuple, "0");
NTuple<?, NTuple<V, ?>> tuple1 = Objects.requireNonNull(tuple0.next(), "1");
NTuple<V, ?> tuple2 = Objects.requireNonNull(tuple1.next(), "2");
return tuple2.value();
}
}

使用范例:

public static void main(String[] args) {
// pre-java 10 without lombok - use lombok's var or java 10's var if you can
NTuple<String, NTuple<Integer, NTuple<Integer, ?>>> multiple = wordCount("hello world");
String original = NTuple.unpack0(multiple);
Integer wordCount = NTuple.unpack1(multiple);
Integer characterCount  = NTuple.unpack2(multiple);


System.out.println(original + ": " + wordCount + " words " + characterCount + " chars");
}


private static NTuple<String, NTuple<Integer, NTuple<Integer, ?>>> wordCount(String s) {
int nWords = s.split(" ").length;
int nChars = s.length();
return NTuple.of(s, NTuple.of(nWords, NTuple.of(nChars)));
}

优点:

  • 非自定义容器类——不需要为返回类型编写类
  • N-length-可以处理任意数量的返回值
  • 不需要从对象进行强制转换
  • Type-safe-通过 Java 的泛型检查类型

缺点:

  • 对于大量的返回值来说效率不高
    • 根据我使用 python 的多个返回值的经验,这种情况在实际中不应该发生
  • 重类型声明重类型声明
    • Lombok/Java10var