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;
}
しかし、私は本当に何が起こっているのかわかりません。
見返りに質問する必要があります。あなたの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のジェネリックの既知の意図的な弱点に起因します。消去を使用して実装されたため、「ジェネリック」クラスは実行時に作成された型引数を知らないため、型を提供できません。何らかの明示的なメカニズム(型チェック)が実装されていない限り、安全です。
あなたはこれを行うことができます:
E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];
これは、Effective Java; Item 26にジェネリックコレクションを実装するための推奨方法の1つです。型エラーがなく、配列を繰り返しキャストする必要もありません。 ただしこれは潜在的に危険なので警告を引き起こします。注意して使用する必要があります。コメントで詳述されているように、このObject[]
は現在、E[]
型になりすましており、安全に使用しないと予期しないエラーやClassCastException
sを引き起こす可能性があります。
経験則として、キャスト配列が内部的に使用され(データ構造をバックアップするなど)、クライアントコードに返されたり公開されたりしない限り、この動作は安全です。ジェネリック型の配列を他のコードに返す必要がある場合は、言及しているリフレクションArray
クラスが正しい方法です。
ジェネリックを使用している場合は、可能な限り、配列ではなくList
を使用して作業するほうがはるかに幸せな時間が得られます。確かに時々あなたは選択をすることができません、しかしコレクションフレームワークを使うことははるかに堅牢です。
型の安全性を保持しながら、ジェネリックを使用して正確に探している型の配列を取得する方法は次のとおりです(他の回答とは対照的に、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>
として宣言され、T
はClass
オブジェクトが表す型を表します)。つまり、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[].class
、null
オブジェクトが表す場合は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));
}
これはタイプセーフな唯一の答えです
E[] a;
a = newArray(size);
@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
return Arrays.copyOf(array, length);
}
より多くの次元に拡張するには、newInstance()
に[]
とdimensionパラメータを追加するだけです(T
は型パラメータ、cls
はClass<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()
を参照してください。
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より前にも行われていますが、それはより面倒です。
これは、 有効なJava、第2版の第5章(総称)でカバーされています 、項目25 ... 配列よりもリストを優先します
未チェックの警告が生成されますが、コードは機能します(これは、次のアノテーションを使用して抑制できます。
@SuppressWarnings({"unchecked"})
ただし、おそらくArrayの代わりにListを使用することをお勧めします。
OpenJDKプロジェクトサイト にこのバグ/機能に関する興味深い議論があります。
Javaジェネリックは、コンパイル時に型をチェックして適切なキャストを挿入することによって機能します。ただし、 消去 はコンパイル済みファイル内の型です。これは総称を理解しないコード(これは意図的な設計上の決定でした)で汎用ライブラリを使用可能にしますが、実行時にその型が何であるかを通常は見つけることができません。
Public Stack(Class<T> clazz,int capacity)
コンストラクタでは、実行時にClassオブジェクトを渡す必要があります。つまり、クラス情報 は であり、それを必要とするコードに使用できます。 Class<T>
形式は、渡されたClassオブジェクトが正確にT型のClassオブジェクトであることをコンパイラがチェックすることを意味します。Tのサブクラスではなく、Tのスーパークラスではなく、正確にTです。
これは、コンストラクタに適切な型の配列オブジェクトを作成できることを意味します。つまり、コレクションに格納したオブジェクトの型は、コレクションに追加された時点で型がチェックされます。
こんにちはスレッドが死んでいるが、私はこれにあなたの注意を引くことを望みます
ジェネリックは、コンパイル時の型チェックに使用されます。
ジェネリッククラスを書いているときに型キャストによる警告を心配しないでください。あなたがそれを使っているときに心配してください。
このコードも見てください。
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;
}
あらゆる種類のオブジェクトのリストを同じ型の配列に変換します。
私は私のために働く迅速で簡単な方法を見つけました。私はこれを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クラスにあるその他のメソッド.
この解決策はどうですか?
@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
return elems;
}
それはうまくいき、本当であるには余りにも単純に見えます。欠点はありますか?
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]
例では、Javaリフレクションを使用して配列を作成しています。安全ではないので、これを行うことは一般にお勧めできません。代わりに、あなたがすべきことは、単に内部のListを使用し、配列をまったく使用しないことです。
値のリストを渡しています...
public <T> T[] array(T... values) {
return values;
}
実際に簡単な方法は、次の例のように、オブジェクトの配列を作成し、それを目的の型にキャストすることです。
T[] array = (T[])new Object[SIZE];
SIZE
は定数、T
は型識別子です。
このコードスニペットを作成して、単純な自動テストユーティリティに渡されるクラスをリフレクティブにインスタンス化しました。
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の一部です。
他の人々によって提案された強制的なキャストは私のために働かなかった、違法なキャストの例外を投げた。
しかし、この暗黙のキャストはうまくいきました:
Item<K>[] array = new Item[SIZE];
ここで、Itemはメンバーを含む私が定義したクラスです。
private K value;
この方法では、K型の配列(アイテムに値しかない場合)、またはクラスItemで定義したい一般的な型を取得できます。
あなたが投稿した例で何が起こっているのかという質問に他の誰も答えていません。
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()
によって返される配列が正しい型であることをコンパイラが知る方法がないため、最後に型キャストを行います(知っていても)。
このスタイルは少し醜いですが、何らかの理由(配列の作成、またはコンポーネントタイプのインスタンスの作成など)のために実行時にコンポーネントタイプを知る必要があるジェネリックタイプを作成することに対する最悪の解決策となることがあります。
私はこの問題に対するある種の回避策を見つけました。
以下の行は一般的な配列作成エラーを投げます
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];
私が取り組んでいたいくつかのコードでは、このようなものが必要でした。これまでのところ問題ありません。
私は実際には、ジェネリックアレイを起動できないことを回避するための、非常にユニークな解決策を見つけました。あなたがしなければならないのは、そのように総称変数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++;
}
あなたはキャストを使用することができます:
public class GenSet<Item> {
private Item[] a;
public GenSet(int s) {
a = (Item[]) new Object[s];
}
}
これを試して。
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>();
}
}
}
このコードが効果的な総称配列を作成するのではないかと思いますか。
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コードを使用するほど用途が広くありません。
これに対する簡単ではあるが面倒な回避策は、メインクラスの中に2番目の「ホルダー」クラスをネストして、それをデータの保持に使用することです。
public class Whatever<Thing>{
private class Holder<OtherThing>{
OtherThing thing;
}
public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}
この質問とは無関係かもしれませんが、使用中に "generic array creation
"エラーが発生していました。
Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];
私は@SuppressWarnings({"unchecked"})
で以下の作品を見つけました(そして私のために働いた):
Tuple<Long, String>[] tupleArray = new Tuple[10];
Object配列を作成し、それをどこにでもEにキャストできます。ええ、それをするのはあまりきれいな方法ではありませんが、少なくともうまくいくはずです。