是否可以在 Java 中动态构建多维数组?

Posted

技术标签:

【中文标题】是否可以在 Java 中动态构建多维数组?【英文标题】:Is it possible to dynamically build a multi-dimensional array in Java? 【发布时间】:2011-03-07 11:29:38 【问题描述】:

假设我们有 Java 代码:

Object arr = Array.newInstance(Array.class, 5);

这会运行吗?作为进一步说明,如果我们要尝试这样的事情会怎样:

Object arr1 = Array.newInstance(Array.class, 2);
Object arr2 = Array.newInstance(String.class, 4);
Object arr3 = Array.newInstance(String.class, 4);
Array.set(arr1, 0, arr2);
Array.set(arr1, 1, arr3);

那么 arr1 将是一个二维数组,相当于:

String[2][4] arr1;

这个怎么样:如果我们直到运行时才知道这个数组的维度怎么办?

编辑:如果这有帮助(我相信它会......)我们正在尝试从表单的字符串中解析一个未知维度的数组

[value1, value2, ...]

[ [value11, value12, ...] [value21, value22, ...] ...]

等等

Edit2:如果像我这样愚蠢的人尝试这种垃圾,这里有一个至少可以编译和运行的版本。逻辑是否合理完全是另一个问题......

Object arr1 = Array.newInstance(Object.class, x);
Object arr11 = Array.newInstance(Object.class, y);
Object arr12 = Array.newInstance(Object.class, y);
...
Object arr1x = Array.newInstance(Object.class, y);
Array.set(arr1, 0, arr11);
Array.set(arr1, 1, arr12);
...
Array.set(arr1, x-1, arr1x);

等等。它必须是一个巨大的嵌套对象数组

【问题讨论】:

这就是集合的用途...看看 ArrayList 好的,有效点。如果我们需要以本机数组形式而不是 ArrayList 的形式使用它怎么办。如果是多维ArrayList,toArray会处理嵌套的List吗? 不,它会给你一个 ArrayLists 数组。 拥有一个原始数组(String[][]):List.toArray 方法,请参阅我的答案。 刚刚从this 了解到,您可以创建一个Object[] 数组,它可以容纳数组,因为数组是对象。这意味着您可以在没有显式数组维度类型的情况下任意嵌套数组 【参考方案1】:

有效的 Java 项目 #(我不记得):了解并使用库。

您可以使用List 并使用toArray 方法:

List<String[]> twoDimension = new ArrayList<String[]>();

要将其转换为您将使用的数组:

String [][] theArray = twoDimension.toArray( new String[twoDimension.size()][] );

诀窍是,外部数组被声明为保存String[](字符串数组),而后者又可以使用另一个List&lt;String&gt; 动态创建,或者,如果您正在使用String.split 方法解析字符串。

演示

专注于数组的动态创建而不是解析,这里有一个例子,它与String.split结合使用是如何工作的

// and array which contains N elements of M size
String input = "[[1],[2,3],[4,5,6,7],[8,9,10,11,12,13]]";

// Declare your dynamic array
List<String[]> multiDimArray = new ArrayList<String[]>();

// split where ],[ is found, just ignore the leading [[ and the trailing ]]
String [] parts = input.replaceAll("\\[\\[|\\]\\]","") 
                       .split("\\],\\[");

// now split by comma and add it to the list
for( String s : parts )
    multiDimArray.add(  s.split(",") ) ;


String [][] result = multiDimArray.toArray( new String[multiDimArray.size()][]);

那里。现在你的result 是一个二维动态创建的数组,包含:[[1], [2, 3], [4, 5, 6, 7], [8, 9, 10, 11, 12, 13]],正如预期的那样。

这是一个complete running demo,它还添加了更多的正则表达式,以消除空格。

其他情况我让你处理。

【讨论】:

但是如果输入是 [[[1]]],这种方法是否会适当地创建 String[][][]? 当然。原理是一样的;创建一个 ArrayList 并将其放入另一个,解析当然会有所不同。更进一步,如果内部数组的数量未知,那么将 ArrayList 作为数据结构会更好(如果不是唯一的方法),因为您将无法定义类型你的数组(没有 String[] 或 String[]?)。要定义结果的类型,您必须指定内部数组的数量,如 String[][][][] ( 4 )【参考方案2】:

所以我遇到了这个问题,代码是从具有可变数量变量的多项式中提取系数。所以用户可能 想要两个变量中的多项式的系数数组3 x^2 + 2 x y,或者它可能是一个包含三个变量的数组。理想情况下,您需要一个用户可以轻松查询的多维数组,以便可以转换为 整数[]、整数[][]等

这基本上使用与 jdmichal 的答案相同的技术,使用 Array.newInstance(obj.getClass(), size) 方法。对于多维数组,obj 可以是少一维的数组。

随机创建元素的示例代码

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Random;

