回答が自明であるものについては、回答は割愛しました。


(4)

例えば、以下のように Sample01a.class を Sample01a.txt にファイル名を変更して java を実行します。

C:\>java Sample01a
Exception in thread "main" java.lang.NoClassDefFoundError: Sample01a

java は Sample01.class というファイルを探しており、それが見当たらないために「クラスが見つからない」というエラーを表示しています。

(5)

例えば、以下のように Sample01a.class を SAMPLE01a.class にファイル名を変更し、java を実行します。

C:\>java SAMPLE01a
Exception in thread "main" java.lang.NoClassDefFoundError: SAMPLE01a (wrong name: Sample01a)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
at java.net.URLClassLoader.access$100(URLClassLoader.java:56)
at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:268)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)

java は SAMPLE01a.class をファイルを探し、それを発見しますが、そのファイル中に SAMPLE01a という名のクラスが見つからないとエラーを報告しています。なぜなら、ファイル中にあるクラスは Sample01a であって、SAMPLE01aではないからです。



step02

(1)

例えば、以下のように、Step 02 の Sample01a.java (中に記述されているクラスは Sample01a) を、X.java とファイル名を変更したとします。

C:\>javac X.java

X.java:1: クラス Sample01a は public であり、ファイル Sample01a.java で宣言しなければなりません。
public class Sample01a {
^
エラー 1 個

javac はファイル名とその中に書かれているクラス名が一致していると想定しているので、異なれば上記のように「ファイルは X なのにクラス名がSample01a ではおかしく、ファイル名は Sample01a のはずです」とエラーを報告します。

(2)

例えば、問題にあるソースプログラムを Sample02a.java というファイルに作成したとします。

C:\>javac Sample02a.java
Sample02a.java:3: クラス Sample02b は public であり、ファイル Sample02b.java で宣言しなければなりません。
public class Sample02b {
^
エラー 1 個

エラーは問1 のものと同じです。この場合もクラス名とファイル名が異なっていると報告しています。

(3)

コンパイルは可能ですが、main () がないために直接 java から呼び出す (実行する) ことはできません。強引に java で実行しようとすれば、main () メソッドを探しても見つからないために、以下のようなエラーになります。

C:\>javac Sample02a.java
C:\>java Sample02a
Exception in thread "main" java.lang.NoSuchMethodError: main



(2)

自明ですが、こんな感じになりますね。

public class Sample03a {
public static void main (String[] args) {
System.out.println ("朝刊太郎");
}
}

(3)

public class Sample03b {
public static void main (String[] args) {
System.out.print ("2");
System.out.print (". ");
System.out.print ("東");
System.out.println ("京");
}
}

"2." と " " (スペース) に分けるてもかまいません。

(4)

public class Sample03b {
public static void main (String[] args) {
System.out.print (123); // 数字
System.out.print ("123"); // 文字列
}
}

結果はどちらも同じです。

(5)

例えば、System.out.println ("Q1: ベトナム\n") のように置き換えます。



(1)

int id = 1;
System.out.println (id + ". ハノイ");

(2)

以下のようなエラーが表示されます。

test.java:3: 精度が落ちている可能性
検出値 : double
期待値 : int
int i = 3.1415;
^
エラー 1 個

(3)

x = x + 1;
x += 1;
x ++;

(4)

コードは次のようになります。

int i = 1;
double d = 1;
System.out.println ("1: int=" + i + " double=" + d);

結果は以下のようになります。

1: int=1 double=1.0

上記のように、System.out.println () は、int ならば小数点以下がないとして 1 だけを、double ならば 1.0 と表示します。

(5)

コードは次のようになります。

double d = 10.3 / 1.2;
float f = 10.3F / 1.2F;
System.out.println ("10.3/1.2: double=" + d + " float=" + f);

結果は以下のようになります。

10.3/1.2: double=8.583333333333334 float=8.583333

このように、double ならば少数以下の桁数が倍程度になります。

(注) 単純に float f = 10.3 / 1.2 とすると、java は 10.3 と 1.2 をdouble として計算し、その結果を桁数の少ない float に代入しようとするため、問2 のように「精度が落ちている可能性」というエラーを報告し、桁落ちがあることを示します。そのため、数値が float であることを明示するよう、「10.3F」「1.2F」と書く必要があります。このことは Step 04 では説明していませんでした (ので、この問題が解けなくても OK です。ごめんなさい)。これは Step 06 で取り上げます。



(1)

p. 25 のコードと基本的には同じです。

public class test {
public static void main (String[] args) {
int qID = 1;
String nation = "ベトナム";
System.out.println ("Q" + qID + ": " + nation);
qID ++;
nation = "東京";
System.out.println ("Q" + qID + ": " + nation);
}
}

qID ++ の箇所は、qID = 2 でもかまいません。

(2)

表紙裏のコード表から、X は 88 であることがわかります。

public class test {
public static void main (String[] args) {
char c = 88;
System.out.println (c);
}
}

(3)

次のようなエラーになります。

test.java:3: 演算子 - は java.lang.String,java.lang.String に適用できません。
System.out.println ("Hello" - "World");
^
エラー 1 個

同様に、* (乗算) や / (除算) もエラーになります。このことから、文字列の演算は + (加算) のみ可能であることがわかります。

(4)

例えば、char に1文字だけからなる文字列を代入すると、java は次のようなエラーを報告します。

C:\home\work\Cutt-Java30\src>javac test.java
test.java:3: 互換性のない型
検出値 : java.lang.String
期待値 : char
char c = "t";
^
エラー 1 個

java は問2 にみるように、char に代入されるものは ASCII コードの数値です。そこで、'' で囲われた文字は数値に変換してから代入します。それ以外のケースでも変換を試みますが、この場合は、1個の数値に変換することができないため「互換性がない」とエラーを報告します。

String に '' で囲われた文字を代入しようとすると、java は次のようなエラーを報告します。

test.java:4: 文字リテラルが閉じられていません。
String s = 'test';
^
test.java:4: 文字リテラルが閉じられていません。
String s = 'test';
^
test.java:5: ';' がありません。
}
^
エラー 3 個

これは、" を前後に書き忘れているのではないか、という指摘です。ですから、文字列の前後にわたって2回エラーが出ているわけです。

(5)

前者の結果は「3」。これは、1 と 2 を足した結果を表示しているからです。後者の結果は「1 + 2 = 12」です。これは、まず最初に文字列があることから、このカッコ内にある加算はすべて文字列の連結と解釈するからです。したがって、1 + 2 は 12 になります。



(1)

コードは次のようになります。数値が float または double であることを明示する必要があります。F や D がないと整数演算とみなされるため、結果はどちらも 4.0 (たとえば 1/3 は 0) になってしまいます。

float pif = 4F * (1F - 1F/3F + 1F/5F - 1F/7F + 1F/9F - 1F/11F + 1F/13F);
double pid = 4D * (1D - 1D/3D + 1D/5D - 1D/7D + 1D/9D - 1D/11D + 1D/13D);
System.out.println (pif + " " + pid);

結果は次の通りです。

3.2837384 3.2837384837384844

(2)

public class test {
public static void main (String[] args) {
byte b8 = 0100;
byte b10 =64;
byte b16 = 0x40;
System.out.println (b8 + " " + b10 + " " + b16);
}
}

(3)

acde

\b でその前にある "b" が消えるから。

(4)

問題に誤植がありました。"abcd\rABC" (A の前には \ はない) です。結果は、"ABCd" になります。"\r" により、abcd 表示後にカーソル位置が先頭に戻るため、もともとあった abc を ABC に置き換えてしまうから。

(5)

System.out.print ("\\\\\\\\\n");

\ は全部で9つ。\\ で \ が1つ表示されるので、これが4組。そして、最後の改行用に "\n" があるので、合計9つになります。もちろん、以下のように \8つで書いてもかまいません。

System.out.println ("\\\\\\\\");



(1) 解法1 (逐次代入)

public class test {
public static void main (String[] args) {
String capitals[] = new String[4];
capitals[0] = "ハノイ";
capitals[1] = "東京";
capitals[2] = "リーブルヴィル";
capitals[3] = "ティンプー";
System.out.println (capitals[0] + " " + capitals[1] + " " +
capitals[2] + " " + capitals[3]);
}
}

(1) 解法2 (宣言時の一気代入)

public class test {
public static void main (String[] args) {
String capitals[] = {"ハノイ", "東京", "リーブルヴィル", "ティンプー"};
System.out.println (capitals[0] + " " + capitals[1] + " " +
capitals[2] + " " + capitals[3]);
}
}

(2)

public class test {
public static void main (String[] args) {
double root[] = {1.0, 1.4142, 1.7321, 2.0};
System.out.println ("1 ->" + root[0]);
System.out.println ("2 ->" + root[1]);
System.out.println ("3 ->" + root[2]);
System.out.println ("4 ->" + root[3]);
}
}

配列は 0 からスタートすることに注意すること。補記にある Math.sqrt ()を用いるなら、配列の代入部分は以下のように書くこともできます (ただし、精度が小数点4桁以上になります)。

double root[] = {Math.sqrt(1), Math.sqrt(2), Math.sqrt(3), Math.sqrt(4)};

(3)

public class test {
public static void main (String[] args) {
double root[] = {1.0, 1.4142, 1.7321, 2.0};
root[0] *= root[0];
root[1] *= root[1];
root[2] *= root[2];
root[3] *= root[3];
System.out.println ("1 ->" + root[0]);
System.out.println ("2 ->" + root[1]);
System.out.println ("3 ->" + root[2]);
System.out.println ("4 ->" + root[3]);
}
}

2乗部分は、root[0] = root[0] * root[0] でも OK です。

(4)

(1) なら System.out.println (capitals.length);
(2) なら System.out.println (root.length);

(5)

負の値でも、配列サイズ以上の値でも、コンパイル時にはエラーは出ず、実行時に出る点に注意してほしい。以下の実行時のエラーは、指定された配列以外の値が用いられている (out of bound とは「範囲外」という) ことを示しています。

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
at test.main(test.java:8)



(1)

5行目と9行目を以下のように変えます。

5 int index = 1;
9 choiceID = index + 2;

(2)

8〜12行目を以下のように変えます。

do {
choiceID = index + 1;
System.out.println (choiceID + ". " + capitals[index]);
index ++;
} while (index < capitals.length);

(3)

ずっと「1. 東京」を表示する無限ループになります。

まず、最初に1であった choiceID (5行目) をもとに、8行目が実行されます。8行目では、まず先に capitals[1] をもとに表示が実行され、その後で1減ぜられるため、choiceID は0になります。続く9行目で1加算するので、ここでまた1に戻ります。以下、これが繰り返されます。

(4)

最初に index は 0 なので、10行目で表示されるのは capitals[0] (つまり、「ハノイ」) です。11行目でこれが1減ぜられると -1 となり、ループを回って2度目に10行目を実行すると、配列サイズ違反となりエラーになります。

実際に実行してみれば、次のようになります。

1. ハノイ
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
at test.main(test.java:11)

(5)

public class test {
public static void main (String[] args)
{
int index = 1;
while (index < 10) {
System.out.println ("7 x " + index + " = " + index * 7);
index ++;
}
}
}



(1)

while (index <= capitals.length - 1)

(2)

while (index >= 0 && index < capitals.length)

index は、最初に0にセットされ (5行目)、加算 (11行目) されていくだけなので、論理的に考えれば、index は常に0以上です。ですから、index >= 0 部分にはあまり意味はありません。ただ、ループ内で index の計算で複雑なことをしており、0未満になることがありえる状況なら、配列サイズ違反を避けるためにこのような比較が必要になることもあります。

(3)

while (index < 1) または while (index <= 0)

(4)

(1 == 1) は常に満たされているため、無限に表示されます。

(5)

true と表示されます。System.out.println () の括弧内の条件文は x = 1 なので満たされるので、結果は boolean true になります。



(1)

15〜22行目を以下に置き換えます。

int capitalIndex = 0;
int qID = 1;
while (qID < 5) {
System.out.println ("Q" + qID + ": " + nations[qID-1]);
int i = 1;
while (i < 5) {
System.out.println (i + ". " + capitals[capitalIndex]);
capitalIndex ++;
i ++;
}
qID ++;
}

(2)

15〜22行目を以下に置き換えます。

int capitalIndex = capitals.length - 1;
for (int qID=1; qID<5; qID++) {
System.out.println ("Q" + qID + ": " + nations[nations.length-qID]);
for (int i=1; i<5; i++) {
System.out.println (i + ". " + capitals[capitalIndex]);
capitalIndex --;
}
}

(3)

15〜22行目を以下に置き換えます。

for (int index=0; index<4; index+=2) {
int qID = index + 1;
System.out.println ("Q" + qID + ": " + nations[index]);
for (int i=0; i<4; i++) {
int capitalIndex = index * 4 + i;
int choiceID = i + 1;
System.out.println (choiceID + ". " + capitals[capitalIndex]);
}
}

ループ条件文では、配列のインデックスを0以上4未満 (0, 1, 2, 3) にしても、問題番号/選択肢番号 (1, 2, 3, 4) にしてもかまいません。ここで前者にしているのは、選択肢配列を計算しやすいからです。問題を奇数番にするには1つおきにループカウンタ (index) を増やします。この問題に対する選択肢配列のインデックスは、その index の4倍 (問題が0番なら 0 から、1番なら4から) になっているので、index * 4 + 1 で計算します。

capitalIndex が、1つの問題について4つの選択肢まで順に増加したあとで、次の奇数番問題の選択肢の先頭を指すようにするには、4つ増加させればよいので、次のようにも書けます。

int capitalIndex = 0;
for (int index=0; index<4; index+=2) {
int qID = index + 1;
System.out.println ("Q" + qID + ": " + nations[index]);
for (int i=0; i<4; i++) {
int choiceID = i + 1;
System.out.println (choiceID + ". " + capitals[capitalIndex]);
capitalIndex ++;
}
capitalIndex += 4;
}

(4)

public class test {
public static void main (String[] args)
{
for (int x=1; x<10; x++) {
System.out.print (x + ": ");
for (int y=1; y<10; y++) {
int z = x * y;
System.out.print (z + " ");
}
System.out.println ("");
}
}
}

なお、列をきれいにそろえたい場合、Java 1.5 以上なら System.out.printf() が利用できます。詳細は、java.io.PrintWriter の printf () メソッドを参照してください。

(5)

問4の6行目 (内側のループ) を以下のように変えます。

for (int y=1; y<x+1; y++)



(1)

9〜11行を以下と置き換えます。

if (correct[i] == answer[i]) {
System.out.println (i + " 当たり!");
score ++;
}

(2)

9〜11行 (for ループの内側) を以下と置き換えます。

String judge = "外れ";
if (correct[i] == answer[i]) {
judge = "当たり!";
score ++;
}
System.out.println (i + " " + judge);

(3)

public class test {
public static void main (String[] args)
{
int i = 0;
while (i < 100) {
System.out.print (i + " ");
i ++;
if (i % 10 == 0) {
System.out.println ("");
}
}
}
}

肝は、if 文にある "i % 10 == 0" です。% は剰余 (p. 24) で、割り算をしたときの余りを計算します。例えば、i が10や20のとき、割り切れるので0になります。これを知らない場合は、改行印字用に別途カウンタを設けます。このカウンタを cr とすると、以下のようになります。

public class test {
public static void main (String[] args)
{
int i = 0;
int cr = 0;
while (i < 100) {
System.out.print (i + " ");
i ++;
cr ++;
if (cr == 10) {
System.out.println ("");
cr = 0;
}
}
}
}

(4)

public class test {
public static void main (String[] args)
{
for (int year=2000; year<2200; year++) {
int days = 365; // 普通の年
if (year % 4 == 0) {
days = 366; // 4で割り切れればうるう年
if (year % 100 == 0) {
days = 365; // 4と100で割り切れれば普通の年
if (year % 400 == 0) {
days = 366; // 4と100と400で割り切れればうるう年
}
}
}
System.out.println (year + "->" + days);
}
}
}

なお、2000年から2199年までの間でうるう年なのは、2000年から4年おきの年(2000, 2004, 2008, ...) ですが、2100年は100で割り切れるので、うるう年ではありません。2000年も100で割り切れますが400で割り切れるので、これはうるう年です。

(5)

public class test {
public static void main (String[] args) {

String[] str = {
"スウェーデン",
"ロンドン", "マスカット", "ボゴタ", "ストックホルム",
"ドミニカ共和国",
"サントドミンゴ", "トビリシ", "プノンペン", "ホニアラ",
"スロベニア",
"リュブリャナ", "ファドゥーツ", "アンタナナリボ", "テグシガルパ",
"ベラルーシ",
"キエフ", "ミンスク", "カイロ", "パナマシティ"
};

int qID = 0;
int choiceID = 0;
for (int index=0; index<str.length; index++) {
if (index % 5 == 0) {
qID ++;
choiceID = 0;
System.out.println ("Q" + qID + ": " + str[index]);
}
if (index % 5 != 0) {
System.out.println (choiceID + ". " + str[index]);
choiceID ++;
}
}
}
}

もちろん、他にも方法はいろいろあります。



(1)

8〜12行目を次のように書き換えます。

for (int i=0; i<correct.length; i++) {
if (correct[i] == answer[i]) {
System.out.println (i + " あたり!");
score ++;
}
else {
System.out.println (i + " はずれ!!");
}
}

(2)

(a) <= と >= を使う場合、5〜16行を次のように書き換えます。

if (n >= 0 && n <= 1) {
System.out.println (n + " = 1年生");
}
else if (n >= 2 && n <= 4) {
System.out.println (n + " = 2年生");
}
else if (n >= 5 && n <= 7) {
 System.out.println (n + " = 3年生");
}
else {
System.out.println (n + " = 4年生以上");
}

(b) < と > を使う場合、5〜16行を次のように書き換えます。

if (n > -1 && n < 2) {
System.out.println (n + " = 1年生");
}
else if (n > 1 && n < 5) {
System.out.println (n + " = 2年生");
}
else if (n > 4 && n < 8) {
System.out.println (n + " = 3年生");
}
else {
System.out.println (n + " = 4年生以上");
}

(3)

5〜16行を次のように書き換えます (<= と >= の場合)。

String year = "";
if (n >=0 && n <= 1) {
year = "1年生";
}
else if (n >=2 && n <= 4) {
year = "2年生";
}
else if (n >= 5 && n <= 7) {
year = "3年生";
}
else {
year = "4年生以上";
}
System.out.println (n + " = " + year);

(4)

5〜16行を次のように書き換えます (<= と >= の場合)。

String year = "";
if (n <= 1) {
year = "1年生";
}
else if (n <= 4) {
year = "2年生";
}
else if (n <= 7) {
year = "3年生";
}
else {
year = "4年生以上";
}
System.out.println (n + " = " + year);

(5)

public class test {
public static void main (String[] args)
{
int days;

for (int year=2000; year<2200; year++) {
if (year % 400 == 0) {
days = 366;
}
else if (year % 100 == 0) {
days = 365;
}
else if (year % 4 == 0) {
days = 366;
}
else {
days = 365;
}
System.out.println (year + "->" + days);
}
}
}



(1)

7〜11行目を以下のように書き換えます。

int i = 0;
while (true) {
if (data[i] == 8) {
break;
}
i ++;
}

(2)

8を含まないような配列だと、最後に配列のサイズをオーバーしてエラーを発生することになるので、8が含まれていることが確実でなければ問題となります。このような問題を発生させないようにするには、if (i > data.length)のような判断文を追加し、i (ループカウンタ) が配列サイズをオーバーしたらループを抜けるようにする必要があります (なお、それならば本文のプログラムの方がずっと簡潔です)。

(3)

8行目を以下のように書き換えます。

if (data[i]==8 && data[i+1]==1 && data[i+2]==4) {

ただ、i が配列の末尾から3番目 (Sample13a.java の5行目の "8") でループを終わらないと配列サイズオーバーエラーになるので、7行目の条件文を変更しておく必要があります。

for (i=0; i<data.length-2; i++) {

(4)

8〜11行目を以下のように書き換えます。

if (data[i] == 3) {
count ++;
System.out.println ("Found 3 in " + i);
}

(5)

8〜11行目を以下のように書き換えます。

if (data[i] == 1) {
System.out.println ("Found 1 in " + i);
count ++;
}
if (count == 2)
break;



(1)

クラス名もコンストラクタ名も NotTruelyExisting

(3)

char[] charStr = {'T', 'h', 'i', 's', ' ',
'i', 's', ' ', 'a', ' ',
't', 'e', 's', 't'};
String strStr = new String (charStr);

(4)

byte[] byteStr = {72, 101, 108, 108, 111, 32,
87, 111, 114, 108, 100, 33};
String strStr = new String (byteStr);

ASCII コード表については、表紙裏を参照してください。

(5)

public class test {
public static void main (String[] args)
{
String strStr = new String ("Hello World!");
strStr = strStr.toUpperCase ();
System.out.println (strStr);
}
}



(1)

String str = "abcd:123";
int loc = str.indexOf (':');
System.out.println (": in " + str + " at " + loc);

(2)

String str = "abcd.efgh.ijkl.mnop";
int loc1 = str.indexOf ('.');
int loc2 = str.indexOf ('.', loc1+1);
System.out.println (". in " + str + " at " + loc2);

(3)

String str = "a";
str = str.concat ("b");
str = str.concat ("c");

上記は、1つにまとめることもできます。

String str = "a".concat("b").concat ("c");

(4)

int len = "0123456789".length ();

(5)

+ 演算時にはコンパイラが暗黙に型変換を行なうが、concat () メソッドでは引数が文字列であると定められているからです。



(1)

double d = Double.parseDouble ("3.1415");

(2)

public class test {
public static void main (String[] args)
{
String s = "3.1415";
double d1 = 1.0;
double d2 = Double.parseDouble (s);
if (d2 > d1) {
System.out.println ("larger!");
}
}
}

d1 の値を適当に変えて試してください。

(3)

public class test {
public static void main (String[] args)
{
float f = 1.234F;
Float fFloat = new Float (f);
System.out.println ("Number is " + fFloat.toString());
}
}

上記は、System.out.println ("Number is " + f) とやっても、暗黙に文字列への変換が行なわれるので同じことになります。

(4)

public class test {
public static void main (String[] args)
{
String s = "123456";

// 解法1
String s1 = s.substring (0, 1);
int i1 = Integer.parseInt (s1) + 3;
System.out.println ("Number is " + i1);

// 解法2
int i2 = Integer.parseInt (s) / 100000 + 3;
System.out.println ("Number is " + i2);
}
}

解法1では、先に先頭文字を取得してから数値に変換しています。解法2では先に全体を数値に変換し、それを100000で割ることで最初の値を取得しています(float で計算すると 1.23456 になりますが、int なので小数点以下が切り捨てられます)。解法2の方が短そうに見えますが、s の桁数があらかじめわかっていなければそれを算出する必要があるため、汎用性に欠けるという問題があります。

(5)

public class test {
public static void main (String[] args)
{
String s = "123.456";
int loc = s.indexOf ('.');
int i1 = Integer.parseInt (s.substring(0, loc));
int i2 = Integer.parseInt (s.substring(loc+1));
int i = i1 + i2;
System.out.println (i1 + " + " + i2 + " = " + i);
}
}



(1)

たとえば、以下のプログラムを作成したとします。

public class test {
public static void main (String[] args)
{
Integer i = new Integer (100);
System.out.println ("i = " + i.value);
}
}

コンパイルをすると、次のようなエラーが報告されます。

C:\>javac test.java
test.java:6: value は java.lang.Integer で private アクセスされます。
System.out.println ("i = " + i.value);
^
エラー 1 個

エラーは "private" と言っており、そうした名称のフィールドは存在するけれども、そのフィールドは外部からはアクセスできない (読み書きできない)と宣言されているため、エラーになります。

(2)

問(1) のプログラムの i.value を i.nonExistingField に変えると、次のようなエラーがコンパイル時に報告されます。

C:\>javac test.java
test.java:5: シンボルを見つけられません。
シンボル: 変数 nonExistingField
場所 : java.lang.Integer の クラス
System.out.println ("i = " + i.nonExistingField);
^
エラー 1 個

上記と異なり、今度はそうしたフィールドが存在しないため「シンボルを見つけられません」(シンボルは変数名のこと) となります。

(3)

次のコードを含むプログラムをコンパイルしてみます。

Byte b = new Byte ("10");
b.MAX_VALUE ++;

すると、コンパイラは以下のようなエラーを報告します。

C\:>javac test.java
test.java:5: final 変数 MAX_VALUE に値を代入することはできません。
b.MAX_VALUE ++;
^
エラー 1 個

「final 変数」とは、そのクラスのフィールド変数としては他の変数と同じですが、その値は「最終的 (final)」なものであり、もう変更することはできないと宣言されている変数を指します。

(4)

public class test {
public static void main (String[] args)
{
System.out.println ("byte: bits=" + Byte.SIZE +
", max=" + Byte.MAX_VALUE +
", min=" + Byte.MIN_VALUE);
System.out.println ("short: bits=" + Short.SIZE +
", max=" + Short.MAX_VALUE +
", min=" + Short.MIN_VALUE);
System.out.println ("int: bits=" + Integer.SIZE +
", max=" + Integer.MAX_VALUE +
", min=" + Integer.MIN_VALUE);
System.out.println ("long: bits=" + Long.SIZE +
", max=" + Long.MAX_VALUE +
", min=" + Long.MIN_VALUE);
}
}

(5)

たとえば、配列中の値の最小を得るプログラムを書くときなど、理論上の最大値を用いると便利です。このようなプログラムでは、配列中の値を1個ずつ対象となる値と比較し、配列値がそれよりも小さいときにその時点で最小だとして比較値を入れ替えます。まず、最初にこの比較対象値を配列の最初の値とし、以下、順に比較していきます。

double val[] = {-17.0, 10.2, 30.23, -10.12, 20.2, -15.0};
double min = val[0];
for (int i=1; i<val.length; i++) {
if (val[i] < min) {
min = val[i];
}
}

この比較値の最初の値を、理論上の最大値にしておけば、配列0からスタートするループで、最初の配列が必ず min に代入されます。

double min = Double.POSITIVE_INFINITY;
for (int i=0; i<val.length; i++) {
if (val[i] < min) {
min = val[i];
}
}



(2)

7〜8行目を次のように置き換えます。

for (int i=0; i<len; i++) {
System.out.print (b[i] + ", ");
}

(5)

問(2) のコードを実行すればわかります。



(1)

いくつか方法は考えられますが、while ループの条件文部分を固定 (true) から変数に変更し、回答範囲外ならば続行、範囲内ならば条件文変数を falseに変更するように書き換えるという手があります。7〜19行を以下のように変更します。

int answer = 0;
boolean loop = true;

while (loop) {
System.out.print ("==>");
System.in.read (b);
answer = Integer.parseInt ((new String (b)).substring (0, 1));
if (answer < 1 || answer > numberOfChoices) {
System.out.println ("正しい選択ではありません。再入力願います");
continue;
}
loop = false;
}

(2)

b[0] にはキーボード入力のあった最初の文字が含まれています。まず、これを Byte にした (new Byte) 上で、整数値に変換します (intValue)。この変換後の値は、入力文字の ASCII コードを示しています。キーボード入力される文字の "1", "2", "3", "4" は、ASCII コードでそれぞれ 49, 50, 51, 52です。そこで、変換後の値から 48 を引けば、それぞれ整数の 1, 2, 3, 4 になります。

(3)

っと、これは適切な問題ではなかったですね。強引にやるならば、1) 1文字格納できる byte[] 配列を別途用意し、2) そこに11行で得られた byte 列の最初の値 (b[0]) を代入し、3) 文字列に直し、4) 整数に直す、という手順になります。つまり、12行目は以下のようになります。

