web-dev-qa-db-ja.com

Javaで総称配列を作成する方法

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

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

979
tatsuhirosatou

見返りに質問する必要があります。あなたの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;
        }
    }
    

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

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

658
Varkhan

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

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

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

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


ジェネリックを使用している場合は、可能な限り、配列ではなくListを使用して作業するほうがはるかに幸せな時間が得られます。確かに時々あなたは選択をすることができません、しかしコレクションフレームワークを使うことははるかに堅牢です。

181
dimo414

型の安全性を保持しながら、ジェネリックを使用して正確に探している型の配列を取得する方法は次のとおりです(他の回答とは対照的に、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];  
    }  
}

これは警告なしでコンパイルされ、mainでわかるように、GenSetのインスタンスを宣言する型に関係なく、aをその型の配列に割り当て、aからその型の変数に要素を割り当てることができます。これは、配列と配列内の値が正しいタイプであることを意味します。

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

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

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

コンストラクター内で、メソッドcastを呼び出すと、渡されたObject引数が、メソッドが呼び出されたClassオブジェクトによって表されるクラスにキャストされて返されます。 Java.lang.reflect.Arrayの静的メソッドnewInstanceを呼び出すと、Objectとして、最初の引数として渡されたClassオブジェクトによって表される型の配列と、2番目の引数として渡されたintによって指定された長さの配列が返されますメソッドgetComponentTypeを呼び出すと、メソッドが呼び出されたClassオブジェクトによって表される配列のコンポーネントタイプを表すClassオブジェクトが返されます(例:String.class for String[].classnullオブジェクトが表す場合はClassアレイ)。

その最後の文は完全に正確ではありません。 String[].class.getComponentType()を呼び出すと、クラスClassを表すStringオブジェクトが返されますが、その型はClass<?>ではなく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));
}
61
gdejohn

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

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}
37
irreputable

より多くの次元に拡張するには、newInstance()[]とdimensionパラメータを追加するだけです(Tは型パラメータ、clsClass<T>d1からd5は整数)。

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() を参照してください。

29
Jason C

Java 8では、ラムダまたはメソッド参照を使用して一種の汎用配列作成を行うことができます。これはリフレクティブアプローチ(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より前にも行われていますが、それはより面倒です。

12
Radiodef

これは、 有効なJava、第2版の第5章(総称)でカバーされています 、項目25 ... 配列よりもリストを優先します

未チェックの警告が生成されますが、コードは機能します(これは、次のアノテーションを使用して抑制できます。

@SuppressWarnings({"unchecked"})

ただし、おそらくArrayの代わりにListを使用することをお勧めします。

OpenJDKプロジェクトサイト にこのバグ/機能に関する興味深い議論があります。

10
Jeff Olson

Javaジェネリックは、コンパイル時に型をチェックして適切なキャストを挿入することによって機能します。ただし、 消去 はコンパイル済みファイル内の型です。これは総称を理解しないコード(これは意図的な設計上の決定でした)で汎用ライブラリを使用可能にしますが、実行時にその型が何であるかを通常は見つけることができません。

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

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

7
Bill Michell

こんにちはスレッドが死んでいるが、私はこれにあなたの注意を引くことを望みます

ジェネリックは、コンパイル時の型チェックに使用されます。

  • したがって目的は、入ってくるものがあなたが必要としているものであることを確認することです。
  • あなたが返すものは消費者が必要とするものです。
  • これをチェックして:

enter image description here

ジェネリッククラスを書いているときに型キャストによる警告を心配しないでください。あなたがそれを使っているときに心配してください。

6
puneeth

このコードも見てください。

public static <T> T[] toArray(final List<T> obj) {
    if (obj == null || obj.isEmpty()) {
        return null;
    }
    final T t = obj.get(0);
    final T[] res = (T[]) Array.newInstance(t.getClass(), obj.size());
    for (int i = 0; i < obj.size(); i++) {
        res[i] = obj.get(i);
    }
    return res;
}

あらゆる種類のオブジェクトのリストを同じ型の配列に変換します。

5
MatheusJardimB

私は私のために働く迅速で簡単な方法を見つけました。私はこれを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

        // ...

    }
}

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

5
Nikos

この解決策はどうですか?

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

それはうまくいき、本当であるには余りにも単純に見えます。欠点はありますか?

5
Benjamin M

Class引数をコンストラクタに渡す必要はありません。これを試して。

public class GenSet<T> {
    private final T[] array;
    @SuppressWarnings("unchecked")
    public GenSet(int capacity, T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
              "Do not provide values for dummy argument.");
        Class<?> c = dummy.getClass().getComponentType();
        array = (T[])Array.newInstance(c, capacity);
    }
    @Override
    public String toString() {
        return "GenSet of " + array.getClass().getComponentType().getName()
            + "[" + array.length + "]";
    }
}

そして

GenSet<Integer> intSet = new GenSet<>(3);
System.out.println(intSet);
System.out.println(new GenSet<String>(2));

結果:

GenSet of Java.lang.Integer[3]
GenSet of Java.lang.String[2]
4
saka1029

