26

Suppose we have the Java code:

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

Would that run? As a further note, what if we were to try something like this:

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);

Would arr1 then be a 2D array equivalent to:

String[2][4] arr1;

How about this: what if we don't know the dimensions of this array until runtime?

Edit: if this helps (I'm sure it would...) we're trying to parse an array of unknown dimensions from a String of the form

[value1, value2, ...]

or

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

And so on

Edit2: In case someone as stupid as I am tries this junk, here's a version that at least compiles and runs. Whether or not the logic is sound is another question entirely...

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);

And so on. It just has to be a giant nested array of Objects

Jordan
  • 263
  • 1
  • 3
  • 5
  • 4
    that's what Collections are for...look at ArrayList – Srinivas Reddy Thatiparthy Jun 23 '10 at 18:43
  • 3
    Okay, valid point. What if we need to have this in a native array form and not an ArrayList. If it is a multi-dimension ArrayList, will toArray handle the nested Lists? – Jordan Jun 23 '10 at 18:45
  • No, it will give you an array of ArrayLists. – aioobe Jun 23 '10 at 18:54
  • to have a primitive array ( `String[][]` ) you use : List.toArray method, see my answer. – OscarRyz Jun 23 '10 at 22:01
  • Just figured out from [this](https://stackoverflow.com/a/74780/2716305) that you can create an `Object[]` array, which can hold arrays, since arrays are objects. This means you can arbitrarily nest arrays without explicit array Dimension typing – Lightfire228 Sep 14 '17 at 04:52

7 Answers7

22

It is actually possible to do in java. (I'm a bit surprised I must say.)

Disclaimer; I never ever want to see this code anywhere else than as an answer to this question. I strongly encourage you to use 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));


    }
}

Example Output:

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]]]
aioobe
  • 413,195
  • 112
  • 811
  • 826
  • So, while the last answer I had tagged was spiritually correct, I find it very hard to argue with a real, tested piece of code. Bravo – Jordan Jun 23 '10 at 20:06
  • I had no idea about `Array.newInstance`. If you hadn't mentioned that method in the question, I had probably said it was impossible... So it was a joint effort ;-) – aioobe Jun 23 '10 at 20:16
  • Neither did I, I found it while looking through the Array API praying for a built-in :P Also, nerd-snipe: now do this in C :D – Jordan Jun 23 '10 at 20:19
  • Well I'm no C expert, but I believe it is trivial, since arr[3][5] is equivalent to arr[15] in C :-) – aioobe Jun 23 '10 at 20:26
  • 3
    Bravo, what a hack job! Let's pray no one ever has to use this. +1 – dimo414 Jun 23 '10 at 20:38
  • Also, completely agree that this is vastly easier in C/C++. Just create an array to hold all the leaf elements, then pretend it's a multi-dimensional array. If it's a jagged array, you will have to do something similar to what aioobe does here, using pointers and `new` over `Array.newInstance`. – jdmichal Jun 23 '10 at 20:50
  • +1 While clever, there is no need ( at least in OP scenario ) to use reflection when `java.util` may do the work. See my answer. I UV for the effort anyway. – OscarRyz Jun 23 '10 at 22:07
4

Arrays are type-safe in java - that applies to simple arrays and "multi-dimensional" arrays - i.e. arrays of arrays.

If the depth of nesting is variable at runtime, then the best you can do is to use an array that corresponds to the known minimum nesting depth (presumably 1.) The elements in this array with then either be simple elements, or if further nesting is required, another array. An Object[] array will allow you to do this, since nested arrays themselves are also considered Objects, and so fit within the type system.

If the nesting is completely regular, then you can preempt this regularity and create an appropriate multimensional array, using Array.newInstance(String.class, dimension1, dimension2, ...), If nesting is irregular, you will be better off using nested lists, which allow for a "jagged" structure and dynamic sizing. You can have a jagged structure, at the expence of generics. Generics cannot be used if the structure is jagged since some elements may be simple items while other elements may be further nested lists.

