6

So I know that you cannot "easily" create an array of a generic type in Java (but you can create collections). I recently ran across a situation where I needed a 2 dimensional array of objects (that were Generic). Here's a "rough" idea of what it looked like (not complete but I'm trying to be as brief as possible):

class Outer<T> {
  private Foo[][] foo;

  abstract class Foo extends Blah<T> {
    public List<T> getContents ();
  }

  abstract class Bar extends Foo {
    ...
  }
}

So somewhere in the code I needed an array as such:

foo = new Foo[width][height];

(which we know can't happen). However, I tried this:

foo = (Foo[][])Array.newInstance (Foo.class, new int[]{getWidth (), getHeight ()});

which the compiler accepted though I had to suppress warnings. I guess my question is "Is this going to nip me in the bud somewhere down the line? The member "foo" is never exposed to the outside (though the types Foo and Bar are). I know it's ugly but it definitely works and saved me from having to create some other "psedu-kludge" the would probably have caused classes overriding the "Outer" class more headaches. Thanks in advance!


This might make things easier to visualize

This is closer to what I'm actually doing; realizing of course that there are many support methods and other logic inside of the Map class that I've left out for brevity.

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

    interface Cell<T> {
        public void add (T t);
        public boolean remove (T t);
        public List<T> getAll ();
        public Map<T> getMap ();
    }

    class Map<T> {
        protected   BaseCell    map[][];

        public abstract class BaseCell implements Cell<T> {
            private List<T> contents;

            public BaseCell () {
                this.contents = new ArrayList<T> ();
            }

            public void add (T t) {
                this.contents.add (t);
            }

            public boolean remove (T t) {
                return this.contents.remove (t);
            }

            public List<T> getAll () {
                return this.contents;
            }

            public Map<T> getMap () {
                return Map.this;
            }

            abstract public boolean test ();
        }

        public class SpecialCell extends BaseCell {
            @Override
            public boolean test() {
                return true;
            }
        }

        public class SpecialCell2 extends BaseCell {
            @Override
            public boolean test() {
                return false;
            }
        }

        @SuppressWarnings("unchecked")
        public Map (int width, int height) {
            this.map = (BaseCell[][])Array.newInstance(BaseCell.class, new int[] {width, height});
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    if (Math.random() < .5) {
                        this.map[x][y] = new SpecialCell ();
                    } else {
                        this.map[x][y] = new SpecialCell2 ();
                    }
                }
            }
        }

        public BaseCell getCellAt (int x, int y) {
            return this.map[x][y];
        }
    }

    public class Junk {

        /**
         * @param args
         */
        public static void main(String[] args) {
            class Occupant {
            }

            Map<Occupant> map = new Map<Occupant> (50, 50);
            map.getCellAt(10, 10).add(new Occupant ());

            map.getCellAt(10, 10).getMap ();

            for (int y = 0; y < 50; y++) {
                for (int x = 0; x < 50; x++) {
                    System.out.print (map.getCellAt (x, y).test () ? "1" : "0");
                }
                System.out.println ();
            }
        }
    }
8
  • 4
    Why can't you do new Foo[width][height]? You just can't do new T[width][height] Commented Mar 25, 2011 at 3:13
  • I think it has as much to do with the outer class being Generic (not the inner class). But I could be wrong. I'll have to write a test to see. Commented Mar 25, 2011 at 3:14
  • Indeed, yes. It's the fact that the outer class is generic which is preventing from just doing new Foo[width][height]. I may be able to refactor Foo so it doesn't have to be an inner class, but it will be problematic and probably consume more memory. Commented Mar 25, 2011 at 3:16
  • why cant you define a new object with obj.width, obj.height and then strike up an array or a linked list of the obj? do you know what a link list is? Commented Mar 25, 2011 at 3:34
  • I know what a linked list is, of course, but the overhead in doing that is a bit excessive when compared to a simple 2D array. I tried to come up with a better example more related to what I'm doing (a 2D map of cells where the cells can contain lists of objects). I'm going to update my question showing the "better" example. Commented Mar 25, 2011 at 3:40

5 Answers 5

2

What you're doing is safe because you're controlling the unexposed map. You should probably make it private though and not protected, otherwise extending classes could incorrectly manipulate it. You can get rid of the compiler warning by casting into a runtime check, like this:

this.map = BaseCell[][].class.cast(Array.newInstance(BaseCell.class,
        new int[] { width, height }));

Then if at some later point the code is potentially changed in an incompatible way that the compiler warning would mask out, it'll at least break early with a runtime exception at the construction of your map. Keep in mind of course that Generics are simply erased at compile time.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks! What would be "incorrect manipulation" by an extending class?
Perhaps I should say "unexpected manipulation" by an extending class. Either way however I don't have an example that would cause you much concern right now, its just that the extending class would have access to map[][] and could change any cell. My only point really is to prefer private unless you have a reason to use protected.
you don't need to use Array.newInstance here. see my answer; you can use new directly. you just have to qualify BaseCell to indicate the actual raw (not parameterized with T) class
1

I think the best choice in this situation is to simply use Object[].

Prior to generics, array types X[] where X!=Object are important types in APIs. We couldn't use List which couldn't specify the component type. X[] was the most useful one. Now with generics, we better use List<X> instead of X[] in APIs. (JDK still use X[] in its APIs, even newer APIs, probably because they want to avoid dependencies among their packages)

That's for API. Within implementation details, we still need arrays, they are indispensible. However, Object[] is enough. Actually if Java keeps only Object[] and rid all other reference array types, nothing seriously bad would happen. All we need is arrays of objects to write our programs.

Of course, it would be silly to restraint ourselves from using X[] when it's available and easy to use.

It would be equally silly, when X is not a simple class, and X[] is hard to work with, to waste our time to make it work. Nothing good is really achieved. In such situations, just use Object[] and we'll save ourselves a lot of troubles.

So conclusion: externally, don't accept arrays, and don't give out arrays; internally, use X[] if there's no sweat, otherewise use Object[] and be happy.

Comments

1

Try guava's ObjectArrays.newArray(T[] reference, int length).

Comments

0

The problem with this Foo[][] is that it in fact is defined as a Outer<T>.Foo[][], but the VM can't control (with ArrayStoreExceptions) that you don't put Outer<S>.Foo objects in it - this is why you get the warning. (This is why generic arrays are generally not considered typesafe.)

If you make sure this is not the case, then you have no problem.

Comments

0

This will allow it to work (along with an unchecked cast, which is normal for this situation since you want to have a variable that is the array of a generic type). The solution is realizing that just Foo refers to the parameterized class Outer<T>.Foo, since you are inside the scope of Outer<T>; and that to get the actual raw class, you need to explicitly qualify it as Outer.Foo:

foo = (Foo[][])new Outer.Foo[width][height];

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.