例では、Javaリフレクションを使用して配列を作成しています。安全ではないので、これを行うことは一般にお勧めできません。代わりに、あなたがすべきことは、単に内部のListを使用し、配列をまったく使用しないことです。

4
Ola Bini

値のリストを渡しています...

public <T> T[] array(T... values) {
    return values;
}
3
Rodrigo Asensio

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

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

SIZEは定数、Tは型識別子です。

3
Pedram Esmaeeli

このコードスニペットを作成して、単純な自動テストユーティリティに渡されるクラスをリフレクティブにインスタンス化しました。

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(配列のクラス、配列のサイズ) 。クラスは、プリミティブ(int.class)とオブジェクト(Integer.class)の両方にすることができます。

BeanUtilsはSpringの一部です。

3
Bobster

他の人々によって提案された強制的なキャストは私のために働かなかった、違法なキャストの例外を投げた。

しかし、この暗黙のキャストはうまくいきました:

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

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

private K value;

この方法では、K型の配列(アイテムに値しかない場合)、またはクラスItemで定義したい一般的な型を取得できます。

1
vnportnoy

あなたが投稿した例で何が起こっているのかという質問に他の誰も答えていません。

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

他の人が言っているように、ジェネリックはコンパイル中に「消去」されます。したがって、実行時には、ジェネリックのインスタンスはそのコンポーネントタイプが何であるかを知りません。その理由は歴史的なもので、Sunは既存のインタフェース(ソースとバイナリの両方)を壊さずに総称を追加したいと考えました。

一方配列は do 実行時にそれらのコンポーネントタイプを知っています。

この例では、コンストラクタを呼び出すコード(型を認識している)が、必要な型をクラスに伝えるパラメータを渡すことで、問題を回避しています。

そのため、アプリケーションは次のようにクラスを構築します。

Stack<foo> = new Stack<foo>(foo.class,50)

そしてコンストラクタは(実行時に)コンポーネントタイプが何かを知っており、リフレクションAPIを通してその情報を使って配列を構築することができます。

Array.newInstance(clazz, capacity);

Array#newInstance()によって返される配列が正しい型であることをコンパイラが知る方法がないため、最後に型キャストを行います(知っていても)。

このスタイルは少し醜いですが、何らかの理由(配列の作成、またはコンポーネントタイプのインスタンスの作成など)のために実行時にコンポーネントタイプを知る必要があるジェネリックタイプを作成することに対する最悪の解決策となることがあります。

1
plugwash

私はこの問題に対するある種の回避策を見つけました。

以下の行は一般的な配列作成エラーを投げます

List<Person>[] personLists=new ArrayList<Person>()[10];

しかし、List<Person>を別のクラスにカプセル化しても、うまくいきます。

import Java.util.ArrayList;
import Java.util.List;


public class PersonList {

    List<Person> people;

    public PersonList()
    {
        people=new ArrayList<Person>();
    }
}

あなたはゲッターを通してPersonListクラスの人々を公開することができます。以下の行はすべての要素にList<Person>を持つ配列をあなたに与えるでしょう。つまりList<Person>の配列です。

PersonList[] personLists=new PersonList[10];

私が取り組んでいたいくつかのコードでは、このようなものが必要でした。これまでのところ問題ありません。

1
developer747

私は実際には、ジェネリックアレイを起動できないことを回避するための、非常にユニークな解決策を見つけました。あなたがしなければならないのは、そのように総称変数Tを取り込むクラスを作成することです:

class GenericInvoker <T> {
    T variable;
    public GenericInvoker(T variable){
        this.variable = variable;
    }
}

それからあなたの配列クラスでちょうどそれがそのように始まっているだけです:

GenericInvoker<T>[] array;
public MyArray(){
    array = new GenericInvoker[];
}

new Generic Invoker[]を開始すると未チェックで問題が発生しますが、実際には問題はないはずです。

配列から取得するには、配列[i] .variableを次のように呼び出します。

public T get(int index){
    return array[index].variable;
}

配列のサイズ変更などの残りの部分は、次のようにArrays.copyOf()で実行できます。

public void resize(int newSize){
    array = Arrays.copyOf(array, newSize);
}

そしてadd関数は次のように追加することができます:

public boolean add(T element){
    // the variable size below is equal to how many times the add function has been called 
    // and is used to keep track of where to put the next variable in the array
    arrays[size] = new GenericInvoker(element);
    size++;
}
0
Crab Nebula

あなたはキャストを使用することができます:

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

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}
0
samir benzenine

これを試して。

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

このコードが効果的な総称配列を作成するのではないかと思いますか。

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

編集:おそらくあなたが必要なサイズが知られていて小さいなら、そのような配列を作成するための代替方法は、単にzeroArrayコマンドに必要な数の "null"を入れることでしょうか?

明らかにこれはcreateArrayコードを使用するほど用途が広くありません。

0
Cambot

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

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}
0
StarMonkey

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

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

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

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

Object配列を作成し、それをどこにでもEにキャストできます。ええ、それをするのはあまりきれいな方法ではありませんが、少なくともうまくいくはずです。

0
Esko