public class MultiDimArray 
    static Random rand = new Random();

    /**
     * Create an multi-dimensional array 
     * @param depth number of dimensions
     * @return
     */
    static Object buildArray(int depth) 
        if(depth ==1)  // For 1D case just use a normal array
            int size = rand.nextInt(3)+1;
            Integer[] res = new Integer[size];
            for(int i=0;i<size;++i) 
                res[i] = new Integer(i);
            
            return res;
        
        // 2 or more dimensions, using recursion 
        int size = rand.nextInt(3)+1;
        // Need to get first items so can find its class
        Object ele0 = buildArray(depth-1);
        // create array of correct type
        Object res = Array.newInstance(ele0.getClass(), size);
        Array.set(res, 0, ele0);
        for(int i=1;i<size;++i) 
            Array.set(res, i, buildArray(depth-1));
        
        return res;
    

    public static void main(String[] args) 
        Integer[] oneD = (Integer[])  buildArray(1);
        System.out.println(Arrays.deepToString(oneD));

        Integer[][] twoD = (Integer[][])  buildArray(2);
        System.out.println(Arrays.deepToString(twoD));

        Integer[][][] threeD = (Integer[][][])  buildArray(3);
        System.out.println(Arrays.deepToString(threeD));
    

【讨论】:

【参考方案3】:

在java中实际上是可以做到的。 (不得不说,我有点惊讶。)

免责声明;除了作为这个问题的答案之外,我从不想在其他任何地方看到这段代码。我强烈建议您使用Lists。

import java.lang.reflect.Array;
import java.util.*;

public class Test 

    public static int[] tail(int[] arr) 
        return Arrays.copyOfRange(arr, 1, arr.length);
    

    public static void setValue(Object array, String value, int... indecies) 
        if (indecies.length == 1)
            ((String[]) array)[indecies[0]] = value;
        else
            setValue(Array.get(array, indecies[0]), value, tail(indecies));
    

    public static void fillWithSomeValues(Object array, String v, int... sizes) 
        for (int i = 0; i < sizes[0]; i++)
            if (sizes.length == 1)
                ((String[]) array)[i] = v + i;
            else
                fillWithSomeValues(Array.get(array, i), v + i, tail(sizes));
    

    public static void main(String[] args) 

        // Randomly choose number of dimensions (1, 2 or 3) at runtime.
        Random r = new Random();
        int dims = 1 + r.nextInt(3);

        // Randomly choose array lengths (1, 2 or 3) at runtime.
        int[] sizes = new int[dims];
        for (int i = 0; i < sizes.length; i++)
            sizes[i] = 1 + r.nextInt(3);

        // Create array
        System.out.println("Creating array with dimensions / sizes: " +
                Arrays.toString(sizes).replaceAll(", ", "]["));
        Object multiDimArray = Array.newInstance(String.class, sizes);

        // Fill with some 
        fillWithSomeValues(multiDimArray, "pos ", sizes);

        System.out.println(Arrays.deepToString((Object[]) multiDimArray));


    

示例输出:

Creating array with dimensions / sizes: [2][3][2]
[[[pos 000, pos 001], [pos 010, pos 011], [pos 020, pos 021]],
 [[pos 100, pos 101], [pos 110, pos 111], [pos 120, pos 121]]]

【讨论】:

所以,虽然我标记的最后一个答案在精神上是正确的,但我发现很难与一段真实的、经过测试的代码争论。太棒了 我不知道Array.newInstance。如果您没有在问题中提到该方法,我可能会说这是不可能的......所以这是共同努力;-) 我也没有,我在查看 Array API 并祈祷内置 :P 时发现它:P 另外,nerd-snipe:现在在 C 中执行此操作:D 好吧,我不是 C 专家,但我相信这很简单,因为 arr[3][5] 等同于 C 中的 arr[15] :-) 太棒了,真是个骇人听闻的工作!让我们祈祷没有人必须使用它。 +1【参考方案4】:

因此您可以将多个维度传递给Array.newInstance,但这会强制每个维度具有固定长度。如果没问题,你可以使用这个:

// We already know from scanning the input that we need a 2 x 4 array.
// Obviously this array would be created some other way. Probably through
// a List.toArray operation.
final int[] dimensions = new int[2];
dimensions[0] = 2;
dimensions[1] = 4;

// Create the array, giving the dimensions as the second input.
Object array = Array.newInstance(String.class, dimensions);

// At this point, array is a String[2][4].
// It looks like this, when the first dimension is output:
// [[Ljava.lang.String;@3e25a5, [Ljava.lang.String;@19821f]
//
// The second dimensions look like this:
// [null, null, null, null]

另一种选择是从底部开始构建它们,使用上一级数组的getClass 作为下一级的输入。以下代码运行并生成由节点定义的锯齿状数组:

import java.lang.reflect.Array;

