レッスン・スタート。

改訂第2版 Java言語プログラミングレッスン (上)

コンパイルも知らない僕だけど、来週にはJavaしてやる!」と昨夜未明に思い立ってしまいました。経緯は内緒。
方向とマイルストンは、結城さんの「Java言語プログラミング・レッスン」を短期集中(突貫?)で完了すること。

レッスン中のメモ帳代わりに、この日記もスタートします!

JDKのダウンロードとインストール。

現時点でのJDKJava開発キット)のダウンロードは以下から。

J2EE 1.4 includes JDK 5.0」からダウンロード・ページに進み、「Windows Offline Installation, Multi-language」をダウンロードしました。
後はこれを起動し、インストーラのデフォルト指定に従ってインストールしただけ。

"Hello"クラスのコンパイルとCLASSPATH。

"Hello.java"のコンパイルに成功したものの、実行すると以下のエラー。

Exception in thread "main" java.lang.NoClassDefFoundError: Hello

まず疑ったことと試したことは、次の通り。

  • 打ち間違いがある。ソースを見直す。ダメなら打ち直す。
  • JDKのバージョンがあってない。JDKのバージョンは良く分からないのだけど、「J2EE 1.4 includes JDK 5.0」だと微妙に何かあわないのかも。ダウンロードページの「JDK 5.0 Update 6」のリンク先にあるものに入れ替えてみる。
  • 誤植がある。「書籍に掲載されたプログラムリストのダウンロード」から結城さんの動作しているソースを取得し、動かしてみる。

どれをやってもダメだったので、次にパスなどを疑ってみる。僕のパソコンは、仕事に応じてJREJava実行環境、JDKにもJREは含まれている)やら、Oracleクライアント(JREなども一緒に入ってくる)やらをインストール・アンインストールしてきているマシンなので、何か良くない状況があるかもしれない。
まずはenv...じゃなかった、setコマンド。

C:\j2sdk1.4.2_11\study\1>set
CLASSPATH=C:\Program Files\QuickTime\QTSystem\QTJava.zip
Path=C:\j2sdk1.4.2_11\bin\;C:\Oracle\product\10.1.0\Client_1\bin;C:\Oracle\product\10.1.0\Client_1\jre\1.4.2\bin\client;C:\Oracle\product\10.1.0\Client_1\jre\1.4.2\bin;C:\Perl\bin\;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\PROGRA~1\NcFTP;C:\usr\bin;C:\Program Files\QuickTime\QTSystem\

しばらくはPathを気にしていて、案の定OracleJREなどにパスが通っていたりしたのでこれを外してみたりと試行錯誤したのだけど、これは不正解。正解はなんとなくJava絡みで聞き覚えのあるCLASSPATHの方でした。

C:\j2sdk1.4.2_11\study\1>set CLASSPATH
CLASSPATH=C:\Program Files\QuickTime\QTSystem\QTJava.zip

C:\j2sdk1.4.2_11\study\1>java Hello
Exception in thread "main" java.lang.NoClassDefFoundError: Hello

C:\j2sdk1.4.2_11\study\1>set CLASSPATH=

C:\j2sdk1.4.2_11\study\1>java Hello
Hello.

えーと、内容からするとQuickTime(が同梱されているiTunes)をインストールしたことで設定された環境変数かな?
正直に言って、今のところCLASSPATHは「聞き覚えがあるから、きっとJava関連」「名前からするとPerlの@INC(ライブラリの検索パス)みたいなもの?」という程度の認識しかありません。まして、何でこれが設定されていると実行がうまくいかないのかもさっぱり。上巻のインデックスを見てもCLASSPATHは載っていないので、焦って調べたりせずに、これはとりあえず「そういうものだ」と思っておくことにします。
きっとその内分かるでしょうから、慌てない慌てない、ひと段落ひと段落。

(2006年4月13日追記)

