我们知道,Java 方法不支持有多个返回值。如果要在一个 Java 方法中返回多个处理结果,有以下几种方式:

使用数组

数组元素要求类型相同,如果方法的处理结果类型不同,比如有数值类型、字符串类型等,可以使用像 List<Object> 来存放。此种方法最大的弊端是调用方需要知道列表中每一个 Object 的实际类型并做强制转换,不够优雅,维护、使用成本也比较高。实际开发中不推荐使用。

使用映射

Map<String, Object>,使用 Object 类型存放结果,调用方需要知道每个 key 值,以及每个 key 值对应的 Object 实际类型。此种方法同样不够优雅,维护、使用成本也比较高。实际开发中不推荐使用。

自定义类

将方法的多个处理结果封装成一个类进行返回,比如方法中需要返回的处理结果有整数类型、浮点数类型和字符串类型,我们可以封装一个这样的类来接收返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class ProcessResult {
private int intVal;
private float floatVal;
private String stringVal;

public ProcessResult() {
}

public ProcessResult(int intVal, float floatVal, String stringVal) {
this.intVal = intVal;
this.floatVal = floatVal;
this.stringVal = stringVal;
}

public int getIntVal() {
return intVal;
}

public void setIntVal(int intVal) {
this.intVal = intVal;
}

public float getFloatVal() {
return floatVal;
}

public void setFloatVal(float floatVal) {
this.floatVal = floatVal;
}

public String getStringVal() {
return stringVal;
}

public void setStringVal(String stringVal) {
this.stringVal = stringVal;
}
}

处理方法返回结果类型为 ProcessResult

1
2
3
4
5
6
7
8
9
10
public ProcessResult process(){
// ... 此处省略业务逻辑处理代码
// 以下为处理结果
int intVal = 1;
float floatVal = 2F;
String stringVal = "test code";

// 返回处理结果
return new ProcessResult(intVal, floatVal, stringVal);
}

此种方式的一个弊端是可能需要封装多个类来处理不同方法的返回结果。

使用第三方库封装的类

借助第三方库封装好的类来返回方法的处理结果的好处是,不用自己针对每一个方法都新建一个类来接收处理结果。

这里以第三方包 commons-lang3 为例。

比如需要返回的值有两个,可以使用的 org.apache.commons.lang3.tuple.Pair<L, R> 类来接收。

首先在 pom.xml 添加依赖:

1
2
3
4
5
6
<!-- commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>

然后,将方法的返回类型定义为 Pair,比如需要返回的两个结果有整数、字符串类型,返回类型定义为 Pair<Integer, String>,示例代码如下:

1
2
3
4
5
6
7
public Pair<Integer, String> process(){
// ... 此处省略业务逻辑处理代码
// 以下为处理结果
Pair<Integer, String> pair = Pair.of(23, "aaa");
// 返回处理结果
return pair;
}

最后,调用方获取结果的方式:

1
2
3
Pair<Integer, String> pair = process();
Integer intVal = pair.getLeft();
String strVal = pair.getRight();

同样,如果需要在方法中返回三个值,可以使用 org.apache.commons.lang3.tuple.Triple<L, M, R>类。其初始化语法如下:

1
Triple.of(L left, M middle, R right)

获取结果:

1
2
3
triple.getLeft();
triple.getMiddle();
triple.getRight();

自定义通用类

借助第三方包,返回值的数量取决于第三方包封装的类对应能接收的个数。比如使用第三方包 commons-lang3,类 Pair 和 Triple 能接收的个数分别为两个和三个。如果返回值的数量超过三个,则需要自己封装类来处理。但如果针对不同方法的返回结果都封装一个返回类型,则有可能导致这样的类过多。

针对这种情况,我们可以自己封装通用类。比如提前封装接收两个返回值的类、三个个返回值的类、四个返回值的类……但不限定返回值的类型。

如在 Flink 项目中,org.apache.flink.api.java.tuple 包下面就提前封装了多个元组类型。我们可以参考该包下的元组类定义,封装自己的通用类。

以下代码定义了一个可以接收四个返回值的类 Tuple4<T0, T1, T2, T3>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// --------------------------------------------------------------
// THIS IS A GENERATED SOURCE FILE. DO NOT EDIT!
// GENERATED FROM org.apache.flink.api.java.tuple.TupleGenerator.
// --------------------------------------------------------------

package org.apache.flink.api.java.tuple;

import org.apache.flink.annotation.Public;
import org.apache.flink.util.StringUtils;