byte[] x = new byte[1];
x[0] = b[0];
answer = Integer.parseInt (new String(x));

かっこよくありません。ごめんなさい。

(4)

スコープ外の21行目で answer を参照しているため、コンパイラが以下のようなエラーを報告します。

C:\>javac test.java
Sample19a.java:21: シンボルを見つけられません。
シンボル: 変数 answer
場所 : Sample19a の クラス
System.out.println ("Answer => " + answer);
^
エラー 1 個



(2)

Sample19a.java で使用されているクラスは System (10, 11, 17, 21行目)、Integer (12行目)、String (12行目) で、このうちの最初の2つをインポートするとしたら、次のようになります。

import java.lang.System;
import java.lang.Integer;

(3)

import java.lang.*;

(4)

java.util.jar

(5)

java.lang.Integer.SIZE



(1)

7行目の "nations.txt" を変更します。

(2)

ファイルからのデータ読み込み前に配列が初期化されないため、ファイル末尾で、その直前に読み込んだデータが混入してしまいます。たとえば、最後の読み込みが60バイトだけだったとすると、直前に読み込まれた128バイトの先頭60バイトが最後の読み込み分で置き換えられますが、残りの68バイトがそのまま c[] に残ってしまいます。その他の場合はすべて128バイトを読み込むので、配列中の残留データを完全に上書きするので、問題は起こりません。