public class DynamicArrayTest

    private static class Node
    
        public java.util.List<Node> children = new java.util.LinkedList<Node>();
        public int length = 0;
    

    public static void main(String[] args)
    
        Node node1 = new Node();
        node1.length = 1;

        Node node2 = new Node();
        node2.length = 2;

        Node node3 = new Node();
        node3.length = 3;

        Node node4 = new Node();
        node4.children.add(node1);
        node4.children.add(node2);

        Node node5 = new Node();
        node5.children.add(node3);

        Node node6 = new Node();
        node6.children.add(node4);
        node6.children.add(node5);

        Object array = createArray(String.class, node6);
        outputArray(array); System.out.println();
    

    private static Object createArray(Class<?> type, Node root)
    
        if (root.length != 0)
        
            return Array.newInstance(type, root.length);
        
        else
        
            java.util.List<Object> children = new java.util.ArrayList<Object>(root.children.size());
            for(Node child : root.children)
            
                children.add(createArray(type, child));
            

            Object array = Array.newInstance(children.get(0).getClass(), children.size());
            for(int i = 0; i < Array.getLength(array); ++i)
            
                Array.set(array, i, children.get(i));
            

            return array;
        
    

    private static void outputArray(Object array)
    
        System.out.print("[ ");
        for(int i = 0; i < Array.getLength(array); ++i)
        
            Object element = Array.get(array, i);
            if (element != null && element.getClass().isArray())
                outputArray(element);
            else
                System.out.print(element);

            System.out.print(", ");
        
        System.out.print("]");
    

【讨论】:

// 此时,数组是一个 String[2][4]。 -- 当然,它的类型是 String[][] 但它等于 空,空 。 这是可验证的错误。我刚刚运行了代码(用int[0] 而不是dimension[0] 来修正我的错误)输出值,我得到[[Ljava.lang.String;@3e25a5, [Ljava.lang.String;@19821f] 作为输出,显示了一个由两个字符串数组组成的数组。正如预期的那样,两个较低的数组中的每一个都被初始化为[null, null, null, null] 哦,你说得对!感谢您指出了这一点。我会更新我的帖子。【参考方案5】:

数组在 java 中是类型安全的——适用于简单数组和“多维”数组——即数组的数组。

如果嵌套的深度在运行时是可变的,那么你能做的最好的事情就是使用一个对应于已知最小嵌套深度(大概为 1)的数组。然后这个数组中的元素要么是简单元素,要么如果需要进一步嵌套,另一个数组。 Object[] 数组将允许您这样做,因为嵌套数组本身也被视为对象,因此适合类型系统。

如果嵌套是完全规则的,那么你可以抢占这个规则并创建一个合适的多维数组,使用Array.newInstance(String.class, dimension1, dimension2, ...),如果嵌套是不规则的,你最好使用嵌套列表,它允许“锯齿状”结构和动态大小。你可以有一个参差不齐的结构,以泛型为代价。如果结构是锯齿状的,则不能使用泛型,因为某些元素可能是简单的项目,而其他元素可能是进一步嵌套的列表。

【讨论】:

这很好用,给定一个常规数组,甚至有可能将这些表示多维字符串数组的对象转换成任何乱七八糟的字符串[][][][][]...[ ]秒。谢谢! 如果嵌套的深度在运行时是可变的,那么你能做的最好的就是使用一个对应于已知最小嵌套深度(大概为 1)的数组。 - - 不,你实际上可以解决它。看我的回答。 @aioobe - 你的回答和我说的一样。没有使用类型安全,而是在每种情况下都假定一个基本对象数组,并且您使用的是固定嵌套(没有锯齿状数组),因此可以安全地假定最后一层是字符串数组。 好的,不确定您的意思,但不涉及对象数组。它是 String 数组、String[] 数组和 String[][] 数组等等,绝不是 Object[]。【参考方案6】:

好的,如果您不确定数组的尺寸,那么下面的方法将不起作用。但是,如果您知道尺寸,请不要使用反射。执行以下操作:

您可以比这更容易地动态构建二维数组。

int x = //some value
int y = //some other value

String[][] arr = new String[x][y];

这将通过y 2d 数组“动态地”创建一个x

【讨论】:

检查我的编辑,这对于未知的数量可能是不可能的 我认为困难的部分是使维度动态化,而不是每个维度中的项目数。 您仍然必须初始化子阵列 AFAIK。它基本上是数组数组:) @Peter,当场。我们不应该假设维度的数量或任何维度的大小 对,维数是runtime确定的。【参考方案7】:

进一步说明,如果我们尝试这样的事情会怎样:

Object arr1 = Array.newInstance(Array.class, 2);
Object arr2 = Array.newInstance(String.class, 4);
Object arr3 = Array.newInstance(String.class, 4);
Array.set(arr1, 0, arr2);
...

不,您不能像这样设置String[] 值。你遇到了

Exception in thread "main" java.lang.IllegalArgumentException: array element type mismatch
at java.lang.reflect.Array.set(Native Method)
at Test.main(Test.java:12)

【讨论】:

是的,我没有费心测试那部分。我只是希望明白这一点:P

以上是关于是否可以在 Java 中动态构建多维数组?的主要内容,如果未能解决你的问题,请参考以下文章

Java基础 | 深入理解多维数组

动态多维数组c#

在 Wordpress meta_query 中使用动态多维数组

PHP:如何修改动态多维数组

PHP如何动态为多维数组的子数组分别添加元素

Java中如何获取多维数组的长度