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 ...
    }

これは、私の配列ラッパー クラスである必要があります。レベルがロードされたときにこの配列を作成します。ゲームのupdateメソッドとrenderメソッドでは、配列のaliveElements -Elements のみを繰り返します。

MyInterface には、 updaterenderなどのメソッド スタブが含まれています...

これで、弾丸を発射するたびに、aliveElementsの位置にある Element で何らかのinitメソッドを呼び出します。ガベージコレクションを避けるためにこれを行います。

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

そして、これは、配列内のすべての要素をどのようにインスタンス化しようとしても、 ClassCastExceptionが原因で失敗する部分です。

注:上記の例では、要素をインスタンス化していないため、 NullPointerExceptionが発生することをよく知っています! それは問題ではありません。

Google、いくつかの本、その他の質問から、Java ではジェネリック型をインスタンス化できないことがわかりました。だから今私の質問は:これを回避する方法はありますか?

updateメソッドまたはrenderメソッドで新しいオブジェクトを割り当てたくありません。ガベージ コレクターが時々フレームレートに何をするかが怖いからです :)

4

3 に答える 3

4

まず第一に、あなたが書くクラスはArrayList、要素の型に追加の制限があるだけです。それを使用できるかどうかを試してください(またはラップしてください)。最終的にそれを行わなくても、実装するCollection<E>、少なくとも Iterable<E>再利用可能なコードを作成するのに役立つはずです。

次: あなたは を解決したと言ってNullPointerExceptionいますが、オブジェクトをどのようにインスタンス化したかは教えてくれません。

そして、はい、できないことを回避する方法がありますnew E()。これには、Class<E回避策があり、幸運にも既に持っています: を使用c.newInstance()して内部配列を埋めることができます (MyInterfaceすべての実装に引数なしの public コンストラクターがある場合)。

于 2013-06-05T14:35:41.940 に答える
1

InstantiationExceptionを呼び出したときにが得られる理由がわかりましたarray[i] = clazz.newInstance()。これは、渡すクラスにデフォルトのコンストラクターがないためです。クラスは次のようになります。

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

それを実行するために、MyInterface をabstract class継承ではなく拡張するように変更しました。Constructorその中にSomeFieldArgumentを入れて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)。あなたの具体的なサブクラス、私はこれの行に沿っていくつかのコードを組み込むことをお勧めします(のBulletサブクラスWallを想定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 に答える