iTunesのバージョンアップに伴うQuickTimeのインストールでCLASSPATHが設定されてしまう - 結城さんのはてな日記」にこの件が載っていることを、コメントで教えてもらいました。ありがとうございます。
やっぱりCLASSPATHの問題で、やっぱりそれがどういけないのか僕には理解できてないのですが、ひとまずこの対応で良さそうです。

"Aisatsu"と間違い探し。

間違いのある"Aisatsu.java"のコンパイルでは、書籍にあるとおりのエラー表示。
ところで、このエラー表示を見ると間違い探しの答えは6つあるのに、javacは4つしか見つけてないみたいです。無能です。...と思ったら、この4つだけ修正してサイドコンパイルしてみると次のようにエラー表示。

C:\J2SDK1.4.2_11\STUDY\1>javac AisatsuError.java
AisatsuError.java:5: 文ではありません。
                System.out,println("こんにちは。");
                      ^
AisatsuError.java:5: ';' がありません。
                System.out,println("こんにちは。");
                          ^
AisatsuError.java:8: '}' がありません。
^
エラー 3 個

ちゃんと全部見つけてますね。ちぇっ。
ところで間違い探しじゃない方の"Aisatsu.java"、自然に手がこんなソースを打ち込んでくれてました。

public class Aisatsu {
  public static void main(String[] args) {
    System.out.println("おはよう。");
    System.out.println("おやすみ。");
    System.out.println("こんにちは。");
    System.out.println("こんばんは。");
  }
}

何だっけな、と思ったらどうやらポンキッキらしい。全文だとこんな歌詞だったんですね。タイトルは「あいさつの歌」らしい。懐かしいなぁ。

"AisatsuAtOnce"。

第1章の練習問題1-4はこんな感じ。あ、「System.out.printlnを一度しか使わずに」だから、「一度も使わずに」やっちゃだめなのか。

public class AisatsuAtOnce {
  public static void main(String[] args) {
    System.out.print("おはよう。\nおやすみ。\nこんにちは。\nこんばんは。");
  }
}

実行時にJavaコマンドに与える引数を、自動補完のおかげで「AisatsuAtOnce.class」としてしまい、エラー。

C:\J2SDK1.4.2_11\STUDY\1>java AisatsuAtOnce.class
Exception in thread "main" java.lang.NoClassDefFoundError: AisatsuAtOnce/class

この間違いは、ちゃんと「付録C よく起こるエラーとその対処法」に載ってました。「生徒『先生、ありがとうございます。案の定やっちゃいました』」な感じです。
この付録Cだけ、販促をかねてWeb上で公開とかされてると良さげな気がしました。

文字列連結と加算。

クイズ3の正解は。

加算の練習5です。

だと思ってしまいました。うまく引っ掛けられた...。
Perlだと、文字列を連結する"."と加算の"+"には優先順位があるので、

print "加算の練習" . 3 + 2 . "です。"

では上のような出力になる。だけどJavaではどちらも"+"なので

print "加算の練習" + 3 + 2 + "です。"

とした場合、左から評価される。出力結果は次の通り。

加算の練習32です。

プログラミング言語を知らなくても」といいながら、これはPerl者を引っ掛けるための罠ですね?そーだ。そーに違いない。結城さんブラック。いや、このクイズがなければ気付かないままで、フィールド出てから引っかかってましたが。
ありがとうございます。

"&"と"&&"と"and"。

  • "&"は論理(ビット)積演算子。左辺と右辺は両方評価され、どちらも真なら真。
  • "&&"は条件付論理積演算子。左辺が真の時だけ右辺を評価し、右辺も真なら真。

Perlってこの使い分けあったっけ」と思って確認すると、ありますね。それからPerlだと"and"も使えて、これは"&&"と同じ条件付論理積演算子。例えば、

print "[and]\n";
0 & print "0 & print\n";
0 && print "0 && print\n";
0 and print "0 and print\n";

print "[or]\n";
1 | print "1 | print\n";
1 || print "1 || print\n";
1 or print "1 or print\n";

というPerlスクリプトを実行すると、以下のような結果になりました。

