3

我目前正在用 Java 编写游戏。在这个游戏中,我使用数组作为子弹之类的数据结构。现在我未能使用泛型类型围绕数组编写包装类。我构建了以下示例来演示我的问题。

    public class CoolArray< E extends MyInterface > {
        private final E[] array;
        public int aliveElements;

        public CoolArray( final Class< ? extends MyInterface > clazz, int size ) {
            array = (E[])Array.newInstance( clazz, size );
            for ( int i = 0; i < array.length; i++ ) {
                // i would like to instantiate all array elements
                // to avoid constant object allocation at runtime
                try {
                    array[i] = clazz.newInstance(); // always throws NullPointerException
                } catch ( Exception e ) { } // omitted
            }
            aliveElements= 0;
        }

        public E get( int i ) {
            return array[ i ];
        }
        // rest omitted ...
    }

这应该是我的数组包装类。我在加载关卡时创建此数组。在游戏的更新渲染方法中,我只迭代了aliveElements - 数组的元素。

MyInterface 包含方法存根,例如updaterender ...

现在,每当我发射子弹时,我都会在位置aliveElements的 Element 上调用一些init方法。我这样做是为了避免垃圾收集。

    myCoolArray.get( myCoolArray.aliveElements ).init( ... )

无论我如何尝试实例化数组中的每个元素,这都是由于ClassCastException而失败的部分。

注意:我很清楚在上面的例子中我会得到一个NullPointerException因为我没有实例化元素!那不是问题。

谷歌,一些书籍和其他问题告诉我,泛型类型不能在 Java 中实例化。所以现在我的问题是:有没有办法解决这个问题?

我不想在更新渲染方法中分配新对象,因为我害怕垃圾收集器会不时对帧速率做些什么:)

4

3 回答 3

4

首先:您编写的类几乎只是ArrayList对元素类型有额外的限制。试试你是否可以使用它(或包装它)。即使你最终没有这样做,实现Collection<E>至少 Iterable<E>应该有助于编写可重用的代码。

下一步:你说你解决了NullPointerException但不告诉我们你是如何实例化你的对象的。

是的,有一种方法可以解决不能做的事情new E(),这涉及到一个方法Class<E,幸运的是你已经有了:你可以简单地使用c.newInstance()来填充你的内部数组(如果你的MyInterface实现都有公共的无参数构造函数)。

于 2013-06-05T14:35:41.940 回答
1

我想通了,为什么InstantiationException我打电话时得到array[i] = clazz.newInstance()。那是因为我传入的Class没有默认的Constructor。该类看起来像:

    public class MyClass implements MyInterface {
        SomeField someField;
        public MyClass( SomeField someField ) {
            this.someField = someField;
        }
        // omitted
    }

为了让它运行,我将 MyInterface 更改为abstract class我扩展而不是继承的。出于安全原因,我将ConstructorwithSomeField参数放入其中,Superclass并将默认构造函数设为私有。

然后我更改了用参数调用构造函数的代码来实例化泛型类的代码SomeField

这是该解决方案的所有类的完整代码。因为我在这个问题上工作了很长时间,所以我想我在这里分享所有这些,希望能帮别人省去麻烦......

新的超类:

    public abstract class Superclass {
        protected SomeField someField;
        /**
        * DO NOT use this constructor! Just don't!
        */
        @SuppressWarnings( "unused" )
        private Superclass() {
        }

        public Superclass( SomeField someField ) {
            this.someField = someField;
        }

        public abstract void update( float delta );
        public abstract void render();
    }

新的通用数组实现:

    public class QuickArray< E extends Superclass > {
        private final E[] array;
        private int elementCount;

        @SuppressWarnings( { "unchecked" } )
        public QuickArray( final Class< E > clazz, final SomeField someField, final int size ) {
            array = (E[])Array.newInstance( clazz, size );
            for ( int i = 0; i < array.length; i++ ) {
                try {
                    array[ i ] = clazz.getDeclaredConstructor( SomeField.class ).newInstance( someField );
                } catch ( Exception e ) {
                    e.printStackTrace();
                }
            }
            elementCount = 0;
        }

        public E get( final int i ) {
            return array[ i ];
        }
        // some other fancy methods
    }

放入数组的测试类:

    public class MyClass extends Superclass {
        public MyClass( SomeField someField ) {
            super( someField );
        }

        @Override
        public void update( float delta ) {
            // update
        }

        @Override
        public void render() {
            // render
        }

        // some other fancy methods
    }

以下是数组的初始化方式:

    SomeField someField = new SomeField();
    QuickArray< MyClass > myArray = new QuickArray< MyClass >( MyClass.class, someField, CAPACITY );
于 2013-06-05T15:48:08.947 回答
0

你是正确的,你不能new E()在你的构造函数的循环中做(因为编译器不能保证所有可能的值都有一个无参数构造函数E),但是因为你提前知道如何构造所有您的具体子类,我建议按照以下内容合并一些代码(假设BulletWall子类MyInterface):

private static <C extends MyInterface> C create (Class<C> c) {
    MyInterface created;

    if (c == Bullet.class) {
        created = new Bullet ();
    } else if (c == Wall.class) {
        created = new Wall ();
    } else {
        throw new IllegalArgumentException ("Unknown class " + c);
    }

    return c.cast (created);
}

您可能更喜欢使用多态性来决定要实例化哪个子类。然后你创建

interface Factory<E extends MyInterface> {
    E create();
}

class BulletFactory implements Factory<Bullet> {
    public Bullet create() {
        return new Bullet();
    }
}

等等等等,然后你将正确的Factory对象传递给构造函数。

最后,我注意到Class您传入的对象没有被正确地约束为类的泛型参数,构造函数应该定义为

public CoolArray(final Class<E> clazz, int size) {
    array = (E[]) Array.newInstance(clazz, size);
    for (int i = 0; i < array.length; i++) {
         array[i] = GenericFactory.create(clazz);
    }
    aliveElements= 0;
}

或者

public CoolArray(final Class<E> clazz, final Factory<E> factory, int size) {
    array = (E[]) Array.newInstance(clazz, size);
    for (int i = 0; i < array.length; i++ ) {
         array[i] = factory.create();        }
    aliveElements= 0;
}

如果您决定使用工厂方法。

E(您的方法中也不需要强制转换get。)

于 2013-06-05T13:30:38.553 回答