ジェネリクス:総称型とは
Androidアプリ開発で、非同期処理の実装にAsyncTaskクラスを使っていた。
(なおAndroidの非同期処理はほかにもいくつか実装方法があり、AsyncTaskを使う方法はもう古いようだ。)
まあAsyncTask自体の話はどうでもよくて、そこで初めてジェネリクス(総称型)の実践を経験した。
AsyncTaskの情報を探すと、
android.os.AsyncTask<Params, Progress, Result>
とか
private class MyTask extends AsyncTask<Void, Void, Void> { ... }
とか出てくる。<~>って何? なんでそんな場所に書けるの? みたいな感じだった。調べてみたらジェネリクスなるものらしい。
結局AsyncTaskはなんとか動いたが、いまだにジェネリクスがよく分かってない。
APIの調べものとかしててジェネリクスが出てくると軽く緊張してしまう。
ということで、少しでも理解を進めていきたいな、というメモ書き。
ジェネリクス
総称型ともいい、あらゆる型がまとまっているモノ(型)を示すそうだ。
Javaだとすべてのクラスの元はObjectクラスで、すなわちすべての型の元はObject型なので、
ジェネリクス≒Object型
と考えた。どうやらそんなに間違ってはいない模様。
ただ、Object型が万能すぎて逆に使いにくかったのを、使いやすくしたのがジェネリクスてことみたいだ。
慣例的に、ジェネリクスの書き方は大文字のようで、以下が頻出ぽい。
<T>・・・Type、つまり型を入れるという示唆。これだけでよくね?と思ったがそうでもないらしく、以下が登場。
<E>・・・Element、つまり要素を入れるという示唆。配列やらコレクションやらの要素のことだろう。
<K>・・・Key、これは(Mapとかの)Keyを入れるという示唆。
<V>・・・Value、これは値なので、戻り値とか(Mapとかの)Valueを入れるという示唆。
複数の時は<T1, T2>みたいにカンマ区切りで。前述の
android.os.AsyncTask<Params, Progress, Result>
とかがその例だろう。
Object型だと、何でも入れられる反面、何が入っているかパッと見わからないし意図しない型の混入が起こる。
ジェネリクスの利点は、使うときに型を限定できるので、上記のような問題を防げる、ということのようだ。
(このへん、理解と語彙が足りず、うまいこと言えない)
クラスで使うなら、
class HogeClass<T>{ //フィールド private T t; //コンストラクタ public HogeClass(T t){ this.t = t; } //ゲッタ public T getT(){ return t; } }
こう書くと、HogeClassのインスタンスは以下のようにして得られる。
HogeClass<String> hogeClassStr = new HogeClass<String>("こんちは"); //右辺のStringは省略可 System.out.println(hogeClassStr.getT()); // → こんちは HogeClass<Double> hogeClassDbl = new HogeClass<Double>(3.14); //右辺のDoubleは省略可 System.out.println(hogeClassDbl.getT()); // → 3.14
ふむふむ。乱暴にまとめる。
▼
クラスの宣言の{}の前に<T>て書いとくと、インスタンス生成時に
クラス名<指定したい型> 変数名 = new クラス名<※指定したい型>(コンストラクタへの引数)
とできる。Java7以降では※は省略可能で、<>となり、これをダイヤモンド演算子と呼ぶそう。
▼
んでTは指定された型として、クラス(インスタンス)内で利用できる。
▼
このTは、メソッド宣言の仮引数とそれを利用するときの実引数の関係に似ていて、仮型引数・実型引数と呼ばれる。
ちょっと自信ないけど、「バインド」という説明を見かけたが、たぶんこのnewで指定した型がクラス(インスタンス)に適用されるさまを言っているのではないか。
文字通り、指定した型で「縛り付ける」的な?
▼
宣言の<T>は仮型引数なので<A>でも<fuga>でもいいんだけど前述のとおり、型=TypeのTという慣例を重んじるのが無難だろう。
・・・あー、そういうことか、
public interface Map<K,V>
という表記の言わんとしていることが分かった気がする。「K」eyの型、「V」alueの型、って伝えたいのね。
▼
Object型でやろうとすると、「どんな型も入っちゃうので自前で管理せねば」と気にする必要があり、また不適切なものを入れてもコンパイルは通ってしまう。
だがジェネリクスを使えば、指定した型以外を入れようとするとコンパイルでチェックしてくれる。
古いJavaではジェネリクスがなかったので、先人方はこういった、Object型ゆえの自前管理コストに苦しんでいた模様。
まずはクラスでの簡単な用法を通じて、ジェネリクスの理解がちょっと進んだかもしんない。
引き続き今度は、メソッドでの用法(引数と戻り値でのジェネリクスの利用)なども調べたいところだ。
JavaとサーブレットとJSPとMVC
あらすじ
MVCと三層クライアントサーバがごっちゃになって理解できないでいる。
owaraiurutorakuizu.hatenablog.com
まず、言葉で少しだけ知ってたつもりのMVCを再点検してみる。
MVCという設計パターン
Webアプリケーションの設計の仕方がいくつかあるようで、その中で比較的メジャー(ちょっと古いかも)なのが、MVC。まずはMVCを知れ、と数年前に習った。
小規模・単純なシステムなどの場合、プレゼン層(CとV)だけで足りる場合があるが、そうでないときはこう分けましょう、というものらしい。
・・・振り返ってみると、自分が関わったものって、CとMの線引きが不明瞭だったような気がする。Cの中にMが多数入り込んじゃってたイメージ。
三層クライアントサーバは一直線であり、概念
やっぱりMVCとは別物らしい。三角形じゃない、てのがキモだそうで。
アプリ層は、ロジック層などとも言う(他にもあるみたい)。
より古い二層(クライアントとサーバ)の時代ではクライアント側がロジックを持ってたりして大変だったので、そこからロジックを引き抜いてサーバ側に持たせる、としたのが三層~だそうだ。自分は、いきなり三層~のことだけを理解しようとしてMVCとごっちゃになってしまっていたが、こういった歴史的な背景をたどって整理したら、腑に落ちた感じだった。
システムの作り方というより、概念図みたいなモノ、と自分は捉えている。
つまり?
二者は別物。
MVCは設計のモデルとかパターンとか、どっちかというと作る人のためのハナシで、その中でも、見た目とか画面遷移とかロジックとかの作り分けの仕方を説いている。データ層とかDBアクセスは、ロジックのある部分のクラスやらBeansやらが手続きする対象というだけで、MVCの文脈の主たる部分ではない。たぶん。
三層~は、もっと抽象的な、概念とかそっちの世界に寄っている。昔は二層だったけどそっから進化したもの。データ層(DB)はメインキャストのひとり。
両者は対応するもの、対応させるべきもの、と考えていたのが、ハマりポイントだったのだなあ、というのが自分の結論。
MVCと三層クライアントサーバがよく分からない
ふと現時点での理解を、上っ面だけでも整理しようなどと思い立った。
応用情報技術者の勉強してて、三層クライアントサーバを初めて知ったが、
MVCと似たものかしら、などと思ったのが端緒。
MVC:
ModelとViewとControllerとに層(役割)を分担して幸せになる、とかそういうの。
Webアプリケーションの作り方の一つとして、有名だから覚えとけ、とか習ったけど、
Androidでアプリ作ってたときも、このアーキテクチャなのかなとか思ってた。
・Viewがユーザにとっての見た目とかインタフェース
・Modelが裏方のこまごました処理
・Controllerがそれら2者の仲立ち
くらいの認識でいる。
三層クライアントサーバ:
時代的にはMVCより古い? プレゼン層とアプリ層とデータ層。
なんか3つに分けるとかの考え方で、MVCの仲間だろう、とか考えてる。
字ヅラ的にこうやろ?
M <-> データ層
V <-> プレゼン層
C <-> アプリ層
・・・ちょっと調べてみたら、コレ全然違う。
前提誤認からの不毛な論争や誤解が多そう。あとオブジェクト指向が参戦してきてたりもする。
理解を固めるにはちゃんと調べないとダメだな。
経験則で言えるのは、Controllerがアホほど膨らんで何でも屋になり、
分けてる感なくね? と思いながらプログラム書いてたナー、てことくらいだ。
アーキテクチャのせいということを言いたいのではもちろんなく、自分の能力のせいだ。
三層クライアントサーバはもう古いぽいから、あんま知らんでもいいのかな?
MVCもレガシーでしょみたいな意見も多そうだ。
継続調査!
Javaの例外 ざっくりメモ
自分の中でJavaの例外の理解がフワフワで、エラーの親戚くらいの理解だったので、少し前進したい。
余談だが「フワフワ」の巧みな用法としては、ヘイポーの謝罪文における「~アメリカ人はセ〇〇スとスキンシップの境界がフワフワしており~」が挙げられると思います。
ともあれ、ちょっくら理解が進んだよメモを残す。
▼例外の概要
例外は、NullPointerなんちゃらExceptionとか、だいたい「Exception」て付いてたらきっと例外。
どうやら例外は、大きく2種類に分類可能らしい。
・チェック例外:try-catchでの処置が必須となる例外。処理しないコードはコンパイルで怒られる。検査例外とも。
・非チェック例外:上記以外。つまりtry-catchでの処理はあってもなくてもいい。非検査例外とも。
前者は、IDEがチェックしてサジェストしてくれる感じだと思う。
そしたら言われたようにtry-catch付けてやれば、コンパイルで怒られることは無くなる。
後者は、コンパイル通るんだけど、プログラム実行中に何らかの処理をしようとして落ちて、ログ見たら「あ、例外だ」みたいになるやつ。Nullなんちゃらとかはこっちの仲間だろう。
例外が発生したさまを、「例外を投げる」「例外が投げられた」などと言うみたいだ。
なるほど、例外で「throw(s)」という単語が出てくるのはそういうことか、と得心した。
まあ「エラーを吐く」とかと似たようなニュアンスなのだろう。
んで、投げられた例外を何も処置しないと、そこでプログラムが終了しちゃう。
よく見る、”エラーです!(赤字)" みたいなやつの原因は大体コレなのかな。
▼例外を処置してやる
例外の処置だが、
・本人:発生させる処理(メソッド)自身。
・呼び出し者:その処理(メソッド)を呼んだやつ。
のどちらかで処置できる。
▼例外を、呼び出し側で処理してやる形
本人側(定義側)では、自身の実装コードの中で発生しそうな例外に見当を付けて、それをthrowsで投げてやる。
投げつける先は、呼び出し側。つまり呼び出し側は、投げられてくるかもしれない例外をtry-catchで捕まえて、捕まえたときの処遇を記述する必要がある。
たぶん「例外を投げる可能性があるメソッド(~throws なんちゃらException とか書かれてるメソッド)を呼び出す側は、投げられてくるかもしれない例外への処置をtry-catch文で書いとかないとダメ」てことだろう。
//本人(定義) public void method() throws Exception{ //何か例外起きそうなコード } //呼ぶ側 try{ method(); } catch(Exception e) { //method()を実行して例外が発生した場合、ここに入る。 //例外eは、持ってるメソッドで例外の中身を表示したりとかできるようだ。 }
▼例外を、本人(メソッド自身)が処置する形
・ここに来たら例外を投げるようにしよう、と意図して例外を投げる。
・ここで例外発生するかもなのでtry-catchしとこう。
・・・といったことができるようだ。
//本人(定義) public void method(){ if(hoge == 0){ //ここに入ることは想定されないがもし入ってきたら・・・そうだ例外投げよう。 throw new Exception("Exceptionが発生したヨ"); } else if(・・・・){ try{ hogehogeMethod(); //例外を投げるかもしれない処理 } catch(Exception e) { //例外捕まえたときの処理を記述する。 ・・・・ } } } //呼ぶ側 method(); //呼ぶだけ。
▼そのほか
try-catch-finallyてのがあって、finallyブロックには必ず実行したい処理を書くことができる模様。
んで、新しいJavaだと、try(例外投げる処理)~みたいに書けるようで、try-with-resources 構文とかリソース付きtryとか言われるものらしい。
これまではfinallyに閉じる処理を書いてたけど、そうせずとも閉じることが保証される、とのことだが、ちょっと何言ってるか分かんないので別途調査しよう。
例外は自分で作ることもできるみたいだ。これも別途調査しよう。
最初からある例外も、自作の例外も、親としてExceptionクラスを共有する。
よって、例外を投げたり捕まえたりする際、Exceptionと記述すればあらゆる例外をひっかけられる。
が、それだと例外のこまかな種別に応じた処理を書き分けるのが無理(困難)なので、適切な種別のExceptionを使い分けるのが良い作法のようだ。
分かってる人が見るとかなり印象悪いようなので気を付けたい。
例外をcatchするがそのcatch時の処理を何も書かないことを、例外を「食べる」とか「もみ消す」とか言うらしく、同様のスラングに「キャッチアンドリリース」てのがあるそうで少し笑った。今度使ってみたい。
なおこれやっちゃうと、実は問題ある動作をしてるのにそれをもみ消してるせいで問題無くプログラムが動いてるように見えてしまう、てことになって、バグを潜在させてしまう問題行動のようだ。
・もみ消すくらいなら最初からcatchせずに例外でプログラム異常終了させるほうが、気づきやすいぶんまだマシ。
というのが正論だろうか。かたや面倒事を隠蔽したい邪なPGなどは、
・ここ例外投げることあるけどよく分かんねーし解析とかめんどいからもみ消しちゃえ。
みたいに考えたりするのかもしれない。ダークサイド堕ちしないようにせねば。これも気を付けたい。