前回の記事はこちら↓
EffectiveJava読書会7日目 - 第7章 メソッド
[扱うテーマ一覧]
項目45 ローカル変数のスコープを最小限にする
項目46 従来のforループよりfor-eachループを選ぶ
項目47 ライブラリーを知り、ライブラリーを使う
項目48 正確な答えが必要ならば、floatとdoubleを避ける
項目49 ボクシングされた基本データより基本データ型を選ぶ
項目50 他の型が適切な場所では、文字列を避ける
項目51 文字列結合のパフォーマンスに用心する
項目52 インタフェースでオブジェクトを参照する
項目53 リフレクションよりインタフェースを選ぶ
※下記は本を読んで、自分の意見と混ぜ合わせたものとなっています。
項目45 ローカル変数のスコープを最小限にする
- ローカル変数のスコープを最小限にする最も協力な技法は、
ローカル変数が初めて使用された時に宣言すること - ほとんど全てのローカル変数宣言は、初期化子を含むべき。
初期化子を与えられない時点で、宣言をする必要は無い。
(try-catchは例外の場合がある※1)
// hogeのスコープはfor文の中だけ
for () {
int hoge = 1;
}
// hogeのスコープはfor文の中だけではない
int hoge;
for () {
}
※1 try-catchにおいて、変数をtry-catchの外でも使用する場合は、
変数の宣言はtry-catch句の中で行わなければ成らない為、
その条件に当てはまらない場合があるということだと思います。
whileよりforがいいと思うところ
- ループの変数の内容がループを抜けた後に特に必要が無い場合、
whileよりもforを使おう。
- for文は初期化時点でループ変数を宣言できる。
このループ変数がローカル変数のスコープを必要最小限なものに限定ししてくれる。
その為に、ループ内の変数の内容がループを抜けた後に特に必要が無い場合は、
whileよりもforを使おうという話でした。
2. コピペのミスにコンパイルエラー時に検知できる
int i = 0;
while (i <= 5) {
System.out.println("the number of i is the" + i + ",");
i++;
}
int i2 = 0;
while (i <= 5) { //間違えてるけどコンパイルは通る
System.out.println("the number of i2 is the" + i2 + ",");
i2++;
}
このケースで、コンパイルが通らないという事は無い。
なぜなら、変数iは、 while内に限定されたローカル変数ではない から。
for(int i = 0; i <= 5 ; i++) {
System.out.println("the number of i is the" + i + ".")
}
for(int i2 = 0; i2 <= 5 ; i++) { //←ここでコンパイルエラー
System.out.println("the number of i2 is the" + i2 + ".")
}
ただ、これを超えて、Java8では、さらにfor文を書かずに、Stream APIを使って書くのかどうかという議論になってくると思っています。
最近ちょくちょく議題にあがってきますね。
http://d.hatena.ne.jp/torutk/20140518/p1
項目46 従来のforループよりfor-eachループを選ぶ
きっとJava8からはStream使ったりでしょう。
ということで、ここの内容はJava8で色々崩れる可能性を持っています。(前提)
Java1.4までは、
コレクションはIteratorを利用したループ、配列はforでのループを使うことが多かったですが、Java1.5以降は、いわゆる拡張for文が利用できるようになりました。
そちらを使いましょう、というのがまず最初の話です。
//collectionをループ(cがコレクション)
for(Iterator i = c.iterator(); i.hasNext(); ){
}
//配列をループ(aが配列)
for(int i =0; i < a.length; i++ ){
}
//拡張for文
for(Element e: elements){
}
//拡張for文
for(int i: wholeNumbers){
}
前者もwhileよりはいいですが、不要なインデックス変数を作らない点において、拡張for文の方が優れているとここでは述べています。
また、前者はループをネストしたときにミスしやすいという例が本には書いてありました。
###方針
方針としては、for-each(拡張for文)が適さないのは下記のようなパターンであり、それを除けばfor-eachがいいでしょうということを言っています。
1 フィルタリング
特定の要素に対して取り除くなどの処理をしたい場合において、使えない場合がある。
(※特定の要素だけ削除したい、など)
2 変換
特定の要素を変換したい場合も同じ
3 並列イテレーション
並列処理をコントロールする場合
※全部Streamでやればいいと思うよ、Java8からはね
項目47 ライブラリーを知り、ライブラリーを使う
Library使おうよ。特に標準ライブラリ。という話。
- 標準ライブラリにある機能を、自前で実装する無駄。
処理を限りなく早くしたり、軽量にしたい場合(組み込み系や昔のゲームソフトとか)
を除いては無駄だと思う。
2.Web Applicationでがっつり数式計算するようなメソッドを気をつける。
例では、乱数を利用した特定の条件に置けるランダムな
整数を計算するメソッドを紹介していました。
(僕はほぼ数学科の出身なので、たまになんかこう、行列計算とか引っ張りだしたくなる時もある(忘れて下さい))けどこれって結局実装する人も、
レビューする人も正しい数学の知識が無いと、
非常に分かりにくいバグをうむ可能性があります。
考えた上で必要なシーンもあると思いますが、不用意にガンガン自前の数式を実装するのは危険だと言えそう。
3.java.lang,java.util,java.ioは押さえとけ
特に標準ライブラリの中でも上記のパッケージの中身はプログラマのベースの知識として、押さえておくべき。
→ということでどっかにまとめよう
項目48 正確な答えが必要ならば、floatとdoubleを避ける
数値がずれるのはみなさんご存知の通り。
9桁まで int
18桁まで long
全般 BigDecimal
- BigDecimalは丸めを制御できるという利点あり。
基本データ型を使用していない、若干のコストを気にしないのであればBigDecimalを使うべき。
項目49 ボクシングされた基本データより基本データ型を選ぶ
基本データ int,double,boolean
ボクシングされた基本データ Integer,Double,Boolean
1.ボクシングされたインスタンスは別々のアイデンティティを持つため、
自動アンボクシング(※暗黙的なInteger->intの変換等)が無い状態で、
==で比較したりすると同じ値同士の比較でもfalseが帰ります。
2.ご存知かと思いますが、後者はnullが入りますよね。
意図して使えていないのであれば、NullPointerExceprionの可能性が増えます。
また、自動でアンボクシングされた際にNullPointerが発生するというパターンもあります。
3.ざっくり言えば、前者の方がパフォーマンスが往往にして良い。
###方針
基本的には可能な限り基本データを使う。(それで困ることもまずない。)
一部の関数や、型などで基本データ型が使えない場合(コレクションの要素等)にのみ、
ボクシングされた基本データを使用する。
ただし、ボクシングされた基本データを扱う際に、前述の懸念点があることは意識する。
基本データと、ボクシングされた基本データには、下記の違いがあります。
できる限り、前者を可能な限り使おう。という話です。
項目50 他の型が適切な場所では、文字列を避ける
Stringが、本当に適した型なのかどうか考えるべき。
たまに見かけてしまうことがあるのが、こういうやつ。
String hoge = "0"; //intとか使えよ
String hoge2 = "true" //booleanですね
あと、こういうやつ
String key = hoge + "#" + huga.next();
- hogeもしくはhuga.next()に"#"が入っていたらどうする
- hogeとhuga.next()のフィールドを見ないと中身が分からない
- 単純なequals,toString,compareToを使えない
⇒この場合は、これらの集合を現すprivate staticなメンバークラスを書くのが良い。
項目51 文字列結合のパフォーマンスに用心する
ここに関して、こちらの本では文字列結合演算子(+)による結合は遅いという話をしています。
これに関しては、日本語のブログでも非常に数多くの議論がなされている問題です。
大量の文字列をループ処理で連結する場合:StringBuilder
少ない場合は、concatか文字列結合演算子
位の印象でいます。
項目52 インタフェースでオブジェクトを参照する
例として挙げられているのは、Listインタフェースを使用しているパターンです。
//Effective javaでは下記で問題無いなら下記の方が柔軟だと言っている
List<String> hoge = new ArrayList<String>();
//↑を下記のように書き換えた際の影響が少ないので
List<String> hoge = new Vector<String>();
//あたり前ですが、下記のようにもかけますよね
ArrayList<String> hoge = new ArrayList<String>();
//Vectorに書き換える際は、どっちも書き換える必要があります。
Vector<String> hoge = new Vector<String>();
前者の方が柔軟なので、型にインタフェースを使用するというのを、
癖として持った方がいいと言っています。
確かに、その方が柔軟ですね。
ただし、これも記事によっては、
厳密に下記の方に書いた方がいいと言っているブログもありました。
いずれにせよ意識することは、
前者がArrayListにしか無い機能を使ったりした場合に、
前者の形は安全では無くなります。
あるいは周りのコードが全てArrayListで同期されている場合など。
本にも、
###適切なインタフェースが存在しない場合に、インタフェースではなく、クラスでオブジェクトを参照することは適切です。
という明確な記述があります。
型にインタフェースで、より柔軟な実装が出来るようになるので、
そこを無意識にやるのではなく、ちゃんと意識しようということだと思います。
項目53 リフレクションよりインタフェースを選ぶ
リフレクションはテストコード(private method)のときによく見る印象です。嫌いですw
後で読みます
項目54 ネイティブメソッドを注意して使用する
CやC++等のネイティブ言語で書かれたネイティブメソッドを使うことに関してですが・・・
使うな
で良く無いですか・・・??
現在でも有効な効果的な使用法があれば教えて頂きたいです。
これに関しては、当時アプリがCやC++などで作られてて、
それからJavaでアプリを作るようになった。
という過程の中から生まれた部分が大きいと思います。
あるとすれば元々ある機能を致し方なく、
使う以外でここをケアする必要すらあまり無いのかなと思っています。
で
項目55 注意して最適化する
早いプログラムを書くのではなく、良いプログラムを書く。
※ここは概念的な話だったのでとりあえず割愛
項目56 一般的に受入れられている命名規則を守る
書いてある内容が至極基本的でした。
多分ここの命名規則を守っていない人はあまりいないと思うので、
大きな問題ではないと思います。
古いドキュメントですが、参考として下記があります。
JLS6.8
http://docs.oracle.com/javase/specs/jls/se5.0/html/names.html#6.8
を読めば分かるように、そんなにずれたことをやってる所はなさそう。
変数名の付け方に関しては、国内外問わず、議論の対象にはなっているようです。
下記の記事が面白いと思いました。
正しいコーディングが身につくエンジニア英語の手引き 〜文法とクラス/メソッド、命名規則〜
http://www.find-job.net/startup/english-for-engineers-naming-conventions
こういうサービスもあるみたいですね。
(下記はまだまだデータが足りない印象を受けましたが)
Codic
http://codic.jp/
あとがき
まだまだ書き足す所や、改善の余地は多いにあるのですが、
一旦編集中を取りましたw
あとは本ではちょくちょくThreadLocalが色んな例に使われてましたが、
これあんまり良くわかってないというか使った事無いので、動かしてみます