Javaでジェネリック配列型を作成できない理由は何ですか?


273

Javaが私たちに許可しない理由は何ですか

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

.NETではそれを行うことができなかったことを理解できました..NETではランタイムに異なるサイズを持つことができる値型がありますが、Javaではすべての種類のTがオブジェクト参照になるため、同じサイズになります(私が間違っている場合は修正してください)。

理由は何ですか?


29
あなたは何について話していますか?あなたは絶対にこれを.NETで行うことができます。-Javaでそれができない理由を理解しようとしています。
BrainSlugs83 2014年

@ BrainSlugs83-それを示すコード例またはチュートリアルへのリンクを追加してください。
MasterJoe2


1
@ MasterJoe2 OPの質問の上記のコードは、私が言及しているものです。C#では正常に機能しますが、Javaでは機能しません。-質問には、どちらでも機能することが記載されていますが、これは誤りです。-それについてさらに議論することに価値があるかどうかはわかりません。
BrainSlugs83

回答:


204

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


29
しかし、消去はどうですか?なぜそれが当てはまらないのですか?
Qix-モニカは2013年

14
どのようにArrayList <SomeType>それを行いますか?
Thumbz 2014年

10
@Thumbz:つまりnew ArrayList<SomeType>()?ジェネリック型には、実行時に型パラメーターが含まれません。typeパラメータは作成では使用されません。生成されたコードには違いありませんnew ArrayList<SomeType>()か、new ArrayList<String>()またはnew ArrayList()すべてでは。
newacct 2014年

8
私はそれがどのようにArrayList<T>機能するかについてもっと質問していましたprivate T[] myArray。コードのどこかに、ジェネリック型Tの配列がなければならないので、どうやって?
Thumbz 2014年

21
@Thumbz:ランタイムタイプの配列はありませんT[]。ランタイムタイプの配列がObject[]あり、1)ソースコードに次の変数が含まれているObject[](これが最新のOracle Javaソースでの方法です)。または2)ソースコードにの型の変数が含まれていますT[]。これは嘘ですがT、クラスのスコープ内で消去されるため問題は発生しません。
newacct 2014年

137

見積もり:

ジェネリック型の配列は適切ではないため、許可されていません。この問題は、静的には有効ではないが動的にチェックされる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!
    }
}

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

- ガフター

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

こちらのコンテキストで確認してください。http//forums.sun.com/thread.jspa?threadID = 457033&forumID = 316


3
答えは私のものではないため、CWにしたことに注意してください。
Bart Kiers、2010年

10
これが、タイプセーフではない理由を説明しています。しかし、タイプセーフティの問題はコンパイラによって警告される可能性があります。実際には、それを行うことさえ不可能です。それは、あなたができないのとほぼ同じ理由でですnew T()。Javaの各配列は、設計上、その中にコンポーネントタイプ(つまりT.class)を格納します。したがって、そのような配列を作成するには、実行時にTのクラスが必要です。
newacct、

2
あなたのnew Box<?>[n]例では役に立たないかもしれませんが、それでも時々十分かもしれないを使用することができます。
Bartosz Klimek

1
@BartKiers取得できません...これはまだコンパイルされません(java-8):java-8以降でBox<String>[] bsa = new Box<String>[3];何か変更はありましたか?
ユージーン

1
@Eugene、特定のジェネリック型の配列は、サンプルで示されているように型の安全性が失われる可能性があるため、許可されていません。Javaのどのバージョンでも許可されていません。答えは、「ジェネリック型の配列は適切ではないため、許可されていません。」から始まります
ガーネット

47

きちんとした解決策を提供することに失敗することによって、あなたはただより悪い私見で終わるだけです。

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

T[] ts = new T[n];

に置き換えられます(TがObjectを拡張し、別のクラスを拡張しないと仮定)

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

私は最初の例を好みますが、より多くの学問のタイプは2番目を好むようです、または単にそれについて考えたくないです。

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

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


6
2番目のものには注意が必要です。このような方法で作成された配列を、たとえばaを期待するString[]ユーザーに返す場合(またはtypeのパブリックにアクセス可能なフィールドに格納し、T[]誰かがそれを取得する場合)、それらはClassCastExceptionを取得します。
newacct 2010年

4
あなたの好ましい例は、ClassCastExceptionがスローされることがあり、Javaとあなたの第二の例では許可されていないので、私はこの答えを否決
ホセ・ロベルト・アラウージョジュニオール

