一、背景

技术交流群里有同学提了一个看似基础但挺有意思的问题。

问题描述

一个对象是一个未知的数组类型,可能是 short 二维数组,可能是 int 的三维数组等。

诉求

  • 想要遍历修改(获取)它的值
  • 不想写太多 if else (该同学的最初方案是通过 instance of 枚举出所有类型,通过 if else 来写代码)

Java 动态判断数组维数并取值_List


群里 程序员 DMZ 给出了很专业的建议,使用策略模式或者采用递归的方式取值。

我的解法也与之类似,本文给出相对具体的参考代码(因为虽然很多同学也能考虑到递归,但递归时如何取值并不太会;如果用策略模式该怎么写也不太会)。

二、推荐方案

2.1 采用递归

这里主要演示传入一维或者 N 维数组,可以获取到每个元素,实际开发中如何处理取出的元素,可以根据示例修改变通即可。

2.1.1 只需要取每个元素

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

public class ArrayDemo2 {

public static void main(String[] args) {

// 测试三维数组
System.out.println(" ------- 测试 3 维数组 ------- ");
int[][][] data = new int[][][]{{{1},{2}},{{3,4}}};

Object obj = data;

List<Float> result= test(obj);

System.out.println(" ------- 测试 2 维数组 ------- ");
// 测试二维数组

int[][] data2 = new int[][]{{1,2},{3,4,5}};
result= test(data2);

System.out.println(" ------- 测试 1 维数组 ------- ");
// 测试一维数组

int[] data3 = new int[]{1,2,3};
result= test(data3);
}


// 开发中并不需要使用方感知到默认的 turn 为1 ,因此再次封装,如果不需要 turn 就不需要再次封账
private static List<Float> test(Object array){
return test(array, 1);
}
/**
* 伪代码,result 的逻辑根据业务需要来写,这里就不处理了
* turn 是为了记录维数,默认1 维,递归一次 +1,如果不需要知道当前是几维数组 turn 可以去掉
*/
private static List<Float> test(Object array, int turn){
// 伪代码模拟结果
List<Float> result = new ArrayList<>();

if(array.getClass().isArray()){
for(int i = 0; i< Array.getLength(array); ++i) {
Object obj = Array.get(array, i);
if(obj.getClass().isArray()){
test(obj,turn+1);
}else{
System.out.println("值:"+obj+",几维数组:"+turn);
// result.add(// 计算结果放到 result里);
}
}
}
return result;
}
}

打印结果

------- 测试 3 维数组 ------- 
值:1,几维数组:3
值:2,几维数组:3
值:3,几维数组:3
值:4,几维数组:3
------- 测试 2 维数组 -------
值:1,几维数组:2
值:2,几维数组:2
值:3,几维数组:2
值:4,几维数组:2
值:5,几维数组:2
------- 测试 1 维数组 -------
值:1,几维数组:1
值:2,几维数组:1
值:3,几维数组:1

可以看到,符合预期。

2.1.2 需要感知维数,并转换为对应维数的“数组”

如果返回值需要“复原”数组维数,可以考虑使用 List 变通实现(理论上等价),相对简单。

如果非要返回值是数组类型
(1)可以采用类似的思路对方法进行改造,可以考虑分两步,第一步根据Object 通过反射(​​​Array.getLength​​​ 和 ​​Array.get​​​) 获取维数和每一维的 length, 第二步采用类似的方式转换并放入数据,即可。
(2)定义入参对象将 List 和 turn 作为属性,最后根据这个入参,构造 List 转数组的工具还原为数组类型。

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

public class ArrayDemo2 {

public static void main(String[] args) {

List result = new ArrayList();
// 测试三维数组
System.out.println(" ------- 测试 3 维数组 ------- ");
int[][][] data = new int[][][]{{{1},{2}},{{3,4}}};

Object obj = data;

test(obj,result);
System.out.println(result);

System.out.println(" ------- 测试 2 维数组 ------- ");
// 测试二维数组
List result2 = new ArrayList();
int[][] data2 = new int[][]{{1,2},{3,4,5}};
test(data2,result2);
System.out.println(result2);

System.out.println(" ------- 测试 1 维数组 ------- ");
// 测试二维数组
List result3 = new ArrayList();
int[] data3 = new int[]{1,2,3};
test(data3,result3);
System.out.println(result3);
}


private static void test(Object array, List result){
test(array, 1, result);
}
/**
* 伪代码,result 的逻辑根据业务需要来写,这里就不处理了
* turn 是为了记录维数,默认1 维,递归一次 +1
*/
private static void test(Object array, Integer turn,List result){

if(array.getClass().isArray()){
for(int i = 0; i< Array.getLength(array); ++i) {
Object obj = Array.get(array, i);
if(obj.getClass().isArray()){
List innerResult = new ArrayList<>();
result.add(innerResult);
test(obj,turn+1, innerResult);
}else{
System.out.println("值:"+obj+",几维数组:"+turn);
// result.add(// 计算结果放到 result里);
result.add("模拟"+obj+"转换结果");
}
}
}
}
}