mdma
  • 56,943
  • 12
  • 94
  • 128
  • This works perfectly, and given a regular array there's even a possibility of converting these Objects representing multi-dimensional String arrays into any mess of String[][][][][]...[]s. Thanks! – Jordan Jun 23 '10 at 19:17
  • *If the depth of nesting is variable at runtime, then the best you can do is to use an array that corresponds to the known minimum nesting depth (presumably 1.)* -- No, you can actually solve it. See my answer. – aioobe Jun 23 '10 at 19:43
  • @aioobe - your answer is equivalent to what I am saying. The type safety is not being used and instead a basic object array is assumed at each case, and that you are using a fixed nesting (no jagged arrays) so that the last level can be safely assumed a string array. – mdma Jun 23 '10 at 21:18
  • ok, not sure what you're meaning, but no object-arrays are involved. It's arrays of String, arrays of String[] and arrays of String[][] and so on, never an Object[]. – aioobe Jun 23 '10 at 21:30
3

So you can pass multiple dimensions to Array.newInstance, but that forces a fixed length for each dimension. If that's OK, you can use this:

// 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]

The other option would be to build them up from the bottom, using getClass on the previous level of array as the input for the next level. The following code runs and produces a jagged array as defined by the nodes:

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("]");
    }
}
jdmichal
  • 10,984
  • 4
  • 43
  • 42
  • *// At this point, array is a String[2][4].* -- Sure, it will be of type String[][] but it will equal { null, null }. – aioobe Jun 23 '10 at 19:46
  • That is verifiably false. I just ran the code (fixing my mistake with doing `int[0]` instead of `dimension[0]`) outputting the values, and I get `[[Ljava.lang.String;@3e25a5, [Ljava.lang.String;@19821f]` as output, showing an array of two arrays of strings. Each of the two lower arrays was initialized with `[null, null, null, null]`, as expected. – jdmichal Jun 23 '10 at 20:29
  • Oh, you are right indeed! Thanks for pointing that out. I'll update my post. – aioobe Jun 23 '10 at 20:45
1

Ok, if you are unsure of the dimensions of the array, then the following method won't work. However, if you do know the dimensions, do not use reflection. Do the following:

You can dynamically build 2d arrays much easier than that.

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

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

This will 'dynamically' create an x by y 2d array.

jjnguy
  • 136,852
  • 53
  • 295
  • 323
1

As a further note, what if we were to try something like this:

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);
...

No, you can't set an String[] value like that. You run into

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)
aioobe
  • 413,195
  • 112
  • 811
  • 826
0

Effective Java item # (I don't remember ) : Know and use the libraries.

You can use a List and use the toArray method:

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

To convert it to an array you would use:

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

The trick is, the outer array is declared to hold String[] ( string arrays ) which in turn can be dynamically created with another List<String> or, if your're parsing strings with the String.split method.

Demo

Focusing on the dynamic creation of the array and not in the parsing, here's an example on how does it works used in conjunction with 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()][]);

There. Now your result is a two dimensional dynamically created array containing : [[1], [2, 3], [4, 5, 6, 7], [8, 9, 10, 11, 12, 13]] as expected.

Here's a complete running demo, which also adds more regexp to the mix, to eliminate white spaces.

I let you handle the other scenarios.

Koray Tugay
  • 22,894
  • 45
  • 188
  • 319
OscarRyz
  • 196,001
  • 113
  • 385
  • 569
  • But does this approach create String[][][] appropriately if the input is, say, [[[1]]]? – aioobe Jun 24 '10 at 06:30
  • Definitely. The principle is the same; create an ArrayList and put it inside another, the parsing would be different of course. Going further, if the number of inner arrays is unknow, it would be **much** better ( if not the only way ) to have an ArrayList as data structure, because you won't be able to define the type of your array ( there is no String[*] or String[]*? ) . To define the type of the result, you have to specify the number of inner arrays like in String[][][][] ( 4 ) – OscarRyz Jun 24 '10 at 15:23
0

So I came across this question with code to extract coefficients from a polynomial with variable numbers of variables. So a user might want the coefficient array for a polynomial in two variables 3 x^2 + 2 x y or it could be one with three variables. Ideally, you want a multiple dimension array which the user can easily interrogate so can be cast to Integer[], Integer[][] etc.

This basically uses the same technique as jdmichal's answer, using the Array.newInstance(obj.getClass(), size) method. For a multi dimensional arrays obj can be an array of one less dimension.

Sample code with randomly created elements

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));
    }
}
Salix alba
  • 7,536
  • 2
  • 32
  • 38