web-dev-qa-db-ja.com

Javaで汎用配列型を作成できない理由は何ですか?

Javaが許可されない理由は何ですか

private T[] elements = new T[initialCapacity];

.NETでは、実行時にさまざまなサイズを持つことができる値型がありますが、Javaではすべての種類のTがオブジェクト参照であるため、.NETではそれを許可していません。したがって、同じサイズになります(間違っている場合は修正してください)。

理由は何ですか?

254

これは、Javaの配列(ジェネリックとは異なります)に、実行時にそのコンポーネントタイプに関する情報が含まれているためです。そのため、配列を作成するときにコンポーネントの種類を知る必要があります。実行時にTが何であるかわからないため、配列を作成できません。

186
newacct

見積もり:

ジェネリック型の配列は適切ではないため許可されません。この問題は、静的にサウンドではないが動的にチェックされるJava配列と、静的にサウンドであり動的にチェックされないジェネリックとの相互作用によるものです。抜け穴を利用する方法は次のとおりです。

class Box<T> {
    final T x;
    Box(T x) {
        this.x = x;
    }
}

class Loophole {
    public static void main(String[] args) {
        Box<String>[] bsa = new Box<String>[3];
        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3); // error not caught by array store check
        String s = bsa[0].x; // BOOM!
    }
}

静的に安全な配列(別名Variance)を使用してこの問題を解決することを提案しましたが、Tigerで拒否されました。

- gafter

(私はそれが ニール・ガフター であると信じていますが、よくわかりません)

ここでコンテキストでそれを参照してください: http://forums.Sun.com/thread.jspa?threadID=457033&forumID=316

131
Bart Kiers

まともな解決策を提供し損なうことで、あなたはただ私見が悪いものになってしまいます。

一般的な回避策は次のとおりです。

T[] ts = new T[n];

に置き換えられます(Tは別のクラスではなくObjectを拡張すると仮定します)

T[] ts = (T[]) new Object[n];

私は最初の例を好むが、より多くの学問の種類は2番目を好むように思われる、または単にそれについて考えないことを好む。

Object []を単に使用できない理由のほとんどの例は、ListまたはCollection(これらはサポートされています)に等しく適用されるため、非常に貧弱な引数と見なされます。

注:これは、コレクションライブラリ自体が警告なしにコンパイルされない理由の1つです。このユースケースを警告なしでサポートできない場合、ジェネリックモデルIMHOで根本的な問題が発生します。

44
Peter Lawrey

これが不可能な理由は、Javaがそのコンパイラを単にコンパイラレベルで実装し、各クラスに対して生成されるクラスファイルが1つしかないためです。これは Type Erasure と呼ばれます。

実行時に、コンパイルされたクラスは、すべての使用を同じバイトコードで処理する必要があります。したがって、new T[capacity]には、どの型をインスタンス化する必要があるのか​​、まったくわかりません。

29
Durandal

配列は共変です

配列は共変であると言われます。これは基本的に、Javaのサブタイピングルールを考えると、タイプT[]の配列にはタイプTの要素またはTのサブタイプが含まれることを意味します。例えば

Number[] numbers = new Number[3];
numbers[0] = newInteger(10);
numbers[1] = newDouble(3.14);
numbers[2] = newByte(0);

しかし、それだけでなく、Javaのサブタイプ規則は、STのサブタイプである場合、配列S[]は配列T[]のサブタイプであると述べているため、次のようなものも有効です:

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

Javaのサブタイプ規則に従って、IntegerはNumberのサブタイプであるため、配列Integer[]は配列Number[]のサブタイプです。

しかし、このサブタイプ規則は興味深い質問につながる可能性があります。これを行おうとするとどうなりますか?

myNumber[0] = 3.14; //attempt of heap pollution

この最後の行は問題なくコンパイルされますが、このコードを実行すると、整数配列にdoubleを挿入しようとしているため、ArrayStoreExceptionを取得します。ここでは、Number参照を介して配列にアクセスしているという事実は関係ありません。重要なのは、配列が整数の配列であることです。

これは、コンパイラをだますことはできますが、ランタイム型システムをだますことはできないことを意味します。これは、配列が「再定義可能型」と呼ばれるものだからです。つまり、実行時にJavaは、この配列が実際にNumber[]型の参照を介してアクセスされる整数の配列として実際にインスタンス化されたことを認識します。

ご覧のとおり、1つはオブジェクトの実際の型であり、もう1つはオブジェクトへのアクセスに使用する参照の型です。