打印结果:

------- 测试 3 维数组 ------- 
值:1,几维数组:3
值:2,几维数组:3
值:3,几维数组:3
值:4,几维数组:3
[[[模拟1转换结果], [模拟2转换结果]], [[模拟3转换结果, 模拟4转换结果]]]
------- 测试 2 维数组 -------
值:1,几维数组:2
值:2,几维数组:2
值:3,几维数组:2
值:4,几维数组:2
值:5,几维数组:2
[[模拟1转换结果, 模拟2转换结果], [模拟3转换结果, 模拟4转换结果, 模拟5转换结果]]
------- 测试 1 维数组 -------
值:1,几维数组:1
值:2,几维数组:1
值:3,几维数组:1
[模拟1转换结果, 模拟2转换结果, 模拟3转换结果]

2.2 使用策略模式

这个问题不推荐使用策略模式,但为了演示为了更通用,提供了策略模式的解决示例。
假设我们遇到类似的需求,不会写递归或者无法写递归,或者数组的类型非常少,我们可以使用策略模式或者责任链模式来破解 If else 的问题。

定义策略接口:

public interface ArrayStrategy {

/**
* 当前策略是否可以处理该对象
*/
Class type();

/**
* 执行处理并返回结果
*/
Object handle(Object object);
}

int[] 类型的处理策略(其他类型的自行编写):

/**
* int [] 处理策略
*/
public class IntArrayStrategy implements ArrayStrategy{

public Class type(){
return int[].class;
}

public Object handle(Object object) {
int[] array = (int[])object;
// 处理逻辑
for (int j : array) {
System.out.println("int 数组,元素:" + j);
}

// 这里是伪代码,返回空数组
return new float[array.length];
}
}

示例代码:

public class ArrayDemo {
private static Map<Class, ArrayStrategy > arrayStrategies = new HashMap<>(16);

static {
// int 一维数组
ArrayStrategy strategy = new IntArrayStrategy();
arrayStrategies.put(strategy.type(),strategy);
// short 一维数组
strategy = new ShortArrayStrategy();
arrayStrategies.put(strategy.type(),strategy);

// int 二维数组等,
}

public static void main(String[] args) {
int[] data1 = new int[]{1, 2};
// 模拟传入 object 类型
Object obj = data1;
ArrayStrategy strategy = arrayStrategies.get( obj.getClass());
// 需要判断 strategy 是否为空
Object result = strategy.handle(obj);
System.out.println(result);
}
}

构造 Map 映射这里,也可以定义 ​​List<ArrayStrategy>​​​ 将支持策略放进去, for 循环构造 ​​Map​​​; 也可以将策略定义为 Spring 的 ​​Bean​​​ ,通过后置处理器构造类型到 ​​Bean​​​ 的映射 ​​Map​​​。
参考​​《巧用 Spring 自动注入实现策略模式升级版》​​。

运行的结果:

int 数组,元素:1
int 数组,元素:2
[F@3f99bd52

这样就可以将不同类型的特有处理逻辑内聚到对应的策略中,如果需要支持新的数组类型(如要支持 ​​double[][] ​​),加一个新的策略即可。

三、总结

日常开发中,遇到觉得“不太对劲” 、“不太优雅” 的地方(其实只要不符合高内聚、弱耦合的场景都有问题),要主动思考如何解决,可以和其他同学交流下,努力写出更简洁和优雅的代码。
日常开发中,多了解 JDK 中反射相关的类,多了解一些知名的三方工具类,很多功能实现起来就会容易一些。
要了解常见的设计模式,很多问题优先考虑是否可以使用某种设计模式或者某种设计模式的变种来解决问题。

总之,写代码是良心活,工作中写代码是项目时间和代码质量之间权衡的结果。
对代码没太大追求的同学有一万种理由不去写出更好的代码。想写出好代码的同学会在项目工期紧张的情况下,尽量写出更简洁、优雅的、健壮、拓展性更强代码。


创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。

Java 动态判断数组维数并取值_java_02