Javaの学習本

Java言語を取り扱った書籍だけでも何冊あるかわからないほど本があります。今まで役に立った書籍を紹介します。

Javaチュートリアル第3版 (The Java series)

Javaチュートリアル第3版 (The Java series)

入門書としては少し難しいかもしれませんが、Java言語を覚えるのに丁度よいレベルだと思います。

プログラミング言語Java (The Java Series)

プログラミング言語Java (The Java Series)

  • 作者: ケンアーノルド,デビッドホームズ,ジェームズゴスリン,Ken Arnold,David Holmes,James Gosling,柴田芳樹
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2001/06
  • メディア: 単行本
  • クリック: 26回
  • この商品を含むブログ (17件) を見る

教科書的な要素でお勧めです。JDK1.3について書かれているので少々古いのですが、言語仕様ついて曖昧な部分をきっちり学ぶことができます。

Java魂―プログラミングを極める匠の技

Java魂―プログラミングを極める匠の技

タイトルがすごいですが、それほどマニアックな内容ではありません。気付きにくい機能や落とし穴、実践向けのテクニックが紹介されています。Javaの理解を深めるには丁度よいと思います。

Effective Java プログラミング言語ガイド

Effective Java プログラミング言語ガイド

これはまだ読んでいないのですが、是非読みたいので挙げておきます。

String VS StringBuffer

前回のStringの呼び出しコストに続き、今回は文字列結合に最適な方法について決着をつけようと思います。

検証に使ったコードは以下のとおりです。

public class ConcatString {
private static int testCount;
private static StringBuffer buffer;
public static void test(int count) {
testCount = count;
buffer = new StringBuffer(testCount * 10);
        TEST_END:
for (int i = 0; ; i++) {
buffer.delete(0,buffer.length());
long start = System.currentTimeMillis();
switch (i) {
case 0:
System.out.println("concatLiteral()");
concatLiteral();
break;
case 1:
System.out.println("concatMethod()");
concatMethod();
break;
case 2:
System.out.println("appendMethod()");
appendMethod();
break;
case 3:
System.out.println("appendDirectConcatLiteral()");
appendDirectConcatLiteral();
break;
case 4:
System.out.println("appendPreConcat1Litera()");
appendPreConcat1Litera();
break;
case 5:
System.out.println("appendAppendMethod()");
appendAppendMethod();
break;
case 6:
System.out.println("concatString()");
concatString();
break;
            default:
break TEST_END;
}
long end = System.currentTimeMillis();
double result = (double)(end - start) / 1000;
System.out.println(result + "秒");
}
}
protected static void concatLiteral() {
for (int i = 0; i < testCount; i++) {
String s = "ABC" + "ABC";
}
}
protected static void concatMethod() {
for (int i = 0; i < testCount; i++) {
String s = "ABC".concat("ABC");
}
}
protected static void appendMethod() {
for (int i = 0; i < testCount; i++) {
buffer.append("ABC");
}
}
protected static void appendDirectConcatLiteral() {
for (int i = 0; i < testCount; i++) {
buffer.append("ABC" + "ABC");
}
}
protected static void appendPreConcat1Litera() {
for (int i = 0; i < testCount; i++) {
String s = "ABC" + "ABC";
buffer.append(s);
}
}
protected static void appendAppendMethod() {
for (int i = 0; i < testCount; i++) {
buffer.append("ABC");
buffer.append("ABC");
}
}
protected static void concatString() {
for (int i = 0; i < testCount; i++) {
String s = "ABC";
s += "ABC";
}
}
public static void main(String[] args) {
ConcatString.test(5000000);
}
}

この検証コードはVMにある程度メモリを渡さないとOutOfMemoryErrorになると思いますので実行する際には注意して下さい。

一応、比較しやすいように実行時の結果も載せておきます。

concatLiteral()
0.015秒
concatMethod()
1.235秒
appendMethod()
0.515秒
appendDirectConcatLiteral()
0.516秒
appendPreConcat1Litera()
0.516秒
appendAppendMethod()
1.015秒
concatString()
2.719秒

concatLiteral()
String変数に値を設定する際にリテラルを+演算子によって結合したものです。これはコンパイラ

String s = "ABCABC";

に書き換えるので、文字列結合自体のオーバーヘッドがなく高速です。

