前回からの続き。
この章からプログラミング一般に関して。啓蒙的な話が多い。知ってることが多かったのでここは比較的読みやすかった。
--------------
29.ローカル変数のスコープは小さく
C言語みたく、冒頭で宣言しない。宣言は必要になったら。
使い回しが効かなくなるのでbugも減る。
全く関係ないが、この章で面白かったのは
for (int i=0; i
ではなく、
for (int i=0,n=list.size(); i
と書いてlist.size()を一回しか呼び出さないように工夫していた点だな。
本当に効果があるのか調べてみた。
package test;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList list = new ArrayList(10000);
for (int i = 0; i < 10000; i++) {
list.add(new Object());
}
long start = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
for (int j = 0; j < list.size(); j++) {
;
}
}
long stop = System.currentTimeMillis();
System.out.println((stop - start) + " ms");
start = System.currentTimeMillis();
for (int i = 0, n = list.size(); i < n; i++) {
for (int j = 0, m = list.size(); j < m; j++) {
;
}
}
stop = System.currentTimeMillis();
System.out.println((stop - start) + " ms");
start = System.currentTimeMillis();
for (Object o : list) {
for (Object oo : list) {
;
}
}
stop = System.currentTimeMillis();
System.out.println((stop - start) + " ms");
}
}
list()呼び出しまくり版は422 ms
list()1回だけ呼び出しは172 msだった。確かに効果はあるようだ。
ちなみにGenerics版は5328 ms。遅ぇ...
50000回に変更したら
list()呼び出しまくり版は5125 ms
list()1回だけ呼び出しは4500 ms と何故か差が縮んだ。なんでだろう。
逆コンパイルした結果も面白い。
list()呼び出しまくり版は
for(int i = 0; i < list.size(); i++)
{
for(int j = 0; j < list.size(); j++);
}
と変わらなかったのに対して
list()1回だけ呼び出しは
int i = 0;
for(int n = list.size(); i < n; i++)
{
int j = 0;
for(int m = list.size(); j < m; j++);
}
何故か最初に宣言したiがforブロックの外に追い出されてる。
Generics版は
for(Iterator iterator = list.iterator(); iterator.hasNext();)
{
Object o = iterator.next();
Object oo;
for(Iterator iterator1 = list.iterator(); iterator1.hasNext();)
oo = iterator1.next();
}
Iterator を使用したコードに機械的に置き換えてる。
ArrayListの場合、Iterator の実装コードはAbstractListのprivate class Itrにある。
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
まぁ、このペアを10000の2乗回呼んでるわけだから、そら重いよなぁ。
--------------
30.ライブラリーを使え
自分で1から作るよりライブラリーを使用した方がメリットが大きい。(既にある。bugが少ない。勝手に保守されていく。)まぁ、これは当たり前の話か。
Random.next() → 乱数の範囲を絞るのにmod演算はダメ。コレ、昔やらかした思い出がある。
nextInt()使えって事です。
あとbit演算の話が面白かったかな。((n&-n) == n)がtrueならnは2の乗数とか。
こーゆうbit演算のテクニックを集めた本とかってないのかしら?
--------------
31.浮動小数点数(float, double)
あくまでも近似値なので、正確な値(例えば金額計算とか)が知りたいときはint, long, BigDecimalを使う。
パフォーマンスが重要なら→intやlongを整数部、小数部に分けて使う。
パフォーマンスは重要じゃない。longに分けられないぐらいの桁数、もしくは分割がめんどくさい→BigDecimal使え。
--------------
32.文字列使用を避けるべき場合について
-他の方で表現した方がよい場合 数値データはintやdouble、はい/いいえの2値データならboolean。列挙型とかもStringで表現しない → 分かっていても守られなくなりがち。
-集合を文字列で表現しない→解析にコストがかかる。
-ケイパビリティ(capability;偽造できないキー)として文字列は使わない
本では例としてThreadLocalのダメな設計例を挙げている。
set(String , Object);やget(String);みたいなインタフェースだと2つのスレッドで渡す文字列が被ったときにヤバイことになる。この場合、set(Object)にして内部で一意なkeyを生成して管理すべき。
→もっといいのはjava.lang.ThreadLocalを最初から利用すること。
--------------
33.文字列の結合
+による結合はO(n^2)
→連結回数が固定ではなく、大量の連結はStringBufferつかえという話なのだが、本はJava1.4までの話なので、Java1.5だとどうなんだろう?
JVMもかなり最適化されたという話だし、スレッドセーフでなくなる代わりに高速化されたStringBuilder(そもそもStringBufferなんて複数スレッドで共有とか普通しないもんな。)も登場したし、よくわからんので後で調べる。 → [開発] [Java] [メモ] StringBufferよりもStringの方が文字列結合は速い?
StringBuilderを使うようにjavacが自動変換してくるのか。賢いな。
でもループ内での+による結合はやっぱり遅いみたいだ。
--------------
34.型を指定するときはインタフェース
もしくは似たような共通の機能を有する親クラスにしろという話。
インタフェースにしておけば実装クラスが切り替わった場合の変更が最小限で済む。
・そもそも実装を切り替える場合ってあるのか
→あるインタフェースを実装している既存のクラスよりもパフォーマンスの良い新しいクラスが現れた場合など。
以前紹介した「HttpSessionの実装について」の5系から6系までの変更とかがよい例だね。
--------------
35.リフレクションの使いどころ、使われどころ
リフレクション:クラスの情報を動的に取得できる仕組みのこと。
ただし、動的なのでコンパイラーによる型検査の意味はなくなる。コードも複雑になりがち。パフォーマンスも悪いというデメリットもある。
→クラスブラウザ、オブジェクトインスペクタ、コード解析ツール、インタプリタ的な組み込みシステム、又はRPC(Remote Procedure Call)とかニッチなもので使うぐらい。
p.147-148より
あなたのアプリケーションがこれらの分類のどれかに属するかどうかについて何らかの疑問があれば、おそらくは属してません。
これは、「そんなことで悩んでるようなペーペーには無縁な技術なんだよ!(#゚Д゚)」という皮肉だろうか。(´Д`)
普通の場面ではデメリットだらけなのでリフレクションは使わない。
唯一よくある例としてはフレームワークの基幹部分。URLから動的にアクションを生成して呼び出すときとか。
コンストラクタに引数いらないならClass.newInstance();の方を使って生成すべき。
--------------
36.ネイティブメソッド
JVMの最適化が進んでいるのでパフォーマンスの為にネイティブメソッドを書くのは×
デメリットの方が大きいので(C、C++で書くのでメモリ領域に関して安全でないし、保守もしづらくなる。)ネイティブメソッドの使用は、低レベルリソースや古いライブラリにアクセスせざるをえないときだけに留める。
--------------
37.最適化の話
工程の早い段階で最適化はしない。
良い(おそらく、可読性の高いということなのだろう)プログラム>>速いプログラム
API、通信プロトコル、永続データのフォーマットに関する設計は後々のパフォーマンスに与える影響が大ということを意識する。
どうしても最適化をするのならプロファイラーを使ってボトルネックになっている箇所を特定してから最適化する。そしてチューニング前後の結果を比べる。
ボトルネック特定後は、細かいチューニングの山よりはプログラムの中に潜むO(n^2)なアルゴリズムをO(nlogn)とかに書き直した方がよっぽど効果がある。→だから最初にやるのは選択したアルゴリズムの検証から。
--------------
38.命名規則
まぁ、知ってる内容だったので割愛。
ひとつ思ったのは、BeanでもないのにgetHoge()やsetHoge(Hoge)という名前にするのはやめようかなぁってことかな。
hoge()やhoge(Hoge)でもいいかもしんない。