5
@JoséRobertoAraújoJúnior最初の例を2番目の例に置き換える必要があることは明らかです。誰にとっても明白ではないので、2番目の例がClassCastExceptionをスローする理由を説明する方が役立つでしょう。
Peter Lawrey、2014

3
@PeterLawrey私はなぜ示す自己回答質問作成T[] ts = (T[]) new Object[n];:悪い考えですstackoverflow.com/questions/21577493/...
ホセ・ロベルト・アラウージョジュニオール

1
@MarkoTopolnik私がすでに言ったのと同じことを説明するために、あなたのすべてのコメントに答えるメダルを与えられるべきT[] ts = new T[n];です。私の元の理由から変わった唯一のことは、彼が言ったことが有効な例であると思ったということです。彼の答えは他の開発者に問題や混乱を引き起こし、話題から外れるため、私は投票を続けます。また、これについてのコメントはやめます。
ホセ・ロベルト・アラウージョ・

38

配列は共変です

配列は共変であると言われます。これは基本的に、Javaのサブタイプ規則が与えられたT[]場合、型の配列には型の要素Tまたはの任意のサブタイプが含まれる可能性があることを意味しTます。例えば

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

それだけでなく、Javaのサブタイプ規則でS[]は、がのサブタイプであるT[]場合、配列は配列SのサブタイプであるTと明記されているため、次のようなものも有効です。

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

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

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

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

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

つまり、コンパイラをだますことができますが、ランタイム型システムをだますことはできません。これは、配列がreifiable型と呼ばれるものだからです。つまり、実行時にJavaは、この配列が実際には整数の配列としてインスタンス化され、たまたまタイプの参照を介してアクセスされることを知っています。Number[]

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

Java Genericsの問題

ここで、Javaのジェネリック型の問題は、型パラメーターの型情報がコードのコンパイルが完了した後にコンパイラーによって破棄されることです。したがって、このタイプ情報は実行時には利用できません。このプロセスはタイプ消去と呼ばれます。このようなジェネリックを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は安全ではなく、許可されていると型システムの前提を破る可能性があるため、コンパイラが行番号4を拒否するのはそのためです。

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

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

この回答の一部をスキップしました。https//dzone.com/articles/covariance-and-contravariance


32

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

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


17

答えはすでに与えられていますが、すでに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


1
これは素晴らしいソリューションです。しかし、これは未チェックの警告(ObjectからT []へのキャスト)を受け取ります。別の「遅い」が「警告のない」ソリューションは次のようになりますT[] ts = t.clone(); for (int i=0; i<ts.length; i++) ts[i] = null;
midnite 2013

1
さらに、私たちが保持しているものがであればT[] t、それはそうなります(T[]) Array.newInstance(t.getClass().getComponentType(), length);。私は理解するためにいくつかの時間を費やしましたgetComponentType()。これが他の人を助けることを願っています。
midnite 2013

1
@midnite t.clone()は返されませんT[]。そのためt、この答えでは配列ではありません。
xmen '25

6

主な理由は、Javaの配列が共変であるためです。

ここに良い概要があります


不変の配列を使用しても、「新しいT [5]」をどのようにサポートできるかわかりません。
Dimitris Andreou、

2
@DimitrisAndreouまあ、全体はむしろJavaデザインのエラーの喜劇です。すべては配列の共分散から始まりました。次に、配列の共分散が得られたら、にキャストString[]Objectて格納できますInteger。そのArrayStoreExceptionため、コンパイル時に問題を検出できなかったため、配列ストア()のランタイム型チェックを追加する必要がありました。(そうでない場合、Integer実際にはでスタックしている可能性がString[]あり、それを取得しようとするとエラーが発生し、それは恐ろしいことです。)...
Radon Rosborough

2
@DimitrisAndreou…次に、はるかに健全なコンパイル時チェックの代わりにランタイムチェックを配置すると、型の消去に遭遇します(これも不幸な設計上の欠陥です。後方互換性のためだけに含まれています)。型消去とは、ジェネリック型のランタイム型チェックを実行できないことを意味します。したがって、配列のストレージタイプの問題を回避するには、一般的な配列を使用することはできません。彼らがそもそも単純に配列を不変にしたのであれば、消去の心配をせずにコンパイル時の型チェックを実行するだけで済みます。
Radon Rosborough、2014

…コメントの5分の編集期間を発見しました。私の最初のコメントにObjectあっObject[]たはずです。
Radon Rosborough、2014

3

私は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でジェネリック配列型を作成できるように見えます。質問を誤解しましたか?