nations.txt で試してみると、最終行の「日本,東京」のあとにも、若干データが表示されます。

東ティモール,ディリ
南アフリカ,プレトリア
日本,東京 <-- 本来ならばここで表示は終了
レバノン,ベイルート
ロシア,モスクワ
赤道ギニア,マラボ
大韓民国,ソウル
中央アフリカ,バンギ
中華人民共和国,北京
中華民国(台湾),台北
朝鮮民主主

(3)

BufferedReader br = new BufferedReader (new FileReader ("nations.txt"));

(4)

br.readLine () の結果が String s に代入されると共に null と比較されているので、結果が null ならばループを抜けるようになっている。

(5)

import java.io.BufferedReader;
import java.io.FileReader;

public class test {
public static void main (String[] args)
throws Exception
{
FileReader fr = new FileReader ("nations.txt");
BufferedReader br = new BufferedReader (fr);

int count = 0;
String[] nations = new String[200];
String[] capitals = new String[200];

while (true) {
String s = br.readLine ();
if (s == null)
break;

int position = s.indexOf (',');
nations[count] = s.substring (0, position);
capitals[count] = s.substring (position+1);

System.out.println (count + ": " +
nations[count] + "/" + capitals[count]);
count ++;
}

br.close ();
fr.close ();
}
}