[and]
0 & print
[or]
1 | print

右辺が評価されるのは、確かに論理積演算子の"&"と論理和演算子の"|"だけみたいです。うーん、ちゃんと意識してれば、今までのスクリプトでもうちょっとうまく書けたコードがあったなぁ。
Javaでは、こんな感じ?

public class test {
  public static void main(String[] args) {
    System.out.print("[and]\n");
    0 & System.out.print("0 & print\n");
    0 && System.out.print("0 && print\n");
    System.out.print("[or]\n");
    1 | System.out.print("1 | print\n");
    1 || System.out.print("1 || print\n");
  }
}

...と思ったら、コンパイルでエラー。

test.java:4: 文ではありません。
                0 & System.out.print("0 & print\n");
                  ^
test.java:5: 文ではありません。
                0 && System.out.print("0 && print\n");
                  ^
test.java:7: 文ではありません。
                1 | System.out.print("1 | print\n");
                  ^
test.java:8: 文ではありません。
                1 || System.out.print("1 || print\n");
                  ^
エラー 4 個

や、多分「"&"とか"|"はif文なんかの中で使え」と言われてるんだろうけど、そんなこと言われても、まだif文その他は出てきてません。ということで、これの確認は後回し。

"\n"と"%n"。

「コラム『printfは使えないの?』」によれば、printf文中では"\n"はLFに、"%n"はプラットフォーム依存の改行文字(WindowsであればCR+LF)に変換されるということ。
ではprint文中では、と言うと...

  • "\n"はLFに変換される。
  • "%n"は変換されない。

...という結果。じゃあ、print文中でプラットフォーム依存の改行文字を使いたい時はどうするんでしょう。ないのかな?

数値の型。

練習問題までいって、結局数値の型を覚えずにスルーしてしまっている自分に気付く。だって、Perlじゃ数値の上限・下限なんて気にしない(僕が気にしてないだけ)ので、身についてないんですもん。
上限、下限を数字で覚えるのは辛いので、「符号付8ビット」とかで覚えるべきだろうな。

  • byteは符号付8ビット。
  • 8ビットあると、(2の8乗=4の4乗=16の2乗=)256種類の数値を表わせる。
  • 符号付なので半分に分けて、範囲は-128〜+128...じゃなくて、-128〜+127。
    • とりあえず「0がプラス側だから、こっちは上限値が一つ少ない」ことにしておく。(ぉぃ
    • 本当は符号によって先頭ビットが反転してどうのこうのという理屈なんだと思う。

あとは、byte、short、int、longの順で、符号付でビット数が倍になる。charは唯一ここから外れた符号無しで、16ビット。これだけ覚えておけば、とりあえず上限、下限はその場で計算できるかな?

あとは、何度も型チェックとかそのための私見項目記述とかさせられていくうちに、なんとなく上限値、下限値も覚えるでしょう。

ロング値と数値の四則演算。

第2章の演習問題2-4をやるついでに、ちょっと実験。

public class CalcBig {
  public static void main(String[] args) {
    System.out.println("100000×100000=" + 100000 * 100000);
    System.out.println("100000L×100000L=" + 100000L * 100000L);
    System.out.println("100000L×100000=" + 100000L * 100000);
    System.out.println("100000×100000L=" + 100000 * 100000L);
    System.out.println("2000000000+2000000000=" + (2000000000 + 2000000000));
    System.out.println("2000000000L+2000000000=" + (2000000000L + 2000000000));
    System.out.println("2000000000+2000000000L=" + (2000000000 + 2000000000L));
  }
}

実行結果はこう。足し算であれ掛け算であれ、左右のどちらかの数値がロング型(数値の後ろにLがつく)だと明示されてれば、ロング型で計算される様子。きっと四則演算は全部耕なんだろうな。

100000×100000=1410065408
100000L×100000L=10000000000
100000L×100000=10000000000
100000×100000L=10000000000
10000000000L+1=10000000001
2000000000+2000000000=-294967296
2000000000L+2000000000=4000000000
2000000000+2000000000L=4000000000

余談だけど、最初は足し算を10000000000と1で試そうと思ってました。で、コンパイルしてみると、その時点でエラー。

C:\j2sdk1.4.2_11\study\2>javac CalcBig.java
CalcBig.java:8: 整数 10000000000 が大き過ぎます。
    System.out.println("10000000000+1L=" + (10000000000 + 1L));
                                              ^
エラー 1 個

のびたの癖に生意気だ!

秀丸用マクロと強調定義ファイル。

そろそろお昼なので、一休み。ついでに、秀丸マクロ強調定義ファイルを追加しました。

マクロでは「javaメソッド補完」と「javaメソッド補完マクロ用の辞書ファイル作成ツール」などもあるようですが、ひとまずは使い慣れていて、辞書の用意が不要でPerlでもHTMLでもそこそこ役に立ってくれる「入力補完マクロ」をインストール。
強調定義ファイルは「Java強調表示定義ファイル」もあるのですが、新しいというだけの理由で「Java プログラミング用 強調表示定義ファイル Ver1.5」を選択。強調定義ファイルは作る人の好みとかマメさでまるで別のものになるので、本当はちゃんと評価するべきなんでしょうけど、これもひとまずはそこそこ役立つレベルを超えてれば良いということで。
さて、これでだいぶ快適になりました。

(2006年4月19日追記)

強調表示には、メソッドが強調表示され「強調表示の一覧」でも探せるように、以下を「正規表現」「行の強調1」で追加しました。

^[^/=]*[^ /=]+ [^ /=]+ ?\(.*\) ?(throws|implement|extends|{)

今のところ、期待通り表示されています。

未定義値の参照。

初期化や代入を行う前に変数の値を参照しようとすると、コンパイル時のエラーになります。

やってみます。

public class UndefValue {
  public static void main(String[] args) {
    int x;
    System.out.println(x);
  }
}
UndefValue.java:4: 変数 x は初期化されていない可能性があります。
                System.out.println(x);
                                   ^
エラー 1 個

おお、確かに。
それにしても、javaやjavacのエラーメッセージって親切だなぁ。比較対象ってPerlかC(これはわけも分からずにmake叩いてるだけ)しかないけど、ことエラーメッセージに関して言えば「いいもの使ってる」感があります。完成度が高く見える点で重要。

基本型。

数値の型をやっと覚えたと思ったら、小数を扱うdoubleが登場。さらに、charで文字を扱っていて、数値のためだけの型ではないことが判明。

「付録H『基本型の一覧』」という項があるので、覚えなおすことにします。

  • 整数値は8ビットのbyteから始まって、short、int、longの順で倍々に。全て符号付。
  • 小数値は32ビットのfloatから始まって、doubleは倍。
  • 文字型は16ビット(というか2バイト)のchar。符号なし整数値でもある。
  • 他にはbooleanとvoid。

整数値の上限、下限の考え方は、「数値の型」での覚え方のままで良いかな。

「基本形についての会話」は知識じゃなく勘所。とてもありがたい内容です。とりあえず整数値はintかlong、小数値はdouble、文字はcharで、後は細かな要求に応じて使い分け。

文字と文字列。

複数の文字からなる「文字列」を作る時には、二重引用符(")でくくりました。'A'や'浩'のように、char型の1文字を作る時には、一重引用符(')でくくります。「文字列」と「文字」は異なるものですので、混同しないように注意してください。

混同してみます。

public class CharWithString {
  public static void main(String[] args) {
    char a = 'A';
    char alpha = 'α';
    char hiroshi = '浩';
    char wo = "を";
    System.out.println("変数 a の値は " + a);
    System.out.println("変数 alpha の値は " + alpha);
    System.out.println("変数 hiroshi の値は " + hiroshi);
    System.out.println("変数 wo の値は " + wo);
  }
}
CharWithString.java:6: 互換性のない型
検出値  : java.lang.String
期待値  : char
        char wo = "を";
                  ^
エラー 1 個

文字列だって「を」一文字なら2バイトで、とりあえず代入できても良さそう(で、バイト数超過してからエラーになれば良さそう)なものだけど、コンパイル時点であっさりエラー。
じゃあということで、最初からmainメソッドの引数宣言に出てきている「String[] args」を試してみます。

CharWithString.java:6: 互換性のない型
検出値  : java.lang.String
期待値  : java.lang.String[]
    String[] wo = "を";
CharWithString.java:6: 互換性のない型
検出値  : java.lang.String
期待値  : java.lang.String[]
    String wo[] = "を";

あれ、なんで?良く分からないのだけど、「[]」を外すとコンパイルも通るし、実行もできます。

public class CharWithString {
  public static void main(String[] args) {
    String wo = "を";
    System.out.println("変数 wo の値は " + wo);
  }
}

うーん、Perl頭で解釈すると、String[]は文字列配列リファレンスで、だから値の方も配列リファレンスじゃなきゃいけない?いやでも、これも変に理解するより、後で出てくるでしょうからひとまずは保留することにします。

慌てない慌てない、ひと段落ひと段落。

「シンボルを解決できません」。

HowOldAreYouのコンパイルでエラー。

HowOldAreYou.java:8: シンボルを解決できません。
シンボル: メソッド readline ()
場所  : java.io.BufferedReader の クラス
            String line = reader.readline();
                                ^

ぱっと見で分からなかったので、また「書籍に掲載されたプログラムリストのダウンロード」から取得した結城さんのソースを引っ張り出してきて、「他の秀丸エディタと内容比較」(つまりdiff)で確認。「readline」じゃなくて、正しくは「readLine」ですね。
(僕の?)Perl頭だとサブルーチン/メソッド名は単語を単につないだり、アンダーラインでつないだり、CamelCaseにしてみたりと結構いい加減につけるんだけど、Javaの場合はCamelCase(ただし先頭語は小文字で始める)なので、これは手に覚えさせるしか。
やっぱり初学時は、あまり補完機能が整ったIDEなどは使わずに、何度も手で打ち込んでみることが大切ですね。まさに「手習い」。

文字列を整数に変換する。

HowOldAreYouの実行結果。

C:\j2sdk1.4.2_11\study\3>java HowOldAreYou
あなたの名前を入力してください。
塚本牧生
塚本牧生さん、こんにちは。
年齢を入力してください。
90
いま 90 歳とすると、10年後は 100歳ですね。

うーん、うまく動いてる...。以下の部分でエラーになると期待して、全角で年齢を入力したのですが、ちゃんと90歳だと認識できている様子。

System.out.println("年齢を入力してください。");
line = reader.readLine();
int age = Integer.parseInt(line);

ただし、-90はOKだけど、マイナス(と入力して変換すると出る"−")90は認識できない模様。

-90
いま -90 歳とすると、10年後は -80歳ですね。
−90
年齢が正しくありません。

ぱっと見、catch (NumberFormatException e)という部分の処理に飛んでいることが分かるので、catch (IOException e)の処理を真似てちょっと書き換えればJava自身の出しているエラーメッセージが分かると期待。ちょっとやってみます。

      System.out.println("年齢が正しくありません。\n" + e);
年齢が正しくありません。
java.lang.NumberFormatException: For input string: "−90"

なるほど...って、まあ、それっぽいエラーだな、というぐらいしか分かりませんが。
ところで、ちょっと余談になりますが、reader.readLineからの読み込み値はラッパー・クラスIntegerのparseIntメソッドで型変換してるのに、これを標準出力に出す時には何もしてないのがちょっと気持ち悪く感じます。いや、そこで暗黙の型変換してくれないとわずらわしくてしょうがないのは確かなんだけど、もうちょっとこう...。どうなれば良いのだろう。
え、年齢ですか?ちょっとだけさば読んでますが何か?