あなたの例は私が尋ねたものとは異なります。あなたが説明したのは、配列の共分散の危険性です。確認してください(.NETの場合:blogs.msdn.com/b/ericlippert/archive/2007/10/17/…
むさぼったエリジウム

うまくいけば、コンパイラから型安全性の警告が出ますか?
Matt McHenry、

1
はい、タイプセーフの警告が表示されます。はい、私の例では質問に反応しません。
Emory

実際、a、b、cのずさんな初期化が原因で、複数の警告が表示されます。また、これはよく知られており、コアライブラリ、たとえば<T> java.util.Arrays.asList(T ...)に影響します。Tにreifiableでない型を渡すと、警告が表示され(作成された配列の型がコードのふりよりも正確でないため)、それは非常に醜いです。このメソッドの作成者は、メソッド自体が安全でユーザーに配列を公開しないことを前提として、使用サイトで警告を出すのではなく、警告を受けた方がよいでしょう。
Dimitris Andreou、

1
ここではジェネリック配列を作成していません。コンパイラーが(汎用でない)配列を作成しました。
newacct

2

私はここでのパーティーに少し遅れていることを知っていますが、これらの回答のどれも私の問題を解決しなかったので、将来のGoogle社員を助けることができると思いました。しかし、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());    

2

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

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

これは不可能だったので、回避策として以下を使用しました。

  1. Stack(MyStackなど)の非ジェネリックラッパークラスを作成しました
  2. MyStack []スタック=新しいMyStack [2]は完全に機能しました

醜いですが、Javaは満足しています。

注:質問へのコメントでBrainSlugs83によって言及されているように、.NETでジェネリックの配列を持つことは完全に可能です


2

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がスローされないことを期待しています。


0

確かにそれを回避する良い方法があるはずです(おそらくリフレクションを使用します)ArrayList.toArray(T[] a)。私は引用します:

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

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

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

それで、誰がどのようtoArray(T[] a)に実装されているか知っていますか?


3
List.toArray(T [])が機能するのは、基本的に実行時にコンポーネントタイプTを与えるためです(必要な配列タイプのインスタンスを与え、そこから配列クラスを取得し、次にコンポーネントクラスTを与えます)。 )。実行時の実際のコンポーネントタイプでは、を使用して常にその実行時タイプの配列を作成できますArray.newInstance()。コンパイル時に不明なタイプの配列を作成する方法を尋ねる多くの質問で言及されていることがわかります。しかし、OPは、なぜnew T[]構文を使用できないのを具体的に尋ねていました。これは別の質問です
newacct

0

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


0

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

私が考えることができる唯一の理由は、可変引数- foo(T...)です。さもなければ、それらは完全に一般的な配列型をスクラブしたかもしれません。(まあ、varargsは1.5より前には存在しなかったため、varargsに配列を使用する必要はありませんでした。これはおそらく別の間違いです。)

だからそれは嘘です、varargsを介してジェネリック配列インスタンス化できます!

もちろん、一般的な配列の問題はまだ現実です。

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

この例を使用して、ジェネリック配列の危険性を実際に示すことができます。

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

そしてfoo2、私たちは、仕様が私たちを遠ざけると主張する問題から私たちを遠ざけるという主張に反論するように指摘することができます。Sunが1.5のためにより多くの時間とリソースを持っていれば、彼らはより満足のいく解決策に到達できたはずだと思います。


0

他の人がすでに述べたように、もちろんいくつかのトリックを介し作成できます。

ただし、お勧めしません。

なぜなら型消去、より重要なのはcovariance、単にサブタイプの配列を可能にする配列では、実行時原因となる値の背中を取得しようとするときに、明示的な型キャストを使用するために強制的スーパータイプの列に割り当てることができるClassCastException主要な目的の一つでありますジェネリックが排除しようとすること:コンパイル時のより強力な型チェック

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

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


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


0

クラスがパラメーター化された型として使用する場合、T []型の配列を宣言できますが、そのような配列を直接インスタンス化することはできません。代わりに、一般的なアプローチは、次に示すように、Object []型の配列をインスタンス化し、T []型にナローイングキャストを行うことです。

  public class Portfolio<T> {
  T[] data;
 public Portfolio(int capacity) {
   data = new T[capacity];                 // illegal; compiler error
   data = (T[]) new Object[capacity];      // legal, but compiler warning
 }
 public T get(int index) { return data[index]; }
 public void set(int index, T element) { data[index] = element; }
}

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.