1188

Java ジェネリックの実装により、次のようなコードを作成することはできません。

public class GenSet<E> {
    private E a[];

    public GenSet() {
        a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
    }
}

タイプセーフを維持しながらこれを実装するにはどうすればよいですか?

Java フォーラムで、次のような解決策を見ました。

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

しかし、私は何が起こっているのか本当にわかりません。

4

31 に答える 31

759

私は見返りに質問をしなければなりません:あなたのGenSet「チェックされた」または「チェックされていない」ですか?どういう意味ですか?

  • チェック済み強い型付けGenSet含まれているオブジェクトのタイプを明示的に認識します(つまり、コンストラクターがClass<E>引数で明示的に呼び出され、メソッドは、タイプではない引数が渡されると例外をスローしますE。を参照してくださいCollections.checkedCollection

    ->その場合、次のように書く必要があります。

    public class GenSet<E> {
    
        private E[] a;
    
        public GenSet(Class<E> c, int s) {
            // Use Array native method to create array
            // of a type only known at run time
            @SuppressWarnings("unchecked")
            final E[] a = (E[]) Array.newInstance(c, s);
            this.a = a;
        }
    
        E get(int i) {
            return a[i];
        }
    }
    
  • チェックされていない:弱いタイピング。引数として渡されたオブジェクトのいずれに対しても、実際には型チェックは行われません。

    ->その場合、あなたは書くべきです

    public class GenSet<E> {
    
        private Object[] a;
    
        public GenSet(int s) {
            a = new Object[s];
        }
    
        E get(int i) {
            @SuppressWarnings("unchecked")
            final E e = (E) a[i];
            return e;
        }
    }
    

    配列のコンポーネントタイプは、タイプパラメータの消去である必要があることに注意してください。

    public class GenSet<E extends Foo> { // E has an upper bound of Foo
    
        private Foo[] a; // E erases to Foo, so use Foo[]
    
        public GenSet(int s) {
            a = new Foo[s];
        }
    
        ...
    }
    

これはすべて、Javaのジェネリックの既知の意図的な弱点に起因します。これは消去を使用して実装されたため、「ジェネリック」クラスは実行時に作成された型引数を認識しないため、型を提供できません。明示的なメカニズム(型チェック)が実装されていない限り、安全性。

于 2009-02-09T22:19:31.197 に答える
256

あなたはこれを行うことができます:

E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];

これは、 Effective Javaでジェネリック コレクションを実装するための推奨される方法の 1 つです。項目26。型エラーはなく、配列を繰り返しキャストする必要もありません。 ただし、これは潜在的に危険であるため、警告をトリガーし、注意して使用する必要があります。コメントで詳しく説明されているように、これObject[]は現在、私たちのE[]型になりすましているため、安全に使用しないと予期しないエラーやClassCastExceptions が発生する可能性があります。

経験則として、この動作は、キャスト配列が内部で使用され (たとえば、データ構造をバックアップするために)、クライアント コードに返されたり公開されたりしない限り安全です。ジェネリック型の配列を他のコードに返す必要がある場合Arrayは、言及したリフレクション クラスが正しい方法です。


可能な限り、Listジェネリックを使用している場合は、配列よりも s を使用する方がずっと楽しい時間を過ごせます。確かに選択の余地がない場合もありますが、コレクション フレームワークを使用する方がはるかに堅牢です。

于 2010-05-27T20:00:45.727 に答える
65

ジェネリックを使用して、型の安全性を維持しながら、探している型の配列を正確に取得する方法を次に示します (Object配列を返すか、コンパイル時に警告が表示される他の回答とは対照的に)。

import java.lang.reflect.Array;  

public class GenSet<E> {  
    private E[] a;  

    public GenSet(Class<E[]> clazz, int length) {  
        a = clazz.cast(Array.newInstance(clazz.getComponentType(), length));  
    }  

    public static void main(String[] args) {  
        GenSet<String> foo = new GenSet<String>(String[].class, 1);  
        String[] bar = foo.a;  
        foo.a[0] = "xyzzy";  
        String baz = foo.a[0];  
    }  
}

これは警告なしでコンパイルされ、 でわかるように、 asmainのインスタンスを宣言する型が何であれ、その型の配列に割り当てることができ、その型の変数に要素を割り当てることができます。つまり、配列は配列内の値は正しい型です。GenSetaa

Java チュートリアルで説明されているように、クラス リテラルをランタイム タイプ トークンとして使用することで機能します。クラス リテラルは、コンパイラによって のインスタンスとして扱われますjava.lang.Class。使用するには、クラス名の後に.class. そのため、クラスを表すオブジェクトString.classとして機能します。これは、インターフェイス、列挙型、任意次元の配列 (例: )、プリミティブ (例: )、およびキーワード(例: ) に対しても機能します。ClassStringString[].classint.classvoidvoid.class

