「Javaプログラミングの教科書」追補情報 |
※ここは、拙著のJava解説書「Javaプログラミングの教科書」(BNN、550頁CD-ROM付、2600円)に関するコーナーです。現在、初版の見直し作業をしていますが、校正情報やよくある質問の答えなどをここでまとめて掲載します。
また、本書についての記述間違いや質問、御意見御感想などあれば、どうぞ掌田津耶乃まで御連絡下さい。
★バグ・校正情報★ |
※ここでは、ソースコードや内容に関する記述間違い、校正ミスなどについて整理しておきます。ここに掲載されていないバグや記述ミスなどがありましたら、どうぞ御連絡下さい。ここに追記していきたいと思います。
-9223372036854775808〜9223372036854775807
値の範囲の一覧表が1カ所間違っていました。上の赤字部分です。
(誤)xの下位4bitをONに → (正)xの下位4bitをそのままに
「1つ1つの値の大きさは全て同じであり…」という記述
これは「値に割り当てられているメモリの大きさは同じ」という意味のつもりだったのですが、「値の数値が同じ」のようにも読めると指摘をいただきました。誤解のないよう、ここで明確にしておきます。
(誤)-128の値から1を除算すると → (正)-128の値から1を減算すると
オーバーフローとアンダーフローの説明部分ですが、非常に大きな勘違いをしてしまいました。値の範囲の上限・下限を超えてしまうことは、どちらも「オーバーフロー」です。アンダーフローは、ここでは関係ありません(浮動小数で指数が負の際に桁数の限界を超えてしまうこと、要するに値が細か過ぎること)。大変申し訳ありませんでした 。
(誤)11110101 → (正)11110100
(誤)「x == -15」という表記 → (正)「x == -16」に修正
正の数と負の数は、基本的にビットが反転した状態ですが、「0」があるため1ずれることになります(正は0が00000000となるが負は-1が11111111となる)。当たり前のことですが頭から抜けて確認し忘れてしました。また、同様に「11110000」となるのは-15ではなく-16になります。
(誤)「ラッパークラス(lapper class)」 → (正)「ラッパークラス(Wrapper class)」
(誤)「〜利用した場合も」 → (正)「〜利用したい場合も」
ソースコードの中で、変数ansの初期値が間違っていました。繰り返しの前に、一度ans = x % n != 0;しておくのを忘れてました。これを忘れると、13以下の素数がうまくいきません。
また、これは素数のアルゴリズムの紹介のつもりでしたので、 1と2の場合の処理を入れておくのを忘れていました。素数の場合、1と2はやや特殊です。1は自身以外に割り切れるものがありませんが素数ではありませんし、2は「2で割り切れる整数」の中で唯一素数として扱われます。
というわけで、以上の修正をしたソースコードを以下に示しておきます。
(正)
public boolean isPrime2(int a) {
int x = a;
int n = 3;
boolean ans = false;
if (a == 1) return false;
if (a == 3) return true;
if (a > 2 && a % 2 == 0) ans = false;
else {
ans = x % n != 0;
while(x / n >= n && x % n != 0) {
n += 2;
ans = x % n != 0;
}
}
return ans;
}
あるいは、whileを使わず、素直にdo 〜を使ったほうがもっとシンプルでよいかも知れません。
(正)
public boolean isPrime2(int a) {
int x = a;
int n = 3;
boolean ans = false;
if (a == 1) return false;
if (a == 3) return true;
if (a > 2 && a % 2 == 0) ans = false;
else {
do {
ans = x % n != 0;
n += 2;
} while(x / n >= n && x % n != 0);
}
return ans;
}
いずれにしても、else以降で一度ans = x % n != 0;してから繰り返しを行なうようにしないと、初期値のfalseの値が小さい素数で残ったままになってしまいます。これはアルゴリズムの説明ですから、うっかりミスではすまない問題でした。 大変申し訳ありませんでした。
(誤)
public void paint(Graphics g) {
g.drawImage(offScreen,0,0,this);
}
(正)
public void paint(Graphics g) {
if (offScreen == null)
offScreen = createImage(getSize().width,getSize().height);
g.drawImage(offScreen,0,0,this);
}
見ればわかるように、オフスクリーンoffScreenがnullであるかどうかのチェックを行なうifが抜けています。どうやら原稿にコピー&ペーストする時に最終版の前のものか何かを使っていてそのまま修正し忘れていたようです。なお、このGraphicCanvasについては後に高速版を用意してあります。
★技術的追補情報★ |
※本書ではなるべく細かな点にまで説明を行なうようつとめましたが、中にはわかりにくい部分や、筆者の勉強不足にて不正確なことを書いてしまったりしている部分もあります。ここで、そうした部分について補足的に説明をしていきたいと思います。
もし、読者の中で「これはおかしいんじゃないか?」「このほうがより正確では?」ということがありましたらどうぞ御連絡下さいませ。ここで補足させていただきたいと思います。
本書では、重量コンポーネントのGraphicsについて、以下のような内容の記述があります。――コード内でgetGraphics()で得たGraphicsオブジェクトは、ネイティブなオブジェクトとして、(Java仮想マシン内ではなく)システムのヒープ内にグラフィックコンテキストを作成し、そこで処理される。これはdispose()しないとそのままヒープを消費したままとなる。
ヒープ内にネイティブなグラフィックコンテキストが作成されるのは確かですが、dispose()しないとそのままヒープを消費し続けるというのは間違いです。ガベージコレクタにより、いつか(?)は回収され破棄されるはずです。本書では、これはガベージコレクタによって破棄されないように記述してありましたが、これは誤りでした。しかし、dispose()しないとシステムのヒープを一時的に大きく消費してしまうことは確かで、「getGraphics()したものは最後にdispose()する」という原則に変わりはありません。
本書では、変数と値についてなるべくスペースをさいて説明したつもりですが、参照についてはあまり触れていませんでした。「参照とは、そもそも何なのか?」と思われる人もいたかも知れません。本書では、参照とは「オブジェクトがメモリ内にある場所を示す値」と説明しています。これを読んで「では、ポインタのことなのか?」と思った人もいることでしょう。
その通りで、参照とは、ポインタのことと考えて間違いありません。 ポインタとは、変数や関数などのあるアドレスを示す値で、PascalやCといった言語で採用されています。本書を読んで、Javaではポインタはないというニュアンスで受け取っている人も多いかも知れませんが、よく読んでいただければ、「Javaではポインタは操作できない」と説明していることに気づいたことでしょう。ポインタが存在しないというわけではないのですね。――ただしJavaでは、オブジェクトのポインタは「参照(reference)」で統一されており、ポインタという用語は使わないようになっているようです。というわけで、本書でもポインタという用語は用いていません。
参照の値でわかりにくいのは、メソッド呼び出しなどで渡される際の「参照渡し」というものでしょう。値渡しと参照渡しの違いが感覚的にわかりにくかったという人は多いかも知れません。――本書では「オブジェクトそのものが複製されて渡されるのではなく参照だけが渡される」と説明してあります。これはすなわち、オブジェクトを引数で渡す時は「『参照』という値が『値渡し』される」ということなのです。従って、全ての引数は値渡しである、ともいえます。
本書の第6章にて、閉じられた領域内を塗りつぶすプログラムを作成しています。これはPixelGrabberを使ったもので、一応ちゃんと動きはしますが、実用にならないほど遅いものです。あくまで「アルゴリズムの例」としてなので、実用にならないとわかっていても掲載をした、という感があります。
その後、Java2 ver. 1.3よりサポートされているjava.awt.Robotパッケージを利用することで、遥かに高速な塗りつぶしが行えることがわかりましたので、ここで補足しておきます。Robotは、本来操作を自動化するためのオブジェクトで、プログラムの自動検証などを目的としたものです。この中に、スクリーン内の特定のピクセルのColorを得るメソッドがあり、これを利用することで塗りつぶしが高速化されます。以下にサンプルコードをあげておきますので、本書のコードと比較してみて下さい。ただしRobotを用いた方法は、ImageやGraphicsオブジェクトを調べているわけではなく、あくまで「スクリーンに表示されているもの」を調べているだけです。このため、他のウィンドウと重なっているなど条件によっては正しく機能しない可能性もあることを了承の上で使って下さい。
※Robot利用のGraphicsCanvas(http://www.h5.dion.ne.jp/~tuyano/Tankoubon/GraphicCanvas.text)
本書の6章ではいくつかのサンプルプログラムを紹介してありますが、その中には、いくつもの値を返すメソッドの類いがあります。よく見ると、こうしたものでは以下の2つのやり方をしているものがあることがわかります。
1.引数に空の配列を渡して呼び出す。すると計算した値がそれに収められる。
2.メソッド内で配列を作成し、それをreturnで返す。
今までにC言語などを使ったことのある人の中には、奇妙な感じを受けた人もいるかも知れません。通常、配列などに値をおさめる必要がある場合には、引数であらかじめ配列を用意してやり、そこに値が収められるようにするものです。2のような書き方はしないでしょう。メソッド内で作成された配列のスコープはそのメソッド内なのですから、メソッドを抜けた時点でその配列は消滅してしまわないか?という疑問を持つ人もいるかも知れません。
実は、Javaではこうした書き方をしても問題なく実行されるのです。Javaでは、オブジェクトはガベージコレクタによって消去されますが、そのオブジェクトが参照されていないかチェックし、どこからも参照されていないものから消されていきます。returnされた参照が使われている間は、そのオブジェクトが作成されたメソッドを抜けた後も、オブジェクトが勝手に消えてしまうことはありません。「Javaでは配列もオブジェクトである」ということを思い出して下さい。
★FAQコーナー★ |
※ここでは、本書の内容についての質問にお答えします。読んでいて不明な点などありましたら、どうぞお問い合わせください。その場合は、必ずお使いのハードウェア環境、OS、及びJDKのバージョン等について明記ください。
答え)Javaでは、ソースコードの記述ミスなどによる文法エラーの他に、ファイル名によるミスが意外に多いようです。本書にて説明したように、Javaのソースコードファイルは、記述されているpublicなクラス名と同名のものでなければいけません。もちろん、大文字小文字までぴったり同じである必要があります。まずは、これをよく確認下さい。
また、初心者の多くは、ファイル名の末尾に「txt」の拡張子がついているのに気づかない、ということもあります。特に、特定の拡張子を非表示にするよう設定してあった場合には気づきにくいものです。この点も、よく確認下さい。
答え)Excutable JARファイルは、mainメソッドを持ったクラスをマニフェストファイルで指定します。ここがうまくいっていなくて起動できないことは多いようです。例えば、メインクラスの指定を「Main-Class:
Test.class」というようにclass拡張子まで指定してしまうとうまく認識できません。また、「最後は改行しておく」こと。これも意外に引っ掛かることは多いようです。
答え)「それで動く機能だけで作られている限り問題ない」と考えて下さい。Java2では新たに多くの機能が追加されていますから、それらを使ってあるプログラムはもちろんJava2以前では動きません。しかし、従来のJDK
1.1からあった機能だけを使ったものならば、Java2で作成したものもJDK 1.1で動きます。反対に、1.1で作ったものをJava2のJVMで動かすことももちろん可能です。Java2のコンパイラ(javac)は、JVM
1.1とJava2のJVMの両方に互換性のあるバイトコードを出力するようになっているとのことです。従って、作成したクラスファイルそのものは、1.1でもJava2でも問題なく使えるはずです。
特にWebブラウザで動くアプレットなどを作る場合、Internet Explorerでは未だにJVM 1.1のまま(Netscapeでは6からJava2となった)なので「1.1で作らないと動かないのでは?」と思う人もいるかも知れませんが、Java2で作成してもこれらで動くものを作ることができます。