(1)(2)(3)(4)

import java.util.ArrayList;

public class test {
public static void main (String[] args)
throws Exception
{
ArrayList<Integer> iArray = new ArrayList<Integer> ();
int i = 0;

for (i=0; i<10; i++) {
iArray.add (i, (i+1)*(i+1));
}

// 問(3) の追加分
i = 0;
while (i < iArray.size()) {
if (iArray.get (i) % 2 == 0) {
iArray.remove (i);
}
else {
i ++;
}
}

// 問(4) の追加分
iArray.add ("1");
iArray.add (100D);

// 問(2) の追加分
for (i=0; i<iArray.size(); i++) {
System.out.println (iArray.get (i));
}
}
}

なお、問(4) の追加分については、コンパイラがエラーを報告します。

(5)

import java.util.ArrayList;

public class test {
public static void main (String[] args)
throws Exception
{
ArrayList iArray = new ArrayList ();
int i = 0;

for (i=0; i<10; i++) {
Integer power = new Integer((i+1) * (i+1));
iArray.add (i, power);
}

// 問(3) の追加分
i = 0;
while (i < iArray.size()) {
Integer power = (Integer)iArray.get (i);
if (power.intValue() % 2 == 0) {
iArray.remove (i);
}
else {
i ++;
}
}

// 問(4) の追加分
iArray.add ("1");
iArray.add (new Double (100D));

// 問(2) の追加分
for (i=0; i<iArray.size(); i++) {
System.out.println (iArray.get (i));
}

}
}



