goo blog サービス終了のお知らせ 

コンピュータ・プログラミング日誌

記録をつけていきます。

ワイルドカードのジェネリクス、不変、共変、反変

2019-11-04 17:07:20 | Java
昭和情報プロセス株式会社から発行されている山本道子氏著のJava Gold SE8であるが、必要最低限の著述でまとめられていて、出来上がりがそこそこである。

しかし、Java Gold SE8と言う分野に特化して著述したためか、Javaの広範な知見に対する紙幅が許されず、あえなく除外した部分があると思われる。

同著のP88のコレクションとジェネリクスの部分を読むと、その中腹あたりに

boolean containsAll(Collection c)

と言う表現が見られる。

この不等号で囲われているはてなマークの部分が何を示すのか、前段で出てこない。
後述でも出てこない(類型は出てくる)。

このに対する説明がないので、ここで補足を行う。

とはジェネリクスの一つで、ジェネリクスとはクラス定義の引数について、汎用的に指定できる型を意図的に曖昧に指定しておくことで広範に使えるようにしたワイルドカードである。

この本の後述には、


の二種が出てくるが、単体の説明はない。

これを説明しているページがあったので引用する。

https://www.thekingsmuseum.info/entry/2016/04/02/155821

非境界ワイルドカード型 (unbounded wildcard type)
具体例:List list
境界ワイルドカード型 (bounded wildcard type)
上限境界ワイルドカード型 (upper bounded wildcard type)
具体例:List list
下限境界ワイルドカード型 (lower bounded wildcard type)
具体例:List list

と言うことで、extendsもsuperもしばりがない汎用型がであるらしい。

関数型インターフェースが分からねええええ と言う方のための解説

2019-11-03 23:57:17 | Java
関数型インターフェースである。
これに接した時、解説が全然無かったのでさっぱり分からなかった。
よって私がここに書く。

昭和情報プロセス株式会社発行のJava Gold SE8(山本道子)の74ページを見ると、次のようなコードが書いてある。

String str = new Function() {
public String apply(String str) {
return "Hello " + str;
}
}.apply("naoki");
System.out.println(str);

と言うことでこれは比較的間tなんな部類なのだが、本当分からなかった。
ちなみに以降の解説は匿名クラスの説明を略しているので、分からない人は先に匿名クラスについての学習をお願いします(無責任)。
また、上記本を74ページまで通読されていることを前提にして書きますので細かい説明は省略。

さて、まずFunctionなるものが関数型インターフェースと言うことは分かるのだが、この引数のとは一体なんじゃらほい、と言うことなのである。
また、それに引数を与えるために、「.apply("naoki")」とは書いているものの、このFunctionには引数らしきものが2つ書いてあるのに、なぜ一つしか与えていないのか? 2つ目はどこに行った? と言うことなのである。これを解説したい。

まず結論から言えば、Functionの不等号マーク<>で囲まれている引数らしきものは、この関数型インターフェースであれこれ使用する要素であって、返り値だったり、引数だったりしますよー、と言うことなのである。この場合では、1つ目のStringが「引数」、2つ目のStringが「返り値」らしい。
(なので、BiFunctionなどは3つ引数らしきものがあるが、前二つ(左端と中央)の要素が渡す引数を示すらしく、一番後ろ(一番右)が返り値の型を示すらしい)。

javaでの継承の概念が分からなかった話。

2019-11-03 21:22:02 | Java
Javaの継承概念が分からなかった話を書く。
本当はキータとかに書くべきなのだろうけれどもここに書く。
同じように悩まれている方はご参考に。


私はJavaの継承概念がよく分からなかった。

例えば、昭和情報プロセス株式会社から発行されている、Java Gold SE8(山本道子著)の61ページを読むと次のように書かれている。

class Super {}
class Sub extends Super {}
class Foo {}

Super obj1 = new Sub();
Sub sub1 = (Sub)obj1; // ①OK
Foo obj2 = new Foo();
Sub sub2 = (Sub)obj2; // ②NG コンパイルエラー
Super obj3 = new Super();
Sub sub3 = (Sub)obj3; // ③NG 実行時エラー

と言うことで本当全然分からん。

まず私は、extendsによる継承と言うものをロボットの部品のようなものと思っていた(これが勘違いのモト)。

だってそうじゃない? 
Superによって継承されたSubには、そのSub内で色々な機能がどんどんひっついてくるんだよ? 

元々Superには頭と胴体しか定義されていなかったのが、Subに継承されて手と足がくっついて、そこから継承されたSub2には、ロケットパンチと飛行機能がついて、そこから継承されたSub3には通信機能がついて、そこから継承されたSub4には合体機能がついて・・・

そうするとまず、
Super obj1 = new Sub();
でSuper型のobj1にnew Sub()が入れられる理屈が分からない。

なぜかというと、上記を前提にして言えば、手足付きのロボットの実態が、手足なしのロボットの型の変数に入れられるんだよ? おかしくないか? そこで機能が制限されたりしないのか? 

と言うことで悶々としていたのだが、やっと悩みが解消された。
上記のロボットモデルは間違っとる。
extendsは拡張機能だと思っていたのが誤りだったのだ。


と言うことで新しいモデルを提示してみる。
概念的属性が必要になるのであって、ここでは、源頼朝をモデルにしよう。
源頼朝を考えると、この人はまず「人間」と言うくくりであるが、その後に「源氏」と言うくくりに入る。そしてその源氏の中でも「頼朝です」と言う属性がつくことになる。

つまりは、
class Human {
//こいつは人間です。
}

class Genji extends Human {
//こいつは人間の中でも源氏です。
]

class MinamotonoYoritomo extends Genji {
//こいつは源氏の中でも頼朝と言う奴です。
}

と言う属性をどんどん付与していくことによって、その具象性を与えることができると言う意味において、その範囲を狭めるのだ。

一番最初の書き方をもう一度書いてみよう。

Super obj1 = new Sub(); //源頼朝をnewして源氏に入れる。概念的には入るのでOK。
Sub sub1 = (Sub)obj1; // 型は源氏だが、実態として源頼朝が入っているobj1を源頼朝型にする。OK。
Foo obj2 = new Foo(); //平清盛を平清盛型に入れる(実態は平清盛)。
Sub sub2 = (Sub)obj2; // 平清盛型の実態は平清盛データを源頼朝型にするのはNG。合ってないので、コンパイルエラー。
Super obj3 = new Super();//源氏データをnewして源氏型に入れる。
Sub sub3 = (Sub)obj3; // 源氏型の源氏データを源頼朝型にしようとしてもできない。NGで実行時エラー。

と言う具合になる。なるほど。


あともう一つ分からないのがコンパイルエラーと実行時エラーに分かれていること。
本当どう言うことやねんと。実行時にエラーになるんだったら、コンパイル時にエラーで拾っとけやと思うのだが、そういう風にコンパイラは動いていないらしい。

どうもコンパイラのコンパイル可否判定については、「記載されている構文の一行一行が正しいか確認していくが、確認する範囲の単位はその一行のみであって、前後構文との矛盾性があってもスルーする。他は見ない。」と言うものらしい。

よって、コンパイラ的には、
Super obj3 = new Super(); //←これはいけるやん! 
Sub sub3 = (Sub)obj3; //←前見てないから前後性知らんけど、これもいけるやん! 
と判定してしまうらしい。