Java Genericsの問題

現在、Javaのジェネリック型の問題は、型パラメーターの型情報がコードのコンパイル後にコンパイラーによって破棄されることです。したがって、このタイプ情報は実行時に利用できません。このプロセスはtype erasureと呼ばれます。このようなジェネリックをJavaで実装するのには十分な理由がありますが、それは長い話であり、既存のコードとのバイナリ互換性に関係しています。

ここで重要な点は、実行時には型情報がないため、ヒープ汚染を行わないことを保証する方法がないことです。

次の安全でないコードを考えてみましょう。

List<Integer> myInts = newArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution

Javaコンパイラーがこれを行うことを止めない場合、ランタイム型システムも私たちを止めることはできません。実行時に、このリストがリストであると判断する方法がない整数のみ。 Javaランタイムは、作成時に整数のリストとして宣言されているため、整数のみを含める必要があるときに、このリストに必要なものを何でも入れることができます。そのため、コンパイラは行番号4を拒否します。これは、それが安全ではなく、許可されていれば型システムの仮定を破る可能性があるためです。

そのため、Javaの設計者は、コンパイラをだますことができないことを確認しました。 (配列でできるように)コンパイラをだますことができない場合は、ランタイム型システムもだますことができません。

そのため、実行時にジェネリック型の真の性質を判別できないため、ジェネリック型は変更不可能であると言います。

ここで記事全体を読むことができるこの回答の一部をスキップしました: https://dzone.com/articles/covariance-and-contravariance

27
Humoyun

答えはすでに与えられていますが、すでにTのインスタンスがある場合は、これを行うことができます。

T t; //Assuming you already have this object instantiated or given by parameter.
int length;
T[] ts = (T[]) Array.newInstance(t.getClass(), length);

助けてくれればいいのに、Ferdi265

18
Ferdi265

主な理由は、Javaの配列が共変であるという事実によるものです。

概要があります こちら

5
GaryF

Gafter によって間接的に与えられる答えが好きです。しかし、私はそれが間違っていると提案します。 Gafterのコードを少し変更しました。コンパイルしてしばらく実行した後、Gafterが予測した場所を爆撃します

class Box<T> {

    final T x;

    Box(T x) {
        this.x = x;
    }
}

class Loophole {

    public static <T> T[] array(final T... values) {
        return (values);
    }

    public static void main(String[] args) {

        Box<String> a = new Box("Hello");
        Box<String> b = new Box("World");
        Box<String> c = new Box("!!!!!!!!!!!");
        Box<String>[] bsa = array(a, b, c);
        System.out.println("I created an array of generics.");

        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3);
        System.out.println("error not caught by array store check");

        try {
            String s = bsa[0].x;
        } catch (ClassCastException cause) {
            System.out.println("BOOM!");
            cause.printStackTrace();
        }
    }
}

出力は

I created an array of generics.
error not caught by array store check
BOOM!
Java.lang.ClassCastException: Java.lang.Integer cannot be cast to Java.lang.String
    at Loophole.main(Box.Java:26)

したがって、Javaで汎用配列型を作成できるように思えます。質問を誤解しましたか?

4
emory

Oracleチュートリアル から:

パラメーター化された型の配列を作成することはできません。たとえば、次のコードはコンパイルされません。

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

次のコードは、さまざまな型が配列に挿入されたときに何が起こるかを示しています。

Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.

一般的なリストで同じことをしようとすると、問題が発生します。

Object[] stringLists = new List<String>[];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.

パラメータ化されたリストの配列が許可されている場合、以前のコードは必要なArrayStoreExceptionのスローに失敗します。

私には、非常に弱いようです。ジェネリックを十分に理解している人なら誰でも完全に大丈夫で、そのような場合にArrayStoredExceptionがスローされないことを期待していると思います。

2
Stick Hero

私の場合、次のようなスタックの配列が必要でした。

Stack<SomeType>[] stacks = new Stack<SomeType>[2];

これは不可能だったため、回避策として次を使用しました。

  1. Stack(MyStackなど)の非汎用ラッパークラスを作成しました
  2. MyStack [] stacks =新しいMyStack [2]は完璧に機能しました

Glyいですが、Javaは幸せです。

注:BrainSlugs83が質問へのコメントで述べたように、.NETでジェネリックの配列を持つことは完全に可能です

2

私はここでパーティーに少し遅れていることを知っていますが、これらの答えのどれもが私の問題を解決しなかったので、私は将来のグーグルを助けることができるかもしれないと考えました。ただし、Ferdi265の回答は非常に役立ちました。