concatMethod()
+演算子ではなくconcatメソッドで結合しました。これはコンパイラによって自動で一つの文字列になることはありませんのでかなり低速です。Stringが変更不可なオブジェクトであることを考えると再生性するコストがかかっていることが予想できます。

appendMethod()
StringBufferによる文字列結合です。バッファ値を再設定するこのとないよう初期値で設定しているので純粋なappendメソッドの検証となりますが、concatメソッドの半分程度のコストしかかかりませんでした。

appendDirectConcatLiteral()
appendメソッド内にString結合があるとどうなるかというものです。この場合はString結合にStringBufferが使用され一つ余分なStringBufferが生成されてしまうと言われますが、ベンチでの結果は全く同じでしたので、速度面では気にする必要はなさそうです。但し、余分なメモリを消費するかについては検証できていませんので、このようなコーディングは避けた方がよいのかもしれません。

appendPreConcat1Litera()
appendメソッド内ではなく前もって結合した文字列を用意してあげるとどうかです。これも速度面での差はなく、単純にappendした際と同じ結果でした。

appendAppendMethod()
StringBufferにさらに追加した場合です。これは単純に倍のコストが掛かったのでバッファの再設定がなければappendメソッドを呼び出すオーバーヘッドのみとなるようです。

concatString()
最後にStringを+=演算子を用いて結合する場合ですが、これは非常に低速でした。concatメソッドのさらに倍程度のコストが掛かっています。+=演算子でなければ実現しないような場合以外は避けた方がよさそうです。

結果としては、文字列が可変でない場合は潔くStringを+演算子で繋いで一つの文字列にしてしまうのが一番良さそうです。文字列が可変の場合はStringBufferを利用する方が高速ですね。検証コードではバッファ値を初期設定で指定していますが、指定しなかった場合でもStringのconcatメソッドで結合するよりは高速でしたので、結果として生成される文字列のサイズがわからなくてもStringBufferを利用する価値はあります。StringBuffer自体の生成コストやtoString()時のコストは無視できるレベルだと思います。

ということで一般的な論が大幅正解というところでしょうか。ただ、見落としがちなのは単純結合の場合はStringBufferを利用するより確実に低コストな点です。比較してみます。

String query = ""
+ "SELECT "
+     "name "
+ "FROM "
+     "human";
StringBuffer query = new StringBuffer();
query.append("SELECT "  );
query.append(    "name ");
query.append("FROM "    );
query.append(    "human");

コスト面では前者が優位です。但しコーディング規約で後者で統一するのも一つだと思いますので、中身の挙動を知って選択することが大切だと思います。

文字を連ねてGoogle検索すると?

調査はしていませんので理由はわかりませんが、文字を連ねてGoogle検索を行うと矢鱈とPDFファイルに引っかかります。連ねてというのは、「jjaavvaa」や「eecclliippssee」という感じで検索するという意味です。

その他の一般的な英単語(例えば「hheelllloo」や「kkiinngg」)でも同じ現象です。PDFに詳しい人からすると至極当然の理屈があったりするのかもしれませんが、もし知っている方がいらっしゃったらコメントかトラックバックで教えてもらえると、今日はすっきり眠れそうです。

ついでにキャンペーンに参加。アップルのiMac G5欲しい!

Eclipseのプラグインはどこで探す

Eclipseで欲しいプラグインを探すときに前まではEclipseWikiで探したりしていましたが、ここではコアなものを見つけるのが大変だったので、最近はEclipse-Pluginsを利用するようになりました。国際化されていなくても良く、ドキュメントがなくても構わないものであれば、結構使い物になるものが見つかります。
自宅で試して良ければ仕事に持ち込みます。

Stringの呼び出しコスト

「Stringを結合してはダメ、StringBufferを使いましょう。」

Javaプログラマなら一度は聞いたことがありそうなテーマです。その通りですので初心者は意味も解らなくても従うべきかもしれませんが、細かい話になるとほとんどのプログラマが別々の見解を持っているので混乱するばかりです。その根拠の多くは人から聞いたやネットで見たなどであやふやです。

環境によって変化するので絶対の数値では勿論ありませんが、気になるので試してみることにします。文字列結合は次回ということで、とりあえずはStringを使うときのコストを研究してみます。

検証に使ったコードは以下のとおりです。

