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

199

我想从一个Java方法中返回两个对象,并且想知道有什么好的方法可以实现这一点?

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

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

我希望从一个方法中返回这两个对象,因为我不想遍历对象列表来获取逗号分隔的名称(我可以在此方法的同一个循环中完成这些操作)。

以某种方式,返回一个HashMap并不是一个非常优雅的方法。


1
列表和CSV基本上是相同数据的不同视图吗?听起来你需要的是一个对象,其中有一个单独的“列表”引用和一种查找表。 - James P.
相关问题:https://dev59.com/ubDla4cB1Zd3GeqP1wYb - John McClane
25个回答

140
如果您想返回两个对象,通常会返回一个封装了这两个对象的单个对象。
您可以像这样返回一个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)。

3
我一直很好奇为什么Java没有一个Pair<T, U>类来实现这个功能。 - David Koelle
1
只有当您想要返回的项目属于同一类,或者至少具有紧密的共同祖先时,此方法才能很好地工作。我的意思是,在任何类型的位置使用Object并不是很好看。 - David Hanak
Joachim -- 我怀疑你可能看不到这个帖子,因为它太旧了,但我想知道你为什么认为返回多种类型的对象是一种“代码异味”。这会导致哪些问题,或者表明了哪些糟糕的设计?谢谢。 - J. Taylor
@jrtayloriv:返回不同类型的对象的问题在于你无法一致地处理这些返回的对象:要么返回的类型取决于提供的输入(在这种情况下,它应该是两种不同的方法),要么与其无关(在这种情况下,你应该返回一个具有足够功能以便于使用的公共基类)。 - Joachim Sauer
@DavidKoelle 你是指类似于双向映射的东西吗? - Weckar E.
显示剩余3条评论

74

如果你知道你将返回两个对象,你也可以使用一个通用的pair:

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 静态方法可以帮助你处理样板类型,而Java 7中的菱形运算符会使这一点更少烦人。在泛型方面有些工作需要做得更好(比如PECS),但现在应该还好。
  • hashcodeequals 是由eclipse生成的。
  • cast方法中的编译时强制转换是可以的,但似乎不太对。
  • 我不确定isInstance中的通配符是否必要。
  • 我只是为了说明而根据评论编写了这篇文章。

@jamesh:你介意在这里详细写出你的Pair吗?我想知道在提供“hashCode/equals实现以及可能的静态isInstance()和cast()方法”后它是什么样子。谢谢。 - Qiang Li
@QiangLi - 我通常会生成hashCode和equals方法。实例方法isInstance接受两个类并确保实例的a和b是这些类的实例。Cast方法将Pair<?,?>小心地转换为Pair<A,B>。实现应该相当容易(提示:Class.cast(),Class.isInstance())。 - jamesh
@Jamesh,如果您可以把您说的话拼写出来,我会非常感激。也许可以另外发一篇回答?我当然会给您点赞!只是我不太确定如何实现您所说的方式。谢谢! - Qiang Li
3
这是一个非常好的“Pair”实现。 我会做一个小小的更改:在“ClassCastException”中添加一条消息。否则,如果出现问题,调试将变成一场噩梦。 (如果您将其转换为“Pair<?,?>”,则不需要使用“rawtypes”抑制警告,因为您只需要从“a”和“b”中使用“Object”方法)。 您介意我稍微修改代码吗? - Joachim Sauer
在我看来,Pair 类的字段 ab 更好的命名应该是 firstsecond,因为这样可以清晰地表达这些字段的含义。另一方面,对于这样一个基本的类,有些人更喜欢使用较短的字段名 - 因此 ab 也不是坏选择。 - ToolmakerSteve
显示剩余2条评论

32

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

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

David Hanak的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;

8
对于Pair类的控制优势。 - Hlex
7
个人认为,不要这样做——声明对于预期返回值说得太少了。据我所知,更广泛的做法是创建泛型类,指定返回参数的数量和类型。例如,Pair<T1, T2>Tuple<T1, T2, T3>Tuple<T1, T2, T3, T4>等等。然后在具体使用时,展示参数的数量和类型,如Pair<int, String> temp = ...或其他类似情况。 - ToolmakerSteve

26

您可以使用以下任何一种方式:

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 CommonsPair

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

在我编写Java代码时,我几乎总是会定义n元组类。例如:

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

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


9

Apache Commons提供了元组和三元组的实现:

  • ImmutablePair<L,R> 一个由两个对象元素组成的不可变的二元组。
  • ImmutableTriple<L,M,R> 一个由三个对象元素组成的不可变的三元组。
  • MutablePair<L,R> 一个由两个对象元素组成的可变的二元组。
  • MutableTriple<L,M,R> 一个由三个对象元素组成的可变的三元组。
  • Pair<L,R> 一个由两个元素组成的二元组。
  • Triple<L,M,R> 一个由三个元素组成的三元组。

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


我们是否有类似的类可以返回超过五个类? - MPPNBD

9

在Java 5之前,我会有点同意Map解决方案并不理想。它不能在编译时进行类型检查,因此可能会在运行时出现问题。然而,在Java 5中,我们有了泛型。

所以你的方法可以这样写:

public Map<String, MyType> doStuff();

“MyType”指的是您返回的对象类型。

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


如果任何名称发生冲突,这将无法工作。列表可以包含重复项,但映射表不能(包含重复键)。 - tvanfosson
当然。我是根据问题做出的假设 - 也许有些过度了 :) - kipz
尽管你的假设在这种情况下是正确的,但我正在走向过早优化的领域(这是我不应该做的)。 - Jagmal

6

虽然在你的情况下,评论可能是一个不错的选择,在Android中,你可以使用Pair。简单地说

return new Pair<>(yourList, yourCommaSeparatedValues);

6

另外,在需要从方法中返回多个内容时,有时我会使用回调机制而不是容器。在无法预先确定将生成多少对象的情况下,这种方法非常有效。

对于您的特定问题,它看起来像这样:

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

抱歉评论了您的一个非常古老的回答,但是关于垃圾回收,回调函数如何进行呢?我肯定对java中的内存管理没有很好的理解,如果您有对象A调用对象B.getResult()并且B.getResult()作为callback调用A.finishResult(),那么对象B会被垃圾回收还是会一直存在直到A完成?可能是一个愚蠢的问题,但这是我基本的困惑! - wired00

5

关于一般情况下多返回值的问题,我通常使用一个小助手类来包装单个返回值,并将其作为参数传递给方法:

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

优点(与其他解决方案相比):

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

感谢这个解决方案,它为每个返回值提供了名称和类型安全性,而不需要为每组返回值类型声明一个类。 - ToolmakerSteve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接