私は自分のリンクリストを作成しようとしているので、次のコードが私のために働いたものです:

package myList;
import Java.lang.reflect.Array;

public class MyList<TYPE>  {

    private Node<TYPE> header = null;

    public void clear() {   header = null;  }

    public void add(TYPE t) {   header = new Node<TYPE>(t,header);    }

    public TYPE get(int position) {  return getNode(position).getObject();  }

    @SuppressWarnings("unchecked")
    public TYPE[] toArray() {       
        TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());        
        for(int i=0 ; i<size() ; i++)   result[i] = get(i); 
        return result;
    }


    public int size(){
         int i = 0;   
         Node<TYPE> current = header;
         while(current != null) {   
           current = current.getNext();
           i++;
        }
        return i;
    }  

ToArray()メソッドには、ジェネリック型の配列を作成する方法があります。

TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());    
1
Derek Ziemba

ジェネリック配列をインスタンス化できない場合、言語にジェネリック配列型があるのはなぜですか?オブジェクトのない型を持つことのポイントは何ですか?

私が考えることができる唯一の理由は、可変引数-foo(T...)です。それ以外の場合は、ジェネリック配列型を完全にスクラブできます。 (まあ、1.5の前に可変引数が存在しなかったので、可変引数に実際に配列を使用する必要はありませんでした。それはおそらく別の間違いです。)

つまり、嘘ですcan可変引数を使用して、汎用配列をインスタンス化します!

もちろん、汎用配列の問題は依然として現実的です。

static <T> T[] foo(T... args){
    return args;
}
static <T> T[] foo2(T a1, T a2){
    return foo(a1, a2);
}

public static void main(String[] args){
    String[] x2 = foo2("a", "b"); // heap pollution!
}

この例を使用して、実際にgeneric arrayの危険性を示すことができます。

一方、10年前から汎用の可変引数を使用してきましたが、空はまだ落ちていません。したがって、問題は誇張されていると言えます。それは大したことじゃないよ。明示的な汎用配列の作成が許可されている場合、あちこちにバグがあります。しかし、私たちは消去の問題に慣れており、それと共に生きることができます。

そして、foo2をポイントして、仕様が私たちを妨げていると主張する問題から私たちを守るという主張に反論することができます。 Sunに1.5の時間とリソースがもっとあれば、もっと満足のいく解像度に到達できたと思います。

0
ZhongYu

他の人がすでに言及したように、もちろん some tricks で作成できます。

しかしそれは推奨されません。

type erasure 、さらに重要なのは、サブタイプ配列のみを許可する配列内のcovarianceをスーパータイプ配列に割り当てることができるためです。これにより、値を取得して、 time ClassCastExceptionこれは、ジェネリックが排除しようとする主な目的の1つです。 コンパイル時の強力な型チェック

Object[] stringArray = { "hi", "me" };
stringArray[1] = 1;
String aString = (String) stringArray[1]; // boom! the TypeCastException

より直接的な例は Effective Java:Item 25 にあります。


covariance:SがTのサブタイプである場合、S []タイプの配列はT []のサブタイプ

0
Hearen

おそらく、それを回避する適切な方法が必要です(おそらくリフレクションを使用する)。それは、ArrayList.toArray(T[] a)がまさに行うことのように思えるからです。引用:

public <T> T[] toArray(T[] a)

このリスト内のすべての要素を正しい順序で含む配列を返します。返される配列のランタイムタイプは、指定された配列のランタイムタイプです。リストが指定された配列に収まる場合、リストに返されます。それ以外の場合、指定された配列のランタイムタイプとこのリストのサイズで新しい配列が割り当てられます。

したがって、1つの方法は、この関数を使用することです。つまり、配列に必要なオブジェクトのArrayListを作成し、その後toArray(T[] a)を使用して実際の配列を作成します。スピーディではありませんが、要件については言及しませんでした。

toArray(T[] a)の実装方法を知っている人はいますか?

0
Adam

Javaを作成した後にジェネリックが追加されたためです。Javaの元のメーカーは、配列を作成するときに型を指定すると考えられるため、ちょっと不格好です。したがって、ジェネリックでは機能しないため、E [] array =(E [])new Object [15];を実行する必要があります。これはコンパイルされますが、警告が表示されます。

0
Alvin

これを試して:

List<?>[] arrayOfLists = new List<?>[4];
0