public class LoadString {
private static int testCount;
public static final String PUBLIC_STRING = "ABC";
private static final String PRIVATE_STRING = "ABC";
private static OuterConstant outerConstant = new OuterConstant();
public static void test(int count) {
testCount = count;
        TEST_END:
for (int i = 0; ; i++) {
long start = System.currentTimeMillis();
switch (i) {
case 0:
System.out.println("directString()");
directString();
break;
case 1:
System.out.println("loadPrivateString()");
loadPrivateString();
break;
case 2:
System.out.println("loadPublicString()");
loadPublicString();
break;
case 3:
System.out.println("loadOuterStaticString()");
loadOuterStaticString();
break;
case 4:
System.out.println("loadOuterInstanceString()");
loadOuterInstanceString();
break;
case 5:
System.out.println("newString()");
newString();
break;
case 6:
System.out.println("newStringWithLiteral()");
newStringWithLiteral();
break;
            default:
break TEST_END;
}
long end = System.currentTimeMillis();
double result = (double)(end - start) / 1000;
System.out.println(result + "秒");
}
}
protected static void directString() {
for (int i = 0; i < testCount; i++) {
String s = "ABC";
}
}
protected static void loadPrivateString() {
for (int i = 0; i < testCount; i++) {
String s = PRIVATE_STRING;
}
}
protected static void loadPublicString() {
for (int i = 0; i < testCount; i++) {
String s = PUBLIC_STRING;
}
}
protected static void loadOuterStaticString() {
for (int i = 0; i < testCount; i++) {
String s = OuterConstant.OUTER_STATIC_STRING;
}
}
protected static void loadOuterInstanceString() {
for (int i = 0; i < testCount; i++) {
String s = outerConstant.outerInstanceString;
}
}
protected static void newString() {
for (int i = 0; i < testCount; i++) {
String s = new String();
}
}
protected static void newStringWithLiteral() {
for (int i = 0; i < testCount; i++) {
String s = new String("ABC");
}
}
public static void main(String[] args) {
LoadString.test(5000000);
}
}
public class OuterConstant {
public static final String OUTER_STATIC_STRING = "ABC";
public final String outerInstanceString = "ABC";
}

では実際に見ていくことにいます。

directString()

メソッド内で直接リテラルを記述する場合です。クラスファイルに直接埋め込まれるため高速に処理してくれます。

loadPrivateString()
loadPublicString()
フィールド定数は直接リテラルを記述する場合と同じ結果が得られました。フィールド定数は置換定数なのでクラスファイルにコンパイル時に埋め込まれるので当たり前といえば当たり前の結果です。publicとprivateによる差はありませんでした。

loadOuterStaticString()
疑い深いので他のクラスの定数を呼び出してみましたが、これも当然同じ速度ですね。

loadOuterInstanceString()
これは実は結構気になっていたのですが、インスタンス変数のアクセスは呼び出しにコストがかかるのかという問題です。結果は上記のメソッドらと同等の速度で遜色はありませんでした。クラスファイルを除いてみるとしっかりリテラルが埋め込まれていました。しかもVMが1.5のときにはStringBuilderもお目見えしていたので、かなり最適化されている模様。

newString()
直接リテラル書くのとnew String()はちがうのか?という疑問です。よく「””で囲った文字列を書くと暗黙でStringオブジェクトがnewされている」という発言を聞きます。確かに、VMリテラルをStringとして使える状態にはすると思いますが、挙動はかなり違うと思います。それを立証するように、このメソッドは他のメソッドの10倍近いコストがかかりました。

newStringWithLiteral()
最後にnew String()するときに引数でリテラルを渡すとどうか?というものです。これは意外な結果でした。当然、明示的にStringオブジェクトを生成しているのでnewString()の場合同様にコストがかかるのですが、こちらの方が僅かに速い。コンストラクタで生成する文字列があった方が速いのは腑に落ちないので、時間があるときに調べてみようかと。一つ言えるのは「Stringオブジェクトの生成と、コンストラクタ内のリテラル暗黙Stringオブジェクトの生成で2重にコストがかかる」というような論はないようです。

結果としては「new String()」しないならあまり気にする必要はないというところですかね。VMは1.4と1.5で試しましたが、特に違いはありませんでした。(比較なのでマシンスペックはいいかな)