sksw
sksw

Reputation: 11

Making a 2D array of generic lists in Java

So - I want to make a 2D array of generic lists containing some data I am interested in (on a grid of of some set size),

private ArrayList<MyDataStruct>[][] gridData;

When I initialize this array, I go,

gridData = (ArrayList<MyDataStruct>[][])new ArrayList[w][h];
for(int x=0; x<w; x++)
    for(int y=0; y<h; y++){
        gridData[x][y] = (ArrayList<MyDataStruct>)new ArrayList<MyDataStruct>(0);
        //other stuff, i.e. get data from somewhere, etc
    }

and I get a unchecked cast warning. Was curious about why this is not type safe and what I can do to make it type safe (short of switching out of using a 2d array). I understand that it's not very good to mix arrays and generics, but in this case I'm just writing this to run some experiments, so i'm using this instead of say lists of lists lists to save myself some time. Are there any other more elegant approaches that let's me get away with easy to read/write syntax (i.e. no nested lists), but are also type safe?

Thanks!

Upvotes: 0

Views: 3046

Answers (3)

csoler
csoler

Reputation: 838

I was playing around with a similar idea, but instead used nested Lists of Optionals. It seems to provide some benefits as you do not need to check for null and can instead use isEmpty or isPresent for each cell. However, I'm sure for reasons unknown to me, this is probably not a great solution. I thought i'd share anyways.

public class Grid<T> {
    private final int rowCount;
    private final int columnCount;
    private final List<List<Optional<T>>> data;

    public Grid(int rowCount, int columnCount) {
        this.rowCount = rowCount;
        this.columnCount = columnCount;
        data = build();
    }

    private List<List<Optional<T>>> build() {
        return IntStream.range(0, rowCount)
                .mapToObj(rowId -> IntStream.range(0, columnCount)
                        .mapToObj(columnId -> Optional.<T>empty())
                        .collect(Collectors.toList()))
                .collect(Collectors.toList());
    }

    public Optional<T> get(int rowId, int columnId) {
        return data.get(rowId).get(columnId);
    }

    public void set(int rowId, int columnId, T t) {
        data.get(rowId).set(columnId, Optional.ofNullable(t));
    }
}

Upvotes: 0

Amir Arad
Amir Arad

Reputation: 6754

I got here with the same problem (just some experiments, "get things done" approach). Here is a solution that seems to work for me, using reflection.

note that it is 100% agnostic to the type of the cells in the array (can be list, map, anything). perhaps with a little more work it can also be agnostic to the # of dimensions of the array, but I've bigger fish to fry today...

private static <T>  T[][] makeArray(Class<T> componentType, int... dimentions) {
    return (T[][]) Array.newInstance(componentType, dimentions);
}

attached below is a test method. to keep the type of the array generic I had to ugly-cast here. this can be handled in another wrapping method to keep client code nice and clean:

@Test
public void testMakeArray() throws Exception {
    @SuppressWarnings("unchecked")
    List<String>[][] arr = makeArray(List.class, 2, 3);

    Assert.assertEquals(2, arr.length);
    Assert.assertEquals(3, arr[0].length);

    for (int i = 0; i < arr.length; i++) {
        for (int j = 0; j < arr[i].length; j++) {
            arr[i][j] = Arrays.asList("cell", "r="+i, "c="+j);
            System.out.print(arr[i][j] + "\t");
        }
        System.out.println("");
    }
}

it prints:

[cell, r=0, c=0]    [cell, r=0, c=1]    [cell, r=0, c=2]    
[cell, r=1, c=0]    [cell, r=1, c=1]    [cell, r=1, c=2]

Upvotes: 0

Louis Wasserman
Louis Wasserman

Reputation: 198211

The only type-safe option is to use nested lists.

Because of type erasure, you can't do new ArrayList<MyDataStruct>[w][h], because that basically becomes new ArrayList[w][h]...and things get complicated and hairy, fast, because arrays don't behave like generics.

Java considers String[] to be a subclass of Object[], but ArrayList<String> isn't a subclass of ArrayList<Object>. Normally, arrays throw an ArrayStoreException when you try to put an element into an array that doesn't match the array's true type, but because of type erasure, it can't do that when generics are in the picture.

Josh Bloch gives the following example in Effective Java:

// Why generic array creation is illegal - won't compile!
List<String>[] stringLists = new List<String>[1];
List<Integer> intList = Arrays.asList(42);
Object[] objects = stringLists;
objects[0] = intList;
String s = stringLists[0].get(0);

The only way to prevent all this code from being entirely legal in Java is to ban the generic array creation, or at least mark it as unsafe.

That said, IMO, the moderate overhead of typing out List and doing it with generic lists is absolutely worth the type safety.

Upvotes: 2

Related Questions