目录
起因
解决方法
方法一:使用自定义结果对象返回
方法二:使用数组作为返回
方法三:使用集合类
方法四:使用Map
方法五:使用引用传递
方法六:使用枚举
方法七:使用commons-lang3工具包下的Pair或Triple类
起因
在工作中,在实现一些业务需求时,如果在实现方法里面把所有逻辑放在一起,那整个方法就会看起来非常冗长,造成可读性降低,代码维护起来也麻烦,更别提经过一段时间后,可能连自己都忘了当时怎么写出那一大堆逻辑的,最后把自己给坑了。
这个时候,我们就需要对里面的一些代码提取出来,使这段代码成为一个单独的方法。这个过程有个名字,英文叫“Refactoring",中文翻译过来就是“重构”。在这里特别推荐一本书,英文书名是《Refactoring: Improing the Design of Existing Code》,中文名是《重构:改善既有代码的设计》。这本书里面提到了非常多的重构技巧。
另外,在平时我们使用的工具IDEA中,也提供了非常实用的重构功能。选中一段代码,单击右键选中菜单中的Refactor,右侧会出现很多可选的重构选项。这个功能实在太棒了,代码重构过程变得轻松了很多。我目前最常用的两个重构功能就是:提取方法(Extract Method)、内联变量(Inline Parameter)。更多功能可以自己试试看,参考下面截图:
话题扯远了。回到一开始我们要讨论的方法多值返回的问题。
一般来说,当从一大堆业务逻辑代码中提取部分代码出来形成一个私有方法时,该方法的返回值只有一个。但是,在开发过程中,也常常会遇到在一个提取出来的方法中需要返回多个值。这个时候,就需要注意一下了。
我查阅了网上的资料,总结下来有以下这么几种方式来解决多值返回的问题:
- 使用自定义对象返回。
- 使用数组。
- 使用集合类。
- 使用map。
- 使用引用传递。
- 使用枚举。
- 使用commons-lang3工具包下的Pair或Triple类
以下详细看看各个方法各有什么优缺点。
解决方法
方法一:使用自定义结果对象返回
自定义一个Result对象,作为方法的返回结果对象。
这种方法的优点是:使用对象的好处是很清晰易懂,很容易根据属性名知道对应的值的含义。
缺点是:不实用,这样做会使类过于杂乱,而且这些自定义类的复用性不高。
private Result test2(int[] arr) {
Result result = new Result();
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
if (arr[i] < min) {
min = arr[i];
}
}
result.setMax(max);
result.setMin(min);
return result;
}
@Data
class Result {
int max;
int min;
}
方法二:使用数组作为返回
这种方式的优点是:能解决出参不能返回多个值的问题。
缺点是:很显然,我们无法通过下标直观的看出对应int[0]、int[1]代表的是什么,而且这种方式也很依赖顺序性。自己看看倒还是可以,别人看你的代码那可能会心里骂娘的,可读性极差,而且这样写代码可维护性也特别差。
代码示例如下:
public void test1(){
int a = 10, b = 7;
int[] compute = compute(a, b);
System.out.println("两数之和:" + compute[0]);
System.out.println("两数之差:" + compute[1]);
}
// 使用数组作为返回值类型
private int[] compute(int a, int b){
return new int[]{a+b, a-b};
}
方法三:使用集合类
这种方法的优点是:我们可以在返回值也就是List对象里面添加任何数量的值返回。
缺点:这种方式与使用数组的方式缺点是一样的,我们都不能很直观的从List[0]、List[1]中读取到什么含义,如果没有相应的注释,或者变量名命名描述不到位,那这样的代码的可读性将是非常糟糕的。可维护性也非常的差。
由于这种形式是非常简单的方式,所以在新手中不少见。我在遇到过的项目中还真看到过有的旧代码写成这样的形式,弄的我苦笑不得,看也不是,不看也不是,重构又没有时间,只能硬着头皮看完,然后不再想多看它第二眼。
// 使用List集合类作为返回值
private List<Integer> returnList(){
List<Integer> list = new ArrayList<>();
list.add(0, 1);
list.add(1, 2);
list.add(2, 3);
return list;
}
方法四:使用Map
Map是一种key-value的形式。这种键值对的形式也很方便的可以用来做返回值的封装。
使用Map比使用集合类或数组的更好的一点是,Map的key是具有可读性的。
这种方式的优点是:Map的key具有可读性,使方法的调用者更容易直观的看出该如何取值。
缺点:key的硬编码会引入的魔法值问题,会造成维护起来比较困难。虽然可以通过为key定义字符串常量,但也容易出错。稍微一个字母拼错,就会取不到对应的值,或者造成一些不可知的bug出现。
public void caller(){
int a = 10, b = 7;
Map<String, Integer> respMap = compute3(a, b);
System.out.println("两数之和:" + respMap.get("sum"));
System.out.println("两数之差:" + respMap.get("diff"));
}
private Map<String,Integer> returnMap(int a,int b){
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("diff", a - b);
hashMap.put("sum", a + b);
return hashMap;
}
方法五:使用引用传递
将引用作为参数放到方法的参数列表中,在方法中对该引用的属性进行修改。调用该方法后,就能取到对应修改后的返回值。
这种方式的优点是:毫无优点吧……
缺点是:
1.不支持基本类型,必须封装成对象才行。这会造成代码冗长。
2.代码可读性非常非常差。我个人非常讨厌使用这种方式的代码。一般来说,在阅读代码时,看到方法调用,一般认为传递了XX参数,获取到了XX返回值,而不是在方法内部把参数神不知鬼不觉的改变掉了,这样极大的增加了维护和代码理解成本。阅读这段代码的人为了了解参数到底发生了什么样的改变,甚至必须深入到方式的内部逻辑,而不是关注方法输入和方法输出。
public int test3(int[] arr, Result result) {
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
if (arr[i] < min) {
min = arr[i];
}
}
result.setMax(max);
result.setMin(min);
int total = arr.length;
return total;
}
方法六:使用枚举
使用Map作为方法返回值时,有一个最大的缺点就是key值是一些String类型的魔法值,消除魔法值的常见办法是定义一些常量或者枚举。在这里,可以考虑使用枚举替代String作为Key值。
这种方法的好处:使用枚举可以清晰的定义Key值,消除key魔法值的存在,提高了代码的可读性以及可维护性。
缺点:1.需要定义一个额外的枚举类。增加了一些代码量。2.在方法中还是需要显式的创建一个HashMap比较麻烦。而且泛型要指定,如果不规范的写代码,直接Raw使用HashMap,那依然会存在硬编码问题。
public void test3(){
int a = 10, b = 7;
Map<ResType, Integer> resMap = compute3(a, b);
// 取值时使用枚举常量作为Key
System.out.println("两数之和:" + resMap.get(ResType.SUM));
System.out.println("两数之差:" + resMap.get(ResType.DIFF));
}
private Map<ResType,Integer> compute3(int a,int b){
HashMap<ResType, Integer> hashMap = new HashMap<>();
// Key值使用枚举常量替代
hashMap.put(ResType.DIFF, a - b);
hashMap.put(ResType.SUM, a + b);
return hashMap;
}
// 枚举Key常量
enum ResType{
SUM,
DIFF
}
方法七:使用commons-lang3工具包下的Pair或Triple类
这里需要在项目中引入apache的commons-lang3工具包的依赖。
这个工具包提供了很多Java库无法提供的辅助工具类。如果熟练使用该包下的工具类,将节省我们的开发时间,避免重复实现,减少出错的可能,提高开发的代码质量。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
Pair的意思是成双、成对的意思,也就是说用来在有两个值需要作为返回值同时返回的情况下。而Triple是三重、三合一的意思,是用来在有三个值需要作为返回值同时返回的情况下的。
简单说,就是Pair返两值,Triple返三值。
这个方法的好处是:使用起来非常简单。当需要两个返回值时,直接放入Pair,取值时使用Pair提供的getLeft()和getRight()方法即可。
缺点是:Pair或Triple中承载的返回值的含义,也不是能很直观的看懂。需要有一些辅助注释说明。
Pair<Integer, Integer> pair = new ImmutablePair<>(1, 2);
System.out.println(pair.getLeft());
System.out.println(pair.getRight());
Triple<String, String, String> triple =
new ImmutableTriple<>("我是第一个结果",
"我是第二个结果",
"我是第三个结果");
System.out.println(triple.getLeft());
System.out.println(triple.getMiddle());
System.out.println(triple.getRight());
以上就是一些在开发过程中如果碰到封装的方法中需要有多个值返回时,可以用到的一些方法。
每个方法都各有其优缺点,在使用过程中视情况使用即可。
如有更好的方法解决这个问题,欢迎在留言区留下你的宝贵意见,一起讨论。
参考资料列表: