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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

5回答

5821閲覧

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

退会済みユーザー

退会済みユーザー

総合スコア0

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

2グッド

0クリップ

投稿2016/08/08 00:24

編集2016/08/08 11:07

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になるのでしょうか。 回答お願いします。 補足です。 コンパイル時には型情報というのは消えてしますようですね。 しかしながら、例えばインスタンスとして、以下のように呼び出したとします。 ```Container<Integer extends Number> c = new Container<>(3); System.out.println(c.get());

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

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

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

ikuwow, yohhoy👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

iwamoto_takaaki

2016/08/08 01:47

引用については、引用元を表記が必要です。 与太話なのか、公式情報なのか判別する必要があるからです。 何より、著作者への礼儀として入れる必要があります。
退会済みユーザー

退会済みユーザー

2016/08/08 01:49

了解です。ご指摘ありがとうございます。
退会済みユーザー

退会済みユーザー

2016/08/08 13:01

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

2016/08/10 05:08

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

回答5

0

ベストアンサー

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/09 07:29

編集2016/08/12 02:38
yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/08/10 02:33

非常にわかりやすい解説ありがとうございます。 しかし、最後のインスタンス生成の部分がよくわかりませんでした。 このジェネリクス型Illegalクラスのバイトコード中には、型消去によりTの情報は失われ、Tはこの場合、Object型になる。 そして、呼び出す側では Illegal<Number> num = new Illegal<Number>(); Number n = (Number) num.create(); のようになるのではないでしょうか。
yohhoy

2016/08/10 05:01 編集

仮にコンパイルが出来てしまった場合、ご指摘の通り「Illegalクラスのバイトコード中には、型消去によりTの情報は失われ、Tはこの場合、Object型になる。」「呼び出す側では Number n = (Number) num.create();」という動きになるでしょう。これは、Object型のインスタンスをNumber型にダウンキャストする処理ですから、実行時にClassCastExceptionがスローされます。 そもそもジェネリクスの導入目的は、実行時のダウンキャスト失敗リスクをなくすことですから、このような危険なコードを許容せずコンパイルエラーとします。 別の観点では、Illegal.create()を書いたプログラマの意図は「ジェネリクスに与える型パラメータTのインスタンス」を生成したいのであり、決してObject型を生成したいわけではありません。その意味でも、このコンパイラの振る舞いを許容する意味はありません。
退会済みユーザー

退会済みユーザー

2016/08/10 11:45

うーん、イマイチつかめていないのですが、Object型のインスタンスをNumber型にダウンキャストされるからClassCastExceptionがスローされるとのことですが、コンパイル時にそれはチェックされているのではないでしょうか。 なので、そのダウンキャストは確実に成功するわけですから、エラーにはならないのではないかと思うのです。
yohhoy

2016/08/10 12:06

前提として、ジェネリクス型クラスと利用する側のクラスは独立してコンパイルされ、それぞれがバイトコードに変換されます。ジェネリクス型クラスではコンパイル時に型消去が行われるため、利用側がどのようなパラメータ型を指定してくるかを、JVMがメソッドcreateの実行時に知ることができません。 > コンパイル時にそれはチェックされているのではないでしょうか。 原理的にチェック不可能です。ジェネリクス型Illegalのコンパイル時には、型パラメータTに何が指定されるのを知ることができません。このため、createメソッドのnew Tは単純にnew Objectへと置換することしかできません。 > そのダウンキャストは確実に成功するわけですから 逆です。このコードを許容してしまうと、ダウンキャストは常に失敗します。Illegal.createメソッドは常にObject型インスタンスを返しますが、パラメータ型Tのクラスではないため、確実にダウンキャスト失敗することが明白です。(TにObjectを指定すれば動きますが、この場合はジェネリクスにする価値が最初からありませんね)
退会済みユーザー

退会済みユーザー

2016/08/11 01:34

ジェネリクス型Illegalに関しては、型パラメータTはObjectになり、creatメソッドはObject型のインスタンスを返すから、パラメータ型Tのインスタンスではないので、ダウンキャストに失敗する。 一方で、メソッドの返り値に関しては、呼び出す側で指定した型パラメータへのダウンキャストが行われるから、返り値は型パラメータの型になる。 この違いがよくわかりません。 一方ではダウンキャストが成功する(コンパイル時にそのダウンキャストが成功するかのチェックがなされる)のに、他方ではダウンキャストが失敗するのでしょうか。
yohhoy

2016/08/12 02:01 編集

後半説明を少し増補しました。 > この違いがよくわかりません。一方ではダウンキャストが成功する(コンパイル時にそのダウンキャストが成功するかのチェックがなされる)のに、他方ではダウンキャストが失敗するのでしょうか。 ダウンキャストの成否"だけ"に着目するのではなく、ソースコードが表している(表現しようとした)意味と、それを実行した(実行できてしまった)時に起こる事象をセットで解釈してください。問題の本質は「ジェネリクス型クラス自身のソースコードをバイトコードに変換(コンパイル)するとき型消去が行われる」ことです。また、ジェネリクスの目的は「コンパイル時に型検査を行うことで、実行時のキャスト例外を防ぐ」ことです。Containerの例はジェネリクスにより安全性がもたらされますが、Illegalの例はジェネリクスの目的に反しており禁止されるべきコードです。
退会済みユーザー

退会済みユーザー

2016/08/12 14:37

何度も何度も本当に申し訳ないのですが、ジェネリクスにより安全性がもたらされるというのがイマイチ理解できません。 Containerの例に関しては、まず型消去により型パラメータはNumberになります。 呼び出す側のコンパイル時に型検査(IntegerはNumberの子クラスであるということを確かめた)を行うことで、どうしてキャスト例外を防ぐことが可能になるのでしょうか。 結局NumberをIntegerにダウンキャストしているのですよね? Doubleだったりする可能性もあるのではないでしょうか。 結局私はジェネリクスによりもたらされる安全性というのがイマイチ理解できていないように思えます。 ただ単に、キャストを省略してくれる機構だったり、クラスをさらに抽象化した存在とかそういう風に捉えておりました。
yohhoy

2016/08/12 15:17 編集

> Containerの例に関しては、まず型消去により型パラメータはNumberになります。 呼び出す側のコンパイル時に型検査(IntegerはNumberの子クラスであるということを確かめた)を行うことで、どうしてキャスト例外を防ぐことが可能になるのでしょうか。 Java言語のオブジェクト指向では、「静的な/ソースコード上の変数の/コンパイル時の型」と「動的な/インスタンスの/実行時の型」の2種類が存在することは理解されていますか?Containerの例では、型消去が行われたContainerクラスバイトコード中の静的な型はNumberとして扱いますが、そこで保持するインスタンスの動的な型(Integer型)の維持されます。ジェネリクスによって、Container<Integer>の利用者には「これから扱う動的な型がInterger型であること」を宣言し、この制約を「コンパイル時に」検査することができます。 > Doubleだったりする可能性もあるのではないでしょうか。 ジェネリクスを適切に使う限り、その可能性はゼロです。Container<Integer>に対してDouble型のインスタンスを格納しようとすると、コンパイルエラーとして検出されます。これこそがジェネリクスがもたらす安全性です。ジェネリクスがなければ(Java 1.4以前の時代)、"静的な型Numberを保持するContainerクラス"しか存在せず、Number型から派生するDouble型インスタンスでもInteger型インスタンスでも自由に格納できてしまいました。問題はインスタンスの格納時ではなく、取得時(get)に顕在化します。設計上は"静的な型Numberを保持するContainerクラス"にInteger型インスタンスしか保持しないと決めても、プログラマが誤ってDouble型インスタンスを格納していた場合、静的な型Integerとして取り出すためのダウンキャスト時に例外がスローされます。これは、直接的な問題箇所(Double型インスタンスの格納時)ではなく、値を取り出すときに初めて例外スローが遅延するため、問題究明やデバッグを困難にします。 > 結局私はジェネリクスによりもたらされる安全性というのがイマイチ理解できていないように思えます。 そうかもしれません。ジェネリクスがなぜ導入されたのか(導入以前の問題は何か)、導入によりどんな利点があるかを整理された方が良いと思います。 > ただ単に、キャストを省略してくれる機構だったり、クラスをさらに抽象化した存在とかそういう風に捉えておりました。 「キャストを省略できる」はジェネリクスによりもたらされる一側面ではありますが、それが目的ではありませんね。真の目的は「実行時のキャストエラーを防ぐ(=安全性の向上)」です。billさんの仰る「クラスをさらに抽象化」が何を指すかはわかりませんが、ジェネリクスは既存の何かを抽象化する機能ではありません。 余談:もしかしてC++言語のテンプレートやC#言語のジェネリクスをご存知でしょうか?それらとJava言語のジェネリクスは本質的な部分がかなり異っています。ご注意ください。
退会済みユーザー

退会済みユーザー

2016/08/13 15:25 編集

ようやく理解できた気がします。 まず、Container<T extends Number>はコンパイル時に型消去により、TはNumberに置換される。 呼び出す側で、Container<Integer>とすることで、Containerに格納できるのはそもそもInteger型のみになる。(この時に別の型を入れようとすると、コンパイルエラーになる) したがって、取り出す時に出てくる変数はInteger型であることが保証されるので、メソッドを呼び出した時ダウンキャストは成功する。 一方で、ジェネリクスIllegalの方はオブジェクト型のインスタンスを生成することになり、私の挙げた例だと、当然Object型はNumber型に代入することはできないので、ClassCastExceptionがスローされてしまう。 こんな感じかと思いました。 本当に申し訳ないのですが、一つ質問させてください。 「Java言語のオブジェクト指向では、「静的な/ソースコード上の変数の/コンパイル時の型」と「動的な/インスタンスの/実行時の型」の2種類が存在することは理解されていますか?」 これはジェネリクスの話ですか?
yohhoy

2016/08/15 01:12

> こんな感じかと思いました。 その解釈で正しいです。 > これはジェネリクスの話ですか? Java言語での一般論です。ジェネリクスに限定した話ではありません。コメントでのやりとりを鑑みて、念のためベース知識に関する確認をしただけです。
退会済みユーザー

退会済みユーザー

2016/08/16 08:26

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

0

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

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

投稿2016/08/08 00:38

maisumakun

総合スコア145183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/08/08 00:54

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

2016/08/08 01:01 編集

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

退会済みユーザー

2016/08/08 01:18

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

0

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

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

投稿2016/08/09 02:15

abs123

総合スコア1280

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

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

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

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

投稿2016/08/08 03:44

編集2016/08/08 12:23
swordone

総合スコア20651

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/08/08 13:03

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

2016/08/08 14:57

ごめんなさい、何に対する質問でしょうか?
退会済みユーザー

退会済みユーザー

2016/08/09 01:04

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

2016/08/09 01:48

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

0

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

投稿2016/08/08 00:39

root_jp

総合スコア4666

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

root_jp

2016/08/08 00:42

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

退会済みユーザー

2016/08/08 00:57

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問