Classそれ自体はジェネリックです ( として宣言されClass<T>Tはオブジェクトが表す型をClass表します)。つまり、 の型はString.classですClass<String>

したがって、コンストラクタ for を呼び出すときは常に、インスタンスの宣言された型のGenSet配列を表す最初の引数にクラス リテラルを渡します (たとえば、 for )。プリミティブは型変数に使用できないため、プリミティブの配列を取得できないことに注意してください。GenSetString[].classGenSet<String>

コンストラクター内でメソッドを呼び出すと、メソッドが呼び出されたオブジェクトによって表されるクラスにキャストされcastた渡された引数が返されます。で static メソッドを呼び出すと、最初の引数として渡されたオブジェクトによって表される型と、2 番目の引数として渡された によって指定された長さの配列として返されます。メソッドを呼び出すと、メソッドが呼び出されたオブジェクトによって表される配列のコンポーネント タイプを表すオブジェクトが返されます (たとえば、オブジェクトが配列を表していない場合) 。ObjectClassnewInstancejava.lang.reflect.ArrayObjectClassintgetComponentTypeClassClassString.classString[].classnullClass

その最後の文は完全に正確ではありません。を呼び出すと、 class を表すオブジェクトString[].class.getComponentType()が返されますが、その型はではなくです。そのため、次のようなことはできません。ClassStringClass<?>Class<String>

String foo = String[].class.getComponentType().cast("bar"); // won't compile

オブジェクトClassを返すすべてのメソッドについても同様です。Class

この回答に対するJoachim Sauer のコメント(自分でコメントするほどの評判はありません) については、キャストを使用する例T[]では警告が表示されます。その場合、コンパイラは型の安全性を保証できないためです。


Ingo のコメントに関する編集:

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}
于 2010-11-19T03:30:15.870 に答える
43

これがタイプセーフな唯一の答えです

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}
于 2011-11-08T15:28:17.957 に答える
35

[]より多くの次元に拡張するには、と次元パラメータをnewInstance()(Tは型パラメータ、clsis a Class<T>d1d5は整数)に追加するだけです:

T[] array = (T[])Array.newInstance(cls, d1);
T[][] array = (T[][])Array.newInstance(cls, d1, d2);
T[][][] array = (T[][][])Array.newInstance(cls, d1, d2, d3);
T[][][][] array = (T[][][][])Array.newInstance(cls, d1, d2, d3, d4);
T[][][][][] array = (T[][][][][])Array.newInstance(cls, d1, d2, d3, d4, d5);

詳細Array.newInstance()については、を参照してください。

于 2013-08-15T13:47:19.580 に答える
16

Java 8 では、ラムダまたはメソッド参照を使用して、一種の汎用配列作成を行うことができます。これは ( a を渡すClass) リフレクティブ アプローチに似ていますが、ここではリフレクションを使用していません。

@FunctionalInterface
interface ArraySupplier<E> {
    E[] get(int length);
}

class GenericSet<E> {
    private final ArraySupplier<E> supplier;
    private E[] array;

    GenericSet(ArraySupplier<E> supplier) {
        this.supplier = supplier;
        this.array    = supplier.get(10);
    }

    public static void main(String[] args) {
        GenericSet<String> ofString =
            new GenericSet<>(String[]::new);
        GenericSet<Double> ofDouble =
            new GenericSet<>(Double[]::new);
    }
}

たとえば、これは によって使用され<A> A[] Stream.toArray(IntFunction<A[]>)ます。

これは Java 8 より前のバージョンでも匿名クラスを使用して実行できましたが、より面倒です。

于 2014-03-05T14:14:02.287 に答える
7

Java ジェネリックは、コンパイル時に型をチェックし、適切なキャストを挿入することによって機能しますが、コンパイルされたファイル内の型を消去します。これにより、ジェネリックを理解しないコードでジェネリック ライブラリを使用できるようになります (これは意図的な設計上の決定でした)。

パブリックStack(Class<T> clazz,int capacity)コンストラクターでは、実行時に Class オブジェクトを渡す必要があります。これは、クラス情報を必要とするコードが実行時にクラス情報を利用できることを意味します。このClass<T>形式は、渡された Class オブジェクトが正確に T 型の Class オブジェクトであることをコンパイラがチェックすることを意味します。T のサブクラスでも、T のスーパークラスでもなく、正確に T です。

これは、コンストラクターで適切な型の配列オブジェクトを作成できることを意味します。つまり、コレクションに格納するオブジェクトの型は、コレクションに追加された時点で型がチェックされます。

于 2009-02-11T10:07:35.283 に答える
7

スレッドは死んでいますが、これに注意を向けたいと思います。

ジェネリックは、コンパイル時の型チェックに使用されます。したがって、目的はチェックすることです

  • 入ってくるものはあなたが必要とするものです。
  • あなたが返すものは、消費者が必要とするものです。

これをチェックして:

ここに画像の説明を入力

ジェネリック クラスを作成しているときに、型キャストの警告について心配する必要はありません。使用中の心配。

于 2011-06-14T19:26:00.757 に答える
6

このソリューションはどうですか?

@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
    return elems;
}

それは機能し、あまりにも単純に見えます。欠点はありますか?

于 2016-02-21T01:28:56.793 に答える
5

この例では、Java リフレクションを使用して配列を作成しています。タイプセーフではないため、これを行うことは一般的に推奨されません。代わりに、内部リストを使用し、配列をまったく使用しないようにする必要があります。

于 2009-02-09T17:33:58.310 に答える
5

私は自分に合った迅速で簡単な方法を見つけました。これはJava JDK 8でのみ使用したことに注意してください。以前のバージョンで動作するかどうかはわかりません。

特定の型パラメーターのジェネリック配列をインスタンス化することはできませんが、作成済みの配列をジェネリック クラス コンストラクターに渡すことはできます。

class GenArray <T> {
    private T theArray[]; // reference array

    // ...

    GenArray(T[] arr) {
        theArray = arr;
    }

    // Do whatever with the array...
}

メインでは、次のように配列を作成できます。

class GenArrayDemo {
    public static void main(String[] args) {
        int size = 10; // array size
        // Here we can instantiate the array of the type we want, say Character (no primitive types allowed in generics)
        Character[] ar = new Character[size];

        GenArray<Character> = new Character<>(ar); // create the generic Array

        // ...

    }
}

配列の柔軟性を高めるために、リンクされたリストを使用できます。Java.util.ArrayList クラスにある ArrayList およびその他のメソッド。

于 2016-11-09T20:43:28.790 に答える
3

このコード スニペットは、単純な自動テスト ユーティリティに渡されるクラスを反射的にインスタンス化するために作成しました。

Object attributeValue = null;
try {
    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }
    else if(!clazz.isInterface()){
        attributeValue = BeanUtils.instantiateClass(clazz);
    }
} catch (Exception e) {
    logger.debug("Cannot instanciate \"{}\"", new Object[]{clazz});
}

このセグメントに注意してください:

    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }

Array.newInstance(class of array, size of array) を開始する配列の場合。クラスは、プリミティブ (int.class) とオブジェクト (Integer.class) の両方にすることができます。

BeanUtils は Spring の一部です。

于 2012-08-31T10:39:28.523 に答える
2

実際には、次の例のように、オブジェクトの配列を作成し、目的の型にキャストするのがより簡単な方法です。

T[] array = (T[])new Object[SIZE];

SIZE定数でTあり、型識別子です

于 2015-06-12T09:40:46.373 に答える
2

他の人が提案した強制キャストは私にはうまくいきませんでした.違法なキャストの例外がスローされました.

ただし、この暗黙のキャストは正常に機能しました。

Item<K>[] array = new Item[SIZE];

ここで、Item はメンバーを含む定義したクラスです。

private K value;

このようにして、タイプ K (項目に値しかない場合) またはクラス Item で定義したい任意のジェネリック型の配列を取得します。

于 2013-09-14T21:26:33.447 に答える
0

これに対する簡単ではありますが、厄介な回避策は、メインクラス内に2番目の「ホルダー」クラスをネストし、それを使用してデータを保持することです。

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}
于 2012-04-05T00:10:45.893 に答える
0

キャストを使用できます:

public class GenSet<Item> {
    private Item[] a;

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}
于 2014-09-15T17:23:32.283 に答える
0

この質問とは関係ないかもしれませんが、generic array creation使用中に「」エラーが発生しました

Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];

私は次の作品を見つけました(そして私のために働いた) @SuppressWarnings({"unchecked"})

 Tuple<Long, String>[] tupleArray = new Tuple[10];
于 2013-08-21T16:11:35.523 に答える
0

このコードが効果的なジェネリック配列を作成するかどうか疑問に思っていますか?

public T [] createArray(int desiredSize){
    ArrayList<T> builder = new ArrayList<T>();
    for(int x=0;x<desiredSize;x++){
        builder.add(null);
    }
    return builder.toArray(zeroArray());
}

//zeroArray should, in theory, create a zero-sized array of T
//when it is not given any parameters.

private T [] zeroArray(T... i){
    return i;
}

編集:おそらく、必要なサイズが既知で小さい場合、そのような配列を作成する別の方法は、必要な数の「null」を単に zeroArray コマンドに入力することでしょうか?

明らかに、これは createArray コードを使用するほど汎用的ではありません。

于 2014-07-09T13:36:35.787 に答える
0

これを試して。

private int m = 0;
private int n = 0;
private Element<T>[][] elements = null;

public MatrixData(int m, int n)
{
    this.m = m;
    this.n = n;

    this.elements = new Element[m][n];
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            this.elements[i][j] = new Element<T>();
        }
    }
}
于 2011-02-13T20:37:34.907 に答える
-1
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}
于 2015-06-03T05:52:51.570 に答える