(1)

import java.util.Random;

public class test {
public static void main (String[] args)
{
Random r = new Random ();
for (int i=0; i<10; i++) {
System.out.println (r.nextDouble());
}
}
}

(2)

import java.util.Random;

public class test {
public static void main (String[] args)
{
Random r = new Random ();
int trueCount = 0;
int falseCount = 0;

for (int i=0; i<1000; i++) {
boolean b = r.nextBoolean ();
if (b == true) {
trueCount ++;
}
else {
falseCount ++;
}
}
System.out.println ("Trues=" + trueCount +
" Falses=" + falseCount);
}
}

(3)

import java.util.ArrayList;
import java.util.Random;

public class test {
public static void main (String[] args)
{
Random r = new Random ();
ArrayList<Integer> iArray = new ArrayList<Integer> ();
int i;

for (i=0; i<20; i++) {
iArray.add (i);
}

i = 0;
while (i < iArray.size()) {
int position = r.nextInt (iArray.size());
int val = iArray.get (position);
System.out.println (val);
iArray.remove (position);
}
}
}

(4)

以下を57行の下に追加します。

if (answer == correct) {
System.out.println ("当たり! " + correct);
}
else {
System.out.println ("はずれ。正解は " + correct);
}

(5)

ランダム回答の正答確率は 25% (1/4)。得点表示は、次のようにします。