/**
* A tuple with 4 fields. Tuples are strongly typed; each field may be of a separate type. The
* fields of the tuple can be accessed directly as public fields (f0, f1, ...) or via their position
* through the {@link #getField(int)} method. The tuple field positions start at zero.
*
* <p>Tuples are mutable types, meaning that their fields can be re-assigned. This allows functions
* that work with Tuples to reuse objects in order to reduce pressure on the garbage collector.
*
* <p>Warning: If you subclass Tuple4, then be sure to either
*
* <ul>
* <li>not add any new fields, or
* <li>make it a POJO, and always declare the element type of your DataStreams/DataSets to your
* descendant type. (That is, if you have a "class Foo extends Tuple4", then don't use
* instances of Foo in a DataStream&lt;Tuple4&gt; / DataSet&lt;Tuple4&gt;, but declare it as
* DataStream&lt;Foo&gt; / DataSet&lt;Foo&gt;.)
* </ul>
*
* @see Tuple
* @param <T0> The type of field 0
* @param <T1> The type of field 1
* @param <T2> The type of field 2
* @param <T3> The type of field 3
*/
@Public
public class Tuple4<T0, T1, T2, T3> extends Tuple {

private static final long serialVersionUID = 1L;

/** Field 0 of the tuple. */
public T0 f0;
/** Field 1 of the tuple. */
public T1 f1;
/** Field 2 of the tuple. */
public T2 f2;
/** Field 3 of the tuple. */
public T3 f3;

/** Creates a new tuple where all fields are null. */
public Tuple4() {}

/**
* Creates a new tuple and assigns the given values to the tuple's fields.
*
* @param f0 The value for field 0
* @param f1 The value for field 1
* @param f2 The value for field 2
* @param f3 The value for field 3
*/
public Tuple4(T0 f0, T1 f1, T2 f2, T3 f3) {
this.f0 = f0;
this.f1 = f1;
this.f2 = f2;
this.f3 = f3;
}

@Override
public int getArity() {
return 4;
}

@Override
@SuppressWarnings("unchecked")
public <T> T getField(int pos) {
switch (pos) {
case 0:
return (T) this.f0;
case 1:
return (T) this.f1;
case 2:
return (T) this.f2;
case 3:
return (T) this.f3;
default:
throw new IndexOutOfBoundsException(String.valueOf(pos));
}
}

@Override
@SuppressWarnings("unchecked")
public <T> void setField(T value, int pos) {
switch (pos) {
case 0:
this.f0 = (T0) value;
break;
case 1:
this.f1 = (T1) value;
break;
case 2:
this.f2 = (T2) value;
break;
case 3:
this.f3 = (T3) value;
break;
default:
throw new IndexOutOfBoundsException(String.valueOf(pos));
}
}

/**
* Sets new values to all fields of the tuple.
*
* @param f0 The value for field 0
* @param f1 The value for field 1
* @param f2 The value for field 2
* @param f3 The value for field 3
*/
public void setFields(T0 f0, T1 f1, T2 f2, T3 f3) {
this.f0 = f0;
this.f1 = f1;
this.f2 = f2;
this.f3 = f3;
}

// -------------------------------------------------------------------------------------------------
// standard utilities
// -------------------------------------------------------------------------------------------------

/**
* Creates a string representation of the tuple in the form (f0, f1, f2, f3), where the
* individual fields are the value returned by calling {@link Object#toString} on that field.
*
* @return The string representation of the tuple.
*/
@Override
public String toString() {
return "("
+ StringUtils.arrayAwareToString(this.f0)
+ ","
+ StringUtils.arrayAwareToString(this.f1)
+ ","
+ StringUtils.arrayAwareToString(this.f2)
+ ","
+ StringUtils.arrayAwareToString(this.f3)
+ ")";
}

/**
* Deep equality for tuples by calling equals() on the tuple members.
*
* @param o the object checked for equality
* @return true if this is equal to o.
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Tuple4)) {
return false;
}
@SuppressWarnings("rawtypes")
Tuple4 tuple = (Tuple4) o;
if (f0 != null ? !f0.equals(tuple.f0) : tuple.f0 != null) {
return false;
}
if (f1 != null ? !f1.equals(tuple.f1) : tuple.f1 != null) {
return false;
}
if (f2 != null ? !f2.equals(tuple.f2) : tuple.f2 != null) {
return false;
}
if (f3 != null ? !f3.equals(tuple.f3) : tuple.f3 != null) {
return false;
}
return true;
}

@Override
public int hashCode() {
int result = f0 != null ? f0.hashCode() : 0;
result = 31 * result + (f1 != null ? f1.hashCode() : 0);
result = 31 * result + (f2 != null ? f2.hashCode() : 0);
result = 31 * result + (f3 != null ? f3.hashCode() : 0);
return result;
}

/**
* Shallow tuple copy.
*
* @return A new Tuple with the same fields as this.
*/
@Override
@SuppressWarnings("unchecked")
public Tuple4<T0, T1, T2, T3> copy() {
return new Tuple4<>(this.f0, this.f1, this.f2, this.f3);
}

/**
* Creates a new tuple and assigns the given values to the tuple's fields. This is more
* convenient than using the constructor, because the compiler can infer the generic type
* arguments implicitly. For example: {@code Tuple3.of(n, x, s)} instead of {@code new
* Tuple3<Integer, Double, String>(n, x, s)}
*/
public static <T0, T1, T2, T3> Tuple4<T0, T1, T2, T3> of(T0 f0, T1 f1, T2 f2, T3 f3) {
return new Tuple4<>(f0, f1, f2, f3);
}
}

(END)