Javaプログラマであるかを見分ける10の質問 - やさしいデスマーチ
"自称Javaプログラマを見分ける"や"問題なく答えられる"と、軽く答えるには高いハードルな気もしますけれど、今日時点の私のスナップショットとして晒しておきます。決して露悪趣味があるわけではありません。
主に業務系Webアプリケーション開発で給料貰ってるプログラマです。ホーム言語はJava*1で経験年数3年も超えてます。回答する事自体がすっごくホラーです。
質問
- ==演算子とequalsメソッドの違いは何か?
- 文字列の連結は原則として+演算子を使ってはならない理由を説明せよ。
- List
のようにジェネリクス型を使う主たる目的は何か? - オブジェクトがガベージコレクション(GC)される主たる条件は何か?
- チェック例外と非チェック例外の違いを型と例外処理の観点で説明せよ。
- フィールドのアクセス修飾子をprivateにしgetter/setterメソッドを提供する事でフィールドを参照する設計方針を取る主な理由を説明せよ
- NullPointerExceptionが発生するのは主にどういう状況か?
- オーバーロードとオーバーライドの違いは何か?
- コンストラクタとは何か?
- インターフェイスを利用する目的を1つ説明せよ
回答
- 2つのオブジェクトを対象に、==演算子は同一かを、equalsメソッドは同値かを比較する。
- +演算子を使用すると本来の目的以上の余計なコストがかかることがあるため。
- タイプセーフのため。
- そのオブジェクトに到達できる参照がなくなったとき。
- 例外の型がチェック例外の場合は例外処理が必須。非チェック例外の場合は省略可。
- フィールドである事を外部に公開する必要がないから。
- 受け取った引数や、メソッドの戻り値が予期しないnullだった場合。
- オーバーロードはシグニチャを追加するもので、オーバーライドは振る舞いを変えるもの。
- オブジェクトの初期化処理を行うもの。finalメンバ変数の初期化が出来る場。
- テストしやすくなる!
真面目に答えました*2。*3
以下は説明というか、言い訳と言うか、解答欄(50文字)に書けなかった計算式的な何か。
==演算子とequalsメソッドの違いは何か?
equalsメソッドはオーバーライド出来ますが、==演算子はできません。演算子をオーバーライドできる言語もありますが、Javaは出来ません。ですが同一の値かを比較したい場合は非常に多いです。その為に用意されたのがequalsメソッドだと思っています。オブジェクト同士の比較に==演算子を使用する事は基本的に無いと考えて構いません。
equalsメソッドはあくまで「そのように実装しなさい」となっているメソッドであり、どう実装されているかは判りません。常にtrueを返すかもしれません。ですが論理的等価性を表すメソッドであり、多くのクラスがその内容に依存しているため、常にtrueを返すならばそういうオブジェクトとなります。
蛇足ですが、Objectクラスに実装されているequalsメソッドは以下のようになっているので、オーバーライドされない場合、thisがnullでなければ*4結果は同じになります。
public boolean equals(Object obj) { return (this == obj); }
文字列の連結は原則として+演算子を使ってはならない理由を説明せよ。
可読性考えると+演算子の方が上になったりしますけど、そもそもコード内のStringを見て可読性云々言うのは固定文字の連結くらいになるのでは。コード内でそんな頻繁に固定値の連結をするならちょっと考え直した方が良いと思います。
なお、固定値同士の結合であれば+演算子を使ったら結合後のStringでバイトコードが出来たりもするので、一概にパフォーマンス云々は言えません。
この話題を見るといつも String#concat(String) って使わないよなーと思っちゃいます。
Listのようにジェネリクス型を使う主たる目的は何か?
キャストを書く手間を無くす為とかも有りと言えば有りかなとよぎりました。キャストを書かずに済めば、ClassCastExceptionも起こらない訳で。でもそれは"主たる目的"とするには狭すぎる。コレクションでジェネリクス型*5を使う主たる目的はタイプセーフだと思います。コンパイル時にチェックできる。素晴らしい。Java1.4でキャストだらけでそこかしこで例外が出るコードを相手に取っ組み合いをした経験があるだけに非常にありがたいものでした。
ジェネリックメソッドの目的は少し変わる気もしないではないんだけど、やはりタイプセーフに収束するのかなーと思ったり。
オブジェクトがガベージコレクション(GC)される主たる条件は何か?
GCは面白くて便利で厄介なあれです。実装は色々とありますけれど、GC対象となる条件は参照が無くなった場合、つまりオブジェクトに到達が出来なくなった場合で良いと思っています。メソッドのローカル変数にのみ参照されているオブジェクトがメソッドの処理が終わった際にGC対象となるのは、唯一の参照であるローカル変数がスコープから外れた事により、そのオブジェクトに到達する手段が無くなる為です。
チェック例外と非チェック例外の違いを型と例外処理の観点で説明せよ。
詰まってしまった…。型と例外処理の観点。型を絡めて50文字…うぬー。学生の頃に解答欄が狭くて困った経験を思い出しました。
チェック例外と非チェック例外の挙動的な違いは例外処理(try-catchやthrows)が必須か否かになります。型で言うと、RuntimeExceptionおよびErrorを継承した場合は非チェック例外、その他はチェック例外になります。Exceptionが特別にチェック例外なのではなく、例外(Throwable)はチェック例外であり、RuntimeExceptionとErrorが特別に非チェックとなっているわけです。
void throwsException() throws Exception { throw new Exception(); } void throwsRuntimeException() { throw new RuntimeException(); } void throwsError() { throw new Error(); } void throwsThrowable() throws Throwable { throw new Throwable(); }
余談ですが、例外処理は全て非チェック例外で良いと思っています。業務システムでは例外処理でオブジェクトの状態を復元して正常な状態とする、なんて処理が書かれるのを見たことがありません。せいぜいが例外に対応したログを出力して処理終了。インスタンスも破棄してしまいます。
例外を全て非チェック例外とし、投げられる例外は全てドキュメント化されるなど判る様にしておき、何かしら処理を出来るようにしておくべきだと思います。その上で例外処理は実装に委ねてしまって良いと思います。
[本音]全メソッドが以下の様になっているのは見飽きました。
void hoge() throws Exception { // 何らかの処理 }
フィールドのアクセス修飾子をprivateにしgetter/setterメソッドを提供する事でフィールドを参照する設計方針を取る主な理由を説明せよ
カプセル化云々はありますけど、"フィールドである事"を外部に公開する必要は基本的に無いはずです。
フィールドはオーバーライド出来ませんが、メソッドならオーバーライド出来ます。setter/getterをオーバーライドする事なんて稀でしょうけれど。この辺はポリモフィズム的に云々。フィールドの シャドーイングハイディング は積極的に使いたくありませんしね。また、setter/getterならば、他のプロパティにより振る舞いを変えることも出来ます。例えばフィールドの型が変わったり不要になったりする場合、他のクラスからフィールドが参照されているとコンパイルエラーになる等、易々と変更できません。フィールド=内部の状態の変更ってだけなのに、変更箇所が複数のクラスに跨るのは単一責務原則に反している気がします。フィールドを参照しているクラスが存在しても、そのクラスが必要なのはフィールドではなく値です。初めからメソッドで実装していれば、フィールドに変更があった場合も、何かしらの代替手段を参照されているメソッドに実装する事が出来ます。その後リファクタリングして、本来あるべき所に移動すれば良いのです。
フィールド名とgetter/setter名が一致している必要はないと思っています。フィールド=プロパティではないという考えです。必ず守らないといけないとは思っていないと言うだけで、積極的に破りたいと考えているわけではありません。無用な混乱を生む必要はありませんし。
NullPointerExceptionが発生するのは主にどういう状況か?
NullPointerExceptionはnullになってしまっている参照のメンバにアクセスしようとした際に発生します。これが起こるのは、受け取ったパラメータがnullでないと思い込んで使用する場合や、なぜかメソッドがnullを返してきたにも関わらずフィールドに格納して使用しようとした際です。自分でnull突っ込んでおいてNullPointerExceptionが発生させてる方もいますが、それを"主な状況"と言われると困ります。
これらは防御的コーディングで対応することになりますが、引数は兎も角、戻り値をいちいちチェックしていると冗長になり読みづらいコードが出来上がります。これを避ける為に外部APIを使う所はメソッドを切り出してそこでチェックし、内部メソッドの戻りはチェックしないとかしたりします。
オーバーロードとオーバーライドの違いは何か?
メソッド名が同一と言う事以外に本質的な共通点はありません。オーバーロードはシグニチャの追加のみで、同じ名前のメソッドは同じように振舞うべきです。オーバーライドは同一メソッドを対象に振る舞いを変える物です。詳細は過去記事のリンクで代用します。
コンストラクタとは何か?
質問がざっくりで、なんと答えていいかしどろもどろに…。
コンストラクタはメソッドの一種のように見えますが、メソッドではありません。オーバーライドできませんし、継承もされません。コンストラクタを扱う際、最低限注意しなければならない事が二点。デフォルトコンストラクタと、コンストラクタの呼び出しです。コンストラクタが省略された場合、デフォルトコンストラクタが追加されます。また、コンストラクタは他のコンストラクタを呼ぶ必要があります。省略すると親クラスのデフォルトコンストラクタが呼ばれます。
とか書いて誤魔化しているけれど、コンストラクタとは何か…うーん。
インターフェイスを利用する目的を1つ説明せよ
色々あるんですが、インターフェイスを使用すると、クラスの型がカプセル化出来るってのが一番の理由だと思います。その結果、クラスの実装の切り替えが出来るようになります。目的に応じて実装クラスを選択出来る、これぞOO……なんて。
抽象クラスでも同じような事は出来ますが、インターフェイスは複数の実装が出来る利点があります。抽象クラスでも継承の継承の…で複数出来ますが、継承階層が深くならないのは大きなメリットです。
こんな理由はあるんですけど、最近の主目的はテスト可能性の向上です。テストのためにインターフェイスの抽出を行ったりします。フィールドの型を実装クラスからインターフェイスに変更します。これでテストのためにテスト対象外の実装を変えられます。
マイブームを答えてどうするんだと思ったんですけど、テストのしやすさって超大事だよね。