Javaメソッドから2つのオブジェクトを返したいのですが、それを行うのに良い方法は何だろうと思っていましたか?
考えられる考えられる方法は、HashMap
を返す(2つのオブジェクトが関連しているため)またはArrayList
オブジェクトのObject
を返すことです。
より正確には、返される2つのオブジェクトは、(a)オブジェクトのList
と(b)同じ名前のコンマ区切りの名前です。
これらの2つのオブジェクトを1つのメソッドから返したいのは、オブジェクトのリストを反復処理してコンマ区切りの名前を取得したくないためです(このメソッドの同じループで実行できます)。
どういうわけか、HashMap
を返すことは、そうするのに非常にエレガントな方法に見えません。
2つのオブジェクトを返す場合は、通常、代わりに2つのオブジェクトをカプセル化する単一のオブジェクトを返します。
次のような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
が必要な場合があります)。
2つのオブジェクトを返すことがわかっている場合は、ジェネリックペアを使用することもできます。
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
静的メソッドは、Java 7のダイアモンド演算子が面倒を少なくするボイラープレートタイピングに役立ちます。これを本当に素晴らしいものにするためのいくつかの作業があります。ジェネリックですが、今は大丈夫です。 (c.f. PECS)hashcode
およびequals
はEclipseによって生成されます。cast
メソッドでのコンパイル時のキャスティングは問題ありませんが、まったく正しくないようです。isInstance
のワイルドカードが必要かどうかわかりません。呼び出しているメソッドがプライベートであるか、1つの場所から呼び出された場合、試してください
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によるペアの例には構文上の利点はなく、2つの値に制限されています。
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;
次のいずれかの方法を使用できます。
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)ペア of 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());
}
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に本当に欠けているものです。
編集:デビッドハナックの例は、ゲッターを定義することを避け、オブジェクトを不変に保つため、よりエレガントです。
Java 5の前に、Mapソリューションが理想的ではないことに同意するでしょう。コンパイル時に型チェックを行うことはないため、実行時に問題が発生する可能性があります。ただし、Java 5では、ジェネリック型があります。
したがって、メソッドは次のようになります。
public Map<String, MyType> doStuff();
もちろん、MyTypeは返されるオブジェクトのタイプです。
基本的に、この場合、Mapを返すことは正しい解決策だと思います。なぜなら、それがまさにあなたが返したいものだからです-オブジェクトへの文字列のマッピング。
私たちは小さな効率を忘れてはなりません。約97%の時間です。早すぎる最適化はすべての悪の根源です。
D.クヌース
あるいは、メソッドから多くの情報を返したい状況では、コンテナの代わりにコールバックメカニズムを使用することがあります。これは、生成されるオブジェクトの数を事前に指定できない場合に非常に役立ちます。
特定の問題では、次のようになります。
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 );
}
}
一般に複数の戻り値に関する問題に関して、私は通常、単一の戻り値をラップし、メソッドにパラメーターとして渡される小さなヘルパークラスを使用します。
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("...");
//...
}
おそらく主な欠点は、呼び出し元が戻りオブジェクトを使用したい場合に備えて事前に準備する必要があることです(そしてメソッドはnullポインターをチェックする必要があります)
ReturnParameter<ClassA> nameForFirstValue = new ReturnParameter<ClassA>();
ReturnParameter<ClassB> nameForSecondValue = new ReturnParameter<ClassB>();
methodThatReturnsTwoValues(nameForFirstValue, nameForSecondValue);
利点(提案されている他のソリューションと比較して):
Apache Commonsには、このためのタプルとトリプルがあります。
ImmutablePair<L,R>
2つのObject要素で構成される不変のペア。ImmutableTriple<L,M,R>
3つのObject要素で構成される不変のトリプル。MutablePair<L,R>
2つのObject要素で構成される可変ペア。MutableTriple<L,M,R>
3つのObject要素で構成される可変トリプル。Pair<L,R>
2つの要素で構成されるペア。Triple<L,M,R>
3つの要素で構成されるトリプル。次のエントリオブジェクトの使用例:
public Entry<A,B> methodname(arg)
{
.......
return new AbstractMap.simpleEntry<A,B>(instanceOfA,instanceOfB);
}
考えられるすべての解決策は、手間のかかるものです(コンテナオブジェクト、HashMapのアイデア、配列によって実現される「複数の戻り値」など)。返されたリストからコンマ区切りリストを再生成することをお勧めします。コードは最終的にはずっときれいになります。
シンプルに保ち、複数の結果の状況に対応するクラスを作成します。この例では、ArrayHelpおよびdatabasehelper getInfoからのメッセージテキストを受け入れます。
コーディングする複数の値を返すルーチンを呼び出す場所:
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;
}
}
動的言語でタプルのようなことを行うことができます(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]);
}
}
そして、このように使用します
public Tuple returnMultiValues(){
return Tuple._t(new ArrayList(),new HashMap())
}
Tuple t = returnMultiValues();
ArrayList list = t.at(0,ArrayList.class);
私が持っていた要件に基づいていくつかの調整を行って、他の回答で説明されているものと同様のアプローチに従い、基本的に次のクラスを作成しました(念のため、すべてが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);
}
}
その後、私の要件は単純で、DBに到達するリポジトリクラスで、DBからデータを取得するよりもGetメソッドについて、失敗したか成功したかを確認する必要があります。 、失敗した場合、実行を停止してエラーを通知します。
したがって、たとえば、私のメソッドは次のようになります。
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は、2つのフィールド(コード/メッセージ)を持つクラスであり、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!");
}
}
私が見るように、ここには本当に3つの選択肢があり、解決策はコンテキストに依存します。リストを生成するメソッドに名前の構成を実装することを選択できます。これはあなたが選んだ選択ですが、私はそれが最良の選択だとは思いません。プロデューサーメソッドで、存在する必要のないコンシュームメソッドにカップリングを作成しています。他の発信者は追加情報を必要としない場合があり、これらの発信者の追加情報を計算することになります。
または、呼び出し元のメソッドに名前を計算させることもできます。この情報を必要とする発信者が1人だけの場合は、そこで停止できます。余分な依存関係はなく、少し余分な計算が必要ですが、構築方法をあまり具体的にしないようにしました。これは良いトレードオフです。
最後に、リスト自体に名前の作成を任せることができます。これは、計算を複数の発信者が行う必要がある場合に行くルートです。これは、オブジェクト自体に最も密接に関連するクラスで名前を作成する責任を負うと思います。
後者の場合、私の解決策は、含まれるオブジェクトの名前のコンマ区切りの文字列を返す特殊なListクラスを作成することです。クラスを十分にスマートにして、オブジェクトの追加や削除に応じてその場で名前の文字列を作成します。次に、このリストのインスタンスを返し、必要に応じて名前生成メソッドを呼び出します。メソッドを最初に呼び出すまで名前の計算を単純に遅らせて、それを保存する(遅延読み込み)のとほぼ同じくらい効率的(かつ単純)かもしれませんが。オブジェクトを追加/削除する場合、計算された値を削除するだけで、次の呼び出しで再計算されます。
これは質問に正確に答えているわけではありませんが、ここで示したソリューションにはすべて欠点があるため、コードを少しリファクタリングして、1つの値のみを返す必要があることをお勧めします。
ケース1
メソッドの内側だけでなく外側にも何かが必要です。外部で計算してメソッドに渡してみませんか?
の代わりに:
[thingA, thingB] = createThings(...); // just a conceptual syntax of method returning two values, not valid in Java
試してください:
thingA = createThingA(...);
thingB = createThingB(thingA, ...);
ほとんどの場合、1つの値が他の値よりも先に作成され、それらを2つの方法で分割して作成できるため、これでほとんどのニーズを満たすことができます。欠点は、メソッドcreateThingsB
にはcreateThings
と比較して余分なパラメーターがあり、おそらくまったく同じパラメーターのリストを異なるメソッドに2回渡すことです。
ケース2
これまでで最も明白なソリューションと、ケース1の簡略化されたバージョン。常に可能であるとは限りませんが、両方の値を互いに独立して作成できますか?
の代わりに:
[thingA, thingB] = createThings(...); // see above
試してください:
thingA = createThingA(...);
thingB = createThingB(...);
より便利にするために、これらの2つのメソッドはいくつかの共通ロジックを共有できます。
public ThingA createThingA(...) {
doCommonThings(); // common logic
// create thing A
}
public ThingB createThingB(...) {
doCommonThings(); // common logic
// create thing B
}
結果を含むWhateverFunctionResult
オブジェクトを作成しないのはなぜですか?andこれらの結果を解析し、それを繰り返し処理するために必要なロジックなど。
私はこの種の問題が何度も発生するのを見ます。これを処理するためのデータと関連機能を含む独自のコンテナ/結果クラスを作成することを恐れないでください。 HashMap
などで単純にデータを渡す場合、クライアントはこのマップを分解して、結果を使用するたびに内容を把握する必要があります。
C++(STL)には、2つのオブジェクトをバンドルするペアクラスがあります。 Java Genericsでは、いくつかの demand がありますが、ペアクラスは使用できません。ただし、自分で簡単に実装できます。
ただし、メソッドから2つ以上のオブジェクトを返す必要がある場合は、それらをクラスにカプセル化することをお勧めします。
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]);
}
}
リストをメソッドに渡してデータを入力し、次のように名前を含むストリングを返します。
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);