a) ループの前に score 変数を用意する。

// 10回繰り返す
int score = 0;
for (int i=1; i<=10; i++) {

b) 当たれば、score を1つ増す (問(4) の回答を次のように修正)。

if (answer == correct) {
System.out.println ("当たり!");
score ++;
}

c) ループの終わりでスコアを表示

System.out.println ("Score = " + score);



(2)

import java.io.*;
import java.util.*;

(3)

41行目を次のように書き換えます。

System.out.println ("Q" + i + ": " + nations.remove (qIndex) +
"(" + capitals.get(qIndex) + ")");

「ずる」バージョンは、プログラムが正しく動作していることをプレーしながら確かめるのに必要です。

(4)

10行目の下あたり (使用前ならどこでもよいのだが、この辺りに全体で利用する変数を用意しているので) に、10 と 4 を格納している変数を次のように用意します。

int numberOfQuestions = 10;
int numberOfChoices = 4;

あとは、37, 46, 69行目を以下のように変更します。

37行目: for (int i=1; i<=numberOfQuestions; i++) {
46行目: for (int j=0; j<numberOfChoices-1; j++) {
69行目: if (answer >= 1 && answer <=numberOfChoices) {

(5)

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Random;

public class test {
public static void main (String[] args)
throws Exception
{
// ========== 準備 =========================
// 得点
int score = 0;

// 国名と首都名を格納する文字列配列
String[] nations = new String[193];
String[] capitals = new String[193];

// ランダムオブジェクトを用意
Random r = new Random ();

// 配列削除操作用のカウンタ
int k;

// ========== ファイルを読む ===============
// まずファイルを読み、ArrayList に格納する
BufferedReader br = new BufferedReader
(new FileReader ("nations.txt"));

String line;
int count = 0; // 使用可能な配列の個数
while ( (line = br.readLine ()) != null ) {
int position = line.indexOf (',');
nations[count] = line.substring (0, position);
capitals[count] = line.substring (position+1);
count ++;
}
br.close ();


// ========= 10回繰り返す ===================
for (int i=1; i<=10; i++) {

// 国名を表示 (首都名はとりあえず保存)
int qIndex = r.nextInt (count);
System.out.println ("Q" + i + ": " + nations[qIndex]);
String correctCapital = capitals[qIndex];

// 表示した国名と首都名は削除
// qIndex から先を1つずつ前にずらす
for (k=qIndex; k<count-1; k++) {
nations[k] = nations[k+1];
capitals[k] = capitals[k+1];
}
count --; // 最後に、配列数を1つ減じる

// 先に選択肢中の正答の位置を決めておく
int correct = r.nextInt (4);
String[] choices = new String[4];

// まず 0 から correct の位置まで誤りを選択肢に格納する
for (int j=0; j<correct; j++) {
int cIndex = r.nextInt (count);
choices[j] = capitals[cIndex];
for (k=cIndex; k<count-1; k++) {
nations[k] = nations[k+1];
capitals[k] = capitals[k+1];
}
count --;
}

// 続いて、正答を格納する
choices[correct] = correctCapital;

// 残りにも誤りを格納する
for (int j=correct+1; j<choices.length; j++) {
int cIndex = r.nextInt (count);
choices[j] = capitals[cIndex];
for (k=cIndex; k<count-1; k++) {
nations[k] = nations[k+1];
capitals[k] = capitals[k+1];
}
count --;
}

// 準備よし。選択肢表示
for (int j=1; j<=choices.length; j++) {
System.out.println (j + ". " + choices[j-1]);
}

// 回答入力
byte[] b = new byte[128];
int answer;

while (true) {
System.out.print ("==>");
System.in.read (b);
answer = Integer.parseInt ((new String (b)).substring (0, 1));
if (answer >= 1 && answer <=4) {
break;
}
else {
System.out.println ("正しい選択ではありません。再入力願います");
}
}
answer --;

// 回答チェック
if (answer == correct) {
score ++;
System.out.println ("当たり");
}
else {
System.out.println ("外れ。正答は " + correctCapital);
}

} // 10回のループ終わり

System.out.println ("得点: " + score);
}
}

配列要素を削除するために、その削除対象からあとの要素を順に1つずつ前にずらすコードが3箇所出てきて見苦しくなっていますが、それらは次の Stepで説明するメソッドを用いることで1箇所にまとめることができます。



(1)

public class test {
public static void main (String[] args)
{
double x = Double.parseDouble (args[0]);
double y = Double.parseDouble (args[1]);
double z = x - y;
System.out.println (x + "-" + y + "=" + z);
}
}

(2)

public class test {
public static void main (String[] args)
{
double x = Double.parseDouble (args[1]);
double y = Double.parseDouble (args[2]);
double z = 0;
String op = " err ";

if (args[0].equals ("add")) {
z = x + y;
op = " + ";
}
else if (args[0].equals ("sub")) {
z = x - y;
op = " - ";
}
else if (args[0].equals ("multi")) {
z = x * y;
op = " * ";
}
else if (args[0].equals ("div")) {
z = x / y;
op = " / ";
}

System.out.println (x + op + y + " = " + z);
}
}

(3)

public class test {
public static void main (String[] args)
{
if (args.length != 3) {
help ();
}
double x = Double.parseDouble (args[1]);
double y = Double.parseDouble (args[2]);
double z = 0;
String op = " err ";

if (args[0].equals ("add")) {
z = x + y;
op = " + ";
}
else if (args[0].equals ("sub")) {
z = x - y;
op = " - ";
}
else if (args[0].equals ("multi")) {
z = x * y;
op = " * ";
}
else if (args[0].equals ("div")) {
z = x / y;
op = " / ";
}

System.out.println (x + op + y + " = " + z);
}

static void help () {
System.out.println ("test <add/sub/multi/div> <num1> <num2>");
System.exit (1);
}
}

(4)

public class test {
public static void main (String[] args)
{
int QUESTIONS = 10;
int CHOICES = 4;
boolean VERBOSE = false; // 追加部分

for (int i=0; i<args.length; i++) {
if (args[i].equals ("-n")) {
QUESTIONS = Integer.parseInt (args[++i]);
}
else if (args[i].equals ("-c")) {
CHOICES = Integer.parseInt (args[++i]);
}
else if (args[i].equals ("-h")) {
help ();
}
else if (args[i].equals ("-v")) { // 追加部分
VERBOSE = true;
}
else {
help ();
}
}
if (VERBOSE) { // 追加部分
System.out.println ("#Q=" + QUESTIONS + ", #C=" + CHOICES);
}
}
// ... 以下 help 部分略
}

(5)

プログラムの最初の部分 (9行目と10行目の間や、21行目と22行目の間など、24-25行目のファイル読み込みの前ならどこでも) に以下を挿入し、25行目の"nations.txt" を以下で宣言した変数 FILE に置き換えます。

String FILE = "nations.txt";
for (int i=0; i<args.length; i++) {
if (args[i].equals("-f")) {
FILE = args[++i];
}
}



(1)

いくつか手段はありますが、例外が発生したら answer を強制的に 0 にすることで、answer が適正値かどうかの判断でひっかかるようにするという手が考えられます (この手は、Step 29 の QuizAnswer.java で用いられています)。

while (true) {
System.out.print ("==> ");
System.in.read (b);
try {
answer = Integer.parseInt ((new String (b)).substring (0, 1));
}
catch (NumberFormatException e) {
answer = 0;
}
if (answer >= 1 && answer <=choices)
break;
System.out.println ("正しい選択ではありません。再入力願います");
}

次のようなコードも考えられます。

while (true) {
System.out.print ("==>");
System.in.read (b);
try {
answer = Integer.parseInt ((new String (b)).substring (0, 1));
if (answer >= 1 && answer <=4) {
break;
}
}
catch (NumberFormatException e) {
}
System.out.println ("正しい選択ではありません。再入力願います");
}

catch ブロックに何も書かれていないのは奇妙かもしれませんが、このtry-catch は、Integer.parseInt () で例外が発生してもプログラムを続行するためだけに書かれていると考えます。

(2)

Sample24a.java (p. 105) の 24-33行目を以下のように書き換えます。

try {
BufferedReader br = new BufferedReader
(new FileReader ("a.txt"));
String line;
while ( (line = br.readLine ()) != null ) {
int position = line.indexOf (',');
nations.add (line.substring (0, position));
capitals.add (line.substring (position+1));
}
br.close ();
}
catch (java.io.FileNotFoundException e) {
System.out.println ("ファイルがみつかりません");
System.exit (1);
}
catch (java.io.IOException e) {
System.out.println ("ファイルに異常あり");
System.exit (1);
}

ファイル読み込みのループを try ブロックに入れたくないのであれば、BufferedReader br を外部で宣言します (単純に while ループを外に出しても、br はスコープ外なのでアクセスできないからです)。

(4)

問題に誤りがありました。ArrayOutOfBoubdsException が発生する前に、nations.size() が -1 となるために、r.nextInt (40行目または47行目) で例外が発生します。

Exception in thread "main" java.lang.IllegalArgumentException: n must be positive
at java.util.Random.nextInt(Random.java:248)
at test.main(test.java:40)

対処する例外を IllegalArgumentException にして問題を考えると、コードは次のようになります (Sample24a.java の 40-50行目)。

String correctCapital = "";
ArrayList<String> choices = new ArrayList<String> ();

try {
int qIndex = r.nextInt (nations.size());
System.out.println ("Q" + i + ": " + nations.remove (qIndex));
correctCapital = capitals.remove (qIndex);

for (int j=0; j<3; j++) {
int cIndex = r.nextInt (nations.size());
choices.add (capitals.remove (cIndex));
nations.remove (cIndex);
}
}
catch (IllegalArgumentException e) {
System.out.println ("データ数不足のため終了します");
System.exit (1);
}

(5)

Sample24a.java の34行目と35行目の間に以下のコードを追加します。

if (nations.size() < 40) {
System.out.println ("ファイルに必要なデータ数がありません");
System.exit (1);
}



(1)

public class Sample27x {
public int x;
public int y;
}

(2)

public class Sample27y {
public static void main (String[] args)
{
Sample27x sp27x = new Sample27x ();

sp27x.x = 10;
sp27x.y = 20;
}
}

(3)

Sample27x.java は以下のようになります。

public class Sample27x {
public int x;
public int y;

public int add () {
return this.x + this.y;
}

public int multiple () {
return this.x * this.y;
}
}

Sample27y.java は以下のようになります。

public class Sample27y {
public static void main (String[] args)
{
Sample27x sp27x = new Sample27x ();

sp27x.x = 10;
sp27x.y = 20;

System.out.println ("add: " + sp27x.add());
System.out.println ("multiply: " + sp27x.multiple());
}
}

(5)

Sample27x.java は以下のようになります。

public class Sample27x {
private int x;
private int y;

public Sample27x (int a, int b) {
this.x = a;
this.y = b;
}

public int add () {
return this.x + this.y;
}

public int multiple () {
return this.x * this.y;
}
}


Sample27y.java は以下のようになります。

public class Sample27y {
public static void main (String[] args)
{
Sample27x sp27x = new Sample27x (10, 20);

System.out.println ("add: " + sp27x.add());
System.out.println ("multiply: " + sp27x.multiple());
}
}



(2)

Hashtable を用いればオブジェクトが1つで済むので、国名/都市名の管理は容易にはなります。しかし、ランダムにデータを選ぶときに、ArrayList のように番号で直接指定ができないので、その部分が多少面倒になります。

(3)

79行目 (p. 126) を次のように書き換えます。

QuizList qList = new QuizList ("C:\temp", "nations.txt");

(4)

72行辺り (どこでもかまいませんが、メソッドはメソッドでまとめて置いたほうがわかりやすいです) に以下を追加します。

public void reverse () {
for (int i=0; i<this.length; i++) {
String key = this.key.get (i);
String val = this.value.get (i);
this.key.set (i, val);
this.value.set (i, key);
}
}

(5)

main (76-92行) の数箇所でフィールド length がじかに使われていますが、private にするとこれらが使えなくなります。そこで、これにアクセスできるよう、たとえば、以下のようなメソッドを作成します。

public int getLength () {
return this.length;
}



(1)

public QuizProblem (String fileName, int choices, boolean cheat)
throws Exception
{
this.choices = choices;
this.values = new ArrayList<String> ();
this.r = new Random ();
this.qList = new QuizList (fileName);
this.cheat = cheat;
}

コンストラクタ QuizList が例外を発するので、このコンストラクタにはthrows Exception か try-catch が必要になります。main 部分には、以下のコードを追加します。

QuizProblem qp291 = new QuizProblem ("nations.txt", numOfChoices, cheat);

System.out.println ("---- 問29(1) ----");
for (int i=0; i<numOfQuestions; i++) {
System.out.println ("Remaings sets: " + qp291.getRemains ());
qp291.makeProblemAndShow (i+1);
}

(2)

常時ずるなしなコンストラクタとオリジナルとの違いは、3番目の引数がないことと、最後の this.cheat が引数指定ではなく固定値の false になっている点だけです。

public QuizProblem (QuizList q, int choices) {
this.choices = choices;
this.values = new ArrayList<String> ();
this.r = new Random ();
this.qList = q;
this.cheat = false;
}

main () での呼び出しは、96行目を以下のように変更して行ないます。

QuizProblem qProb = new QuizProblem (qList, numOfChoices);

(3)(4)

public static int get ()
throws Exception
{
byte[] b = new byte[128];
int answer;

System.out.print ("==> ");
System.in.read (b);
try {
answer = Integer.parseInt ((new String (b)).substring (0, 1));
}
catch (NumberFormatException e) {
answer = 0;
}
if (answer < 1 || answer > 9)
answer = DEFAULT_ANSWER;

return --answer;
}

入力が範囲外 (この場合は 1-9) の場合、再入力を求めるのではなく、プログラムの側で規定値に変更するので、while ループは不要になっています。

(5)

13行目を以下のように変更します。

String input = (new String (b)).trim ();
int len = input.length ();
if (len > 1)
len = 2;
answer = Integer.parseInt (input.substring (0, len));

入力が1文字 (1-9) のときと2文字 (10-99) のときとで、入力から得る文字の長さをそれぞれ変更する必要があります (上記の変数 len)。最初の trim ()は、入力にともなう改行文字を除くためのものです。



(1)

public Quiz (String[] args)
{
try {
for (int i=0; i<args.length; i++) {
if (args[i].equals ("-f"))
this.fileName = args[++i];
else if (args[i].equals ("-n"))
this.problems = Integer.parseInt (args[++i]);
else if (args[i].equals ("-c"))
this.choices = Integer.parseInt (args[++i]);
else if (args[i].equals ("-C"))
this.cheat = true;
else if (args[i].equals ("-h"))
help ();
}
}
catch (Exception e) {
help ();
}
}

呼び出すときは以下のようにします。

Quiz q = new Quiz (args);

(5)

rt.jar が C:\Progra~1\Java\jdk1.5.0_04\jre\lib ディレクトリの下にあるとすれば、以下のように実行することで、rt.jar の中身を確認できます。

C:\>jar tvf \Progra~1\Java\jdk1.5.0_04\jre\lib\rt.jar | more