【问题标题】:Creating Arrays of Generic Types in Java在 Java 中创建泛型类型的数组
【发布时间】:2011-07-22 15:19:07
【问题描述】:

所以我知道您不能“轻松”在 Java 中创建泛型类型的数组(但您可以创建集合)。我最近遇到了一种情况,我需要一个二维对象数组(即通用)。这是一个“粗略”的想法(不完整,但我试图尽可能简短):

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

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

  abstract class Bar extends Foo {
    ...
  }
}

所以在代码的某个地方我需要一个这样的数组:

foo = new Foo[width][height];

(我们知道这不可能发生)。但是,我尝试了这个:

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

尽管我不得不禁止警告,但编译器接受了它。我想我的问题是“这会不会把我扼杀在萌芽状态?成员“foo”从不暴露在外面(尽管 Foo 和 Bar 类型是)。我知道它很丑,但它确实有效并且使我不必创建一些其他“psedu-kludge”,这可能会导致覆盖“外部”类的类更加头痛。提前致谢!


这可能会使事情更容易可视化

这更接近于我实际在做的事情;当然要意识到 Map 类内部有许多支持方法和其他逻辑,我为了简洁而省略了。

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

【问题讨论】:

  • 你为什么不能new Foo[width][height]?你不能这样做new T[width][height]
  • 我认为这与外部类是通用的(而不是内部类)有很大关系。但我可能是错的。我得写个测试看看。
  • 确实,是的。事实上,外部类是通用的,这阻止了只做新的 Foo[width][height]。我也许可以重构 Foo 使其不必是内部类,但它会出现问题并且可能会消耗更多内存。
  • 为什么不能用obj.width、obj.height定义一个新的对象,然后把obj的数组或者链表拼起来?你知道什么是链接列表吗?
  • 我当然知道链表是​​什么,但是与简单的二维数组相比,这样做的开销有点过大。我试图想出一个与我正在做的事情更相关的更好的例子(一个 2D 单元格地图,其中单元格可以包含对象列表)。我将更新我的问题以显示“更好”的示例。

标签: java arrays generics


【解决方案1】:

您正在做的事情是安全的,因为您正在控制未公开的map。您可能应该将其设为私有且不受保护,否则扩展类可能会错误地操纵它。您可以通过强制转换为运行时检查来消除编译器警告,如下所示:

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

然后,如果稍后代码可能以不兼容的方式更改,编译器警告会屏蔽掉,它至少会在构造 map 时提前中断并出现运行时异常。当然请记住,泛型只是在编译时被删除。

【讨论】:

  • 谢谢!扩展类的“​​错误操作”是什么?
  • 也许我应该说扩展类的“​​意外操作”。无论哪种方式,但是我现在没有一个示例会引起您的极大关注,只是扩展类可以访问map[][] 并且可以更改任何单元格。我唯一的意思是更喜欢private,除非你有理由使用protected
  • 这里不需要使用Array.newInstance。看我的回答;你可以直接使用new。您只需要限定 BaseCell 以指示实际的原始(未使用 T 参数化)类
【解决方案2】:

我认为在这种情况下最好的选择是简单地使用Object[]

在泛型之前,数组类型X[] 其中X!=Object 是API 中的重要类型。我们无法使用无法指定组件类型的ListX[] 是最有用的。现在有了泛型,我们最好在 API 中使用 List&lt;X&gt; 而不是 X[]。 (JDK 在其 API 中仍然使用X[],甚至是更新的 API,可能是因为他们希望避免包之间的依赖关系)

那是 API。 在实现细节中,我们仍然需要数组,它们是不可或缺的。但是,Object[] 就足够了。实际上,如果 Java 只保留 Object[] 并删除所有其他引用数组类型,那么不会发生严重的坏事。我们所需要的只是编写程序的对象数组。

当然,当X[] 可用且易于使用时,克制自己不使用它是愚蠢的。

X 不是一个简单的类,而X[] 很难使用,浪费我们的时间让它工作时,这同样是愚蠢的。没有什么好事是真正实现的。在这种情况下,只需使用Object[],我们就会为自己省去很多麻烦。

所以结论:对外,不接受数组,不给数组;在内部,如果没有汗水,请使用X[],否则请使用Object[]并开心。

【讨论】:

    【解决方案3】:

    【讨论】:

      【解决方案4】:

      这个Foo[][] 的问题在于它实际上被定义为Outer&lt;T&gt;.Foo[][],但是VM 无法控制(使用ArrayStoreExceptions)你没有将Outer&lt;S&gt;.Foo 对象放入其中 -这就是您收到警告的原因。 (这就是为什么泛型数组通常不被认为是类型安全的。)

      如果你确定不是这种情况,那么你就没有问题。

      【讨论】:

        【解决方案5】:

        这将允许它工作(以及未经检查的强制转换,这在这种情况下是正常的,因为您想要一个变量是泛型类型的数组)。解决方案是意识到只有Foo 指的是参数化类Outer&lt;T&gt;.Foo,因为您在Outer&lt;T&gt; 的范围内;并且要获得实际的原始类,您需要将其明确限定为Outer.Foo

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

        【讨论】:

          猜你喜欢
          • 2011-10-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-02-23
          • 1970-01-01
          • 1970-01-01
          • 2016-06-20
          相关资源
          最近更新 更多