質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.23%

Javaのジェネリクスについて

解決済

回答 5

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,861
退会済みユーザー

退会済みユーザー

Javaのジェネリクスについて調べていると、以下のような記事を目にしました。

以下引用です。(http://qiita.com/pebblip/items/1206f866980f2ff91e77)
コンパイル時に型情報は消去される
パラメータ化された型や、型パラメータの持つ型情報はコンパイラによって消去される。これは型消去(type erasure)と呼ばれる。
例えば、以下の型変数Tを持つジェネリック型を考える。
ContainerはIntegerやBigDecimalなどのNumber型のみの値を保持するコンテナクラス。

class Container<T extends Number> {
    private T value;    
    public Container(T value) {
        this.value = value;
    }   
    public T get() {
        return value;
    }
}


コンパイラは、上記のクラスから型情報を消去し、以下のクラスと同等のクラスを生成する。

class Container {
    private Number value;
    public Container(Number value) {
        this.value = value;
    }
    public Number get() {
        return value;
    }
}

型情報といっても実際に消去するわけではなく、型パラメータはその上限境界の型(上の例ではNumber)に置換される。
このように、型情報はコンパイラによって消去されるため、実行時には型情報を取得することはできない。

よくわからないのですが、上の例でいうと、Container<Integer>としていても、下のコードのように型パラメータは全てNumberになるのでしょうか。
回答お願いします。

補足です。
コンパイル時には型情報というのは消えてしますようですね。
しかしながら、例えばインスタンスとして、以下のように呼び出したとします。

System.out.println(c.get());

そうすると、getメソッドの返り値はInteger型になると思います。
これは呼び出すときに、<Integer extends Number>としているので、JVMが返り値がInteger型であることを認識してくれるからですね。
ここまでは理解できましたが、そうすると、なぜインスタンスを生成することはできないのでしょうか。

class Illegal<T> {
    public T create() {
        return new T();
    }
}


このコードのようにしてインスタンスを生成することはできないというのはなぜでしょうか。
呼び出す側できちんと情報はあるはずなので、インスタンスを生成し、返すことは可能だと考えられるのですが。。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • asahina_dev

    2016/08/08 21:30

    ちなみに「消去されない」だったりする  http://qiita.com/asahina_dev/items/2a42e50ec82a492764bf の方法で総称型のクラス名(クラスオブジェクト)を取得できているので

    キャンセル

  • 退会済みユーザー

    退会済みユーザー

    2016/08/08 22:01

    なぜでしょうか。コードを見ても消去されないというのがわかりませんでした。

    キャンセル

  • yohhoy

    2016/08/10 14:08

    asahina_devさんが指摘するように、(条件付きですが)リフレクション機能によってジェネリクスの型パラメータにアクセスすることはできます。ただ、挙げられている例は、Seasar2フレームワークの機能を利用しているのでこれだけ見ても訳が分からないですし、AbstractDao<E>から派生されたクラスでしか機能しませんね...

    キャンセル

回答 5

checkベストアンサー

+3

class Container<T extends Number> {
...


上の例でいうと、Container<Integer>としていても、下のコードのように型パラメータは全てNumberになるのでしょうか。

なります。ジェネリクス型Containerの型パラメータTには上限境界ワイルドカード(upper bounded wildcard)T extents Numberが指定されていますから、バイトコード上では型消去によりTNumberへと置換されます。(もしワイルドカード指定が無ければTObjectへ置換)


コンパイル時には型情報というのは消えてしますようですね。
...
これは呼び出すときに、<Integer extends Number>としているので、JVMが返り値がInteger型であることを認識してくれるからですね。

技術的にはあまり正確ではありません。「コンパイル時に型情報が消える」の意味を誤解しているように見うけられます。

ジェネリクスによる型消去は、ジェネリクス型(この例ではContainer)のコンパイルで行われます。一方、ジェネリクス型を利用する側のコンパイル時には、ダウンキャスト処理(型パラメータに指定したIntegerへのキャスト)が暗黙に生成されます。

しかしながら、例えばインスタンスとして、以下のように呼び出したとします。

System.out.println(c.get());


そうすると、getメソッドの返り値はInteger型になると思います。

なりません。型消去の結果としてJVMから見えるgetメソッド戻り値型はNumberとなっています。一方の呼び出し側では、コンパイラによりInteger型へのダウンキャスト処理が自動的に埋め込まれており、JVMはこれに従ってNumberIntegerへと変換します(コンパイル時に型検査済みのため、このダウンキャストは必ず成功する)。

型消去後のContainerクラスバイトコードは下記に相当し(質問者の理解で正しい):

class Container {
    private Number value;
    public Container(Number value) {
        this.value = value;
    }
    public Number get() {
        return value;
    }
}

ジェネリクス型の利用側では、下記のように記述したのと同じバイトコードが生成されます:

System.out.println((Integer) c.get());

class Illegal<T> {
    public T create() {
        return new T();
    }
}


このコードのようにしてインスタンスを生成することはできないというのはなぜでしょうか。
呼び出す側できちんと情報はあるはずなので、インスタンスを生成し、返すことは可能だと考えられるのですが。。

Tのコンストラクタを呼び出すコード、つまりジェネリクス型Illegalクラスのバイトコード中には、型消去により「実際に指定された型」情報が既に失われています。そのため、ジェネリクス型を作成したプログラマが期待する動作「createメソッド中でT型としてインスタンス生成する」ことは不可能であり、Javaコンパイラはこのソースコードをエラーとして拒絶します。

追記: 仮にIllegal<T>クラスのコンパイルを通した場合、型消去により下記相当のバイトコードに展開されるでしょう。

class Illegal {
    public Object create() {
        return new Object();
    }
}


ジェネリクス型の利用側ソースコードは次のようになるでしょうが、

Illegal<Number> num = new Illegal<Number>();
Number n = num.create();


生成されるバイトコードは下記に相当します。ジェネリクスに指定した型パラメータ(T = Number)に関係なくnum.create()は常にObject型インスタンスを返すため、Number型へのキャストは常に失敗しClassCastExcetion例外がスローされるという危険かつ無意味な処理となっています。このため、JavaコンパイラはIlligalクラスのようなソースコードをエラー扱いします。

Number n = (Number) num.create();

Javaのジェネリクスで実際に何が行われるかを深く理解したければ、Java Generics FAQs - Under The Hood Of The Compiler  をお勧めします。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/08/13 23:43 編集

    ようやく理解できた気がします。
    まず、Container<T extends Number>はコンパイル時に型消去により、TはNumberに置換される。
    呼び出す側で、Container<Integer>とすることで、Containerに格納できるのはそもそもInteger型のみになる。(この時に別の型を入れようとすると、コンパイルエラーになる)
    したがって、取り出す時に出てくる変数はInteger型であることが保証されるので、メソッドを呼び出した時ダウンキャストは成功する。

    一方で、ジェネリクスIllegalの方はオブジェクト型のインスタンスを生成することになり、私の挙げた例だと、当然Object型はNumber型に代入することはできないので、ClassCastExceptionがスローされてしまう。

    こんな感じかと思いました。

    本当に申し訳ないのですが、一つ質問させてください。
    「Java言語のオブジェクト指向では、「静的な/ソースコード上の変数の/コンパイル時の型」と「動的な/インスタンスの/実行時の型」の2種類が存在することは理解されていますか?」

    これはジェネリクスの話ですか?

    キャンセル

  • 2016/08/15 10:12

    > こんな感じかと思いました。

    その解釈で正しいです。

    > これはジェネリクスの話ですか?

    Java言語での一般論です。ジェネリクスに限定した話ではありません。コメントでのやりとりを鑑みて、念のためベース知識に関する確認をしただけです。

    キャンセル

  • 2016/08/16 17:26

    長々とおつきあいいただき本当にありがとうございました。
    よろしかったら、別の質問にもお付き合い願います。

    キャンセル

+2

はい、ジェネリクス自体があとからJavaに追加されたということもあって、ジェネリクス上の<T extends Number>のような型はJavaのソースレベルのみの存在となっていて、クラスファイルにコンパイルすると、ジェネリクスで受け入れられるいちばん上位の型(限定しきれなければjava.lang.Object)として処理されます。

ということで、下のようにContainer<T extends Number>でのT型はNumber型としてコンパイルされますが、返り値がT型となるようなメソッドを作った場合にはJava側でキャストしてくれるので、ちゃんとIntegerとして受け取れます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/08/08 09:54

    回答ありがとうございます。
    最後の文章「返り値がT型となるようなメソッドを作った場合にはJava側でキャストしてくれるので、ちゃんとInteger型として受け取れます。」について、型情報が消えるのに、どうしてメソッドの返り値だけは残っているのでしょうか。

    キャンセル

  • 2016/08/08 10:00 編集

    「呼んだ側」ではきちんと型がわかっているので、呼んだ側の変数としては適切な型としてハンドリングするようにコンパイルできます。

    キャンセル

  • 2016/08/08 10:18

    読んだ側ではきちんと型はわかるのでしょうか。
    例えば、あるクラスが下のように呼び出したとします。
    Container<Integer extends Number> c = new Container<Integer extends Number>(3);
    System.out.prntln(c.get());
    まず、Containerクラスの中身の型コンパイルするときにはNumberになっているのですよね?
    そして、よび出す側でContainer<Integer extends Number>とすることでIntegerだとわかるということですか。
    すいません、非常に混乱してます。。

    キャンセル

+1

結局のところ、汎用性の高いコードを書く際に、
プログラマがキャストとか気を付ける必要のあった処理を、
ある程度コンパイラが肩代わりすることによって、
汎用性と安全性を確保できるようにした仕組みが、ジェネリクスってことだと思います。

あくまで肩代わりなので、
元々プログラマが書くことが出来ないような処理は、
ジェネリクス使っても出来ないということです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

書かれている説明の通りで言うと、Integerに置換されるのではないでしょうか。
T extends Number だと、LongやBigDecimalなども受けれるわけですが、
T extends Integer だと、Integerかそのサブクラスしか受けれなくなるわけですから、
全然変わってきますよね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/08/08 09:42

    ん?ちょっと問いと答えが合ってませんね。すみません。
    T extends Number のクラスを、Container<Integer>として使用した場合の話ですね。
    おっしゃる通り、Number型として受け取りますね。
    インスタンスはIntegerですが。

    キャンセル

  • 2016/08/08 09:57

    Integer型の変数をNumber型として、受け取るということですね?
    そうすると、getメソッドはどうなるのでしょうか。
    返り値はIntegerになるみたいですが、、

    キャンセル

0

返り値としてジェネリクスの型を扱う際には、コンパイル後はキャストで整合性を取っています。
T extends Numberの場合、扱う型がNumberとして扱えることは確定なので、コンパイル後はNumber型として書いたようにコンパイルされます。これをIntegerという型を使ってインスタンス化した場合、getメソッドは返ってきたNumberをIntegerにキャストすることで、あたかもgetメソッドの返り値がIntegerであるようにしているのです。

このコードのようにしてインスタンスを生成することはできないというのはなぜでしょうか。 

当該クラスがこのコードで使っているような「引数無しのコンストラクタ」を持っている保証がありません。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/08/08 22:03

    このブログには「型パラメータは消去されるため、型Tのインスタンスを生成することはできない。」と書いています。
    この記述がおかしいのでしょうか。

    キャンセル

  • 2016/08/08 23:57

    ごめんなさい、何に対する質問でしょうか?

    キャンセル

  • 2016/08/09 10:04

    補足に対するswordoneさんの回答に対する質問です。
    型情報が呼び出す側で保持されるので、この情報を用いてメソッドの返り値に対して、キャストするから、返り値の型はジェネリクスで指定したものになります。
    しかしながら、このブログでは「型パラメータは保持されるため、型Tのインスタンスを生成することはできない。」と書いています。
    ですが、型パラメータは呼び出す側で保持されるのであれば、インスタンス生成も可能ではないのかと私が質問したところ、swordoneさんは「当該クラスがこのような引数なしのコンストラクタを持っている保証はない。」と回答されました。
    確かに、おっしゃる通りなのですが、このブログの言いたいことではないと思うのです。
    ブログが正しければ、当該クラスが引数なしのコンストラクタを持っていたとしても、インスタンス生成はできないと思うのです。
    したがって、私の質問は「当該クラスが引数なしのコンストラクタを持っていた場合、インスタンス生成はできないと思われますが、それはなぜでしょうか。」
    となります。
    わかりづらくて申し訳ありません。

    キャンセル

  • 2016/08/09 10:48

    持っているか持っていないかではなく、「持っている保証がない」ので、そのコンストラクタを持っていない可能性のあるクラスに対して、そのコンストラクタを起動するような記述は出来ません。コンパイルの時点で型安全性が保たれず、弾かれることになります。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.23%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる