「プロになるJava」 第3部「Javaの文法」の練習問題解答

「プロになるJava」の第3部「Javaの文法」の練習問題の解答です。

第7章 条件分岐

論理型

1. 「test」に「st」が含まれているかどうかcontainsメソッドで確認してみましょう
jshell> "test".contains("st")
$1 ==> true

値の比較

1. 12と35を<演算子を使って大小比較を行ってみましょう
jshell> 12 < 35
$2 ==> true
2. 12と35を<=演算子を使って等しいかどうか比較を行ってみましょう
jshell> 12 <= 35
$3 ==> true
3. 12と35を==演算子を使って等しいかどうか比較を行ってみましょう
jshell> 12 == 35
$4 ==> false
4. 12と35を!=演算子を使って等しいかどうか比較を行ってみましょう
jshell> 12 != 35
$5 ==> true

オブジェクトの大小比較

1. "test""TEST"compareToメソッドで比較してみましょう。
jshell> "test".compareTo("TEST")
$6 ==> 32

解説: 一致しない最初の文字の差を結果として返します。

jshell> "test".compareTo("tdST")
$9 ==> 1

片方の文字列がもう片方の文字列の先頭部分の場合、長さの差を返します。

jshell> "test".compareTo("tester")
$14 ==> -2
2. 今日の日付と2022年3月15日をcompareToメソッドで比較してみましょう
jshell> LocalDate.now().compareTo(LocalDate.of(2022, 3, 15))
$11 ==> 15

Javadocでの明確な記述は見つけれませんでしたが、日数の差が返ってきますね。 これを書いているのは3月30日です。

jshell> LocalDate.now()
$12 ==> 2022-03-30
3. 今日の日付が2022年3月15日よりも前かどうかisBeforeメソッドで確認してみましょう
jshell> LocalDate.now().isBefore(LocalDate.of(2022, 3, 15))
$13 ==> false

オブジェクトが等しいかどうかの比較

1. 文字列「hello」にtoUpperCaseメソッドを呼び出した結果が「HELLO」であるかどうか確認してみましょう
jshell> "hello".toUpperCase().equals("HELLO")
$16 ==> true

equalsメソッドを使わず==で判定すると、内容は同じだけど別オブジェクトであるということでfalseになります。

jshell> "hello".toUpperCase() == "HELLO"
$17 ==> false
2. 2021年9月14日をLocalDateで表したときに、plusDaysメソッドで10日足した結果が2021年9月24日であるかどうか確認してみましょう
jshell> LocalDate.of(2021, 9, 14).plusDays(10).equals(LocalDate.of(2021, 9, 24))
$18 ==> true

equalsメソッドを使わず==で判定すると、内容は同じだけど別オブジェクトであるということでfalseになります。

jshell> LocalDate.of(2021, 9, 14).plusDays(10) == LocalDate.of(2021, 9, 24)
$19 ==> false

if文での条件分岐

if文

1. 変数aに割り当てる値を変えてみて、表示が変わることを確認しましょう。

変数aに0を割り当てると、0は3よりも小さいので「小さい」が表示されます。

var a = 0;
if (a < 3) {
    System.out.println("小さい");
}

変数aに0を割り当てると、0は3よりも小さいので「小さい」が表示されます。

var a = 0;
if (a < 3) {
    System.out.println("小さい");
}

変数aに5を割り当てると、5は3よりも小さくないので何も表示されなくなります。

var a = 5;
if (a < 3) {
    System.out.println("小さい");
}

変数aに3を割り当てると、3は3よりも小さくないので何も表示されなくなります。

var a = 3;
if (a < 3) {
    System.out.println("小さい");
}

else

1. 変数aに割り当てる値を変えてみて、表示が変わることを確認しましょう。

変数aに0を割り当てると、0は3よりも小さいので「小さい」が表示されます。

var a = 0;
if (a < 3) {
    System.out.println("小さい");
} else {
    System.out.println("大きい");
}

変数aに5を割り当てると、5は3よりも小さくないので「大きい」が表示されます。

var a = 5;
if (a < 3) {
    System.out.println("小さい");
} else {
    System.out.println("大きい");
}

変数aに3を割り当てると、3は3よりも小さくないので「大きい」が表示されます。

var a = 3;
if (a < 3) {
    System.out.println("小さい");
} else {
    System.out.println("大きい");
}

else if

1. 変数aに割り当てる値を変えてみて、表示が変わることを確認しましょう。

変数aに0を割り当てると、0は3よりも小さいので「小さい」が表示されます。

var a = 0;
if (a < 3) {
    System.out.println("小さい");
} else if (a < 7) {
    System.out.println("中くらい");          
} else {
    System.out.println("大きい");
}

変数aに5を割り当てると、5は3よりも小さくなく、7よりも小さいので「中くらい」が表示されます。

var a = 5;
if (a < 3) {
    System.out.println("小さい");
} else if (a < 7) {
    System.out.println("中くらい");          
} else {
    System.out.println("大きい");
}

変数aに10を割り当てると、10は3よりも小さくなく、7よりも小さくないので「大きい」が表示されます。

var a = 10;
if (a < 3) {
    System.out.println("小さい");
} else if (a < 7) {
    System.out.println("中くらい");          
} else {
    System.out.println("大きい");
}

変数aに7を割り当てると、7は3よりも小さくなく、7よりも小さくないので「大きい」が表示されます。

var a = 7;
if (a < 3) {
    System.out.println("小さい");
} else if (a < 7) {
    System.out.println("中くらい");          
} else {
    System.out.println("大きい");
}

変数aに3を割り当てると、3は3よりも小さくなく、7よりも小さいので「中くらい」が表示されます。

var a = 3;
if (a < 3) {
    System.out.println("小さい");
} else if (a < 7) {
    System.out.println("中くらい");          
} else {
    System.out.println("大きい");
}

switchでの条件分岐

switch

1. 変数aの値が5だった場合に「five」と表示するようにcase句を追加してみましょう
switch (a) {
    case 1, 2 -> System.out.println("one-two");
    case 3 -> System.out.println("three");
    case 4 -> System.out.println("four");
    case 5 -> System.out.println("five");
}

第8章 データ構造

Listで値をまとめる

List

1. LocalDate型であらわした2021年9月14日と2021年3月15日が格納されたListを用意してみましょう

(importが必要になるので注意してください)

jshell> import java.time.*

jshell> var dates = List.of(LocalDate.of(2021, 9, 14), LocalDate.of(2021, 3, 15))
dates ==> [2021-09-14, 2021-03-15]
2. 用意したListから2番目の要素を表示してみましょう(2021-03-15が表示されるはずです)
jshell> dates.get(1)
$3 ==> 2021-03-15

変更のできるList

1. authorsに「hosoya」を追加してみましょう
jshell> authors.add("hosoya")
$6 ==> true

jshell> authors
authors ==> [yamamoto, naoki, sugiyama, hosoya]
2. authorsの2番目の要素を「kishida」に戻してみましょう
jshell> authors
authors ==> [yamamoto, naoki, sugiyama, hosoya]

jshell> authors.set(1, "kishida")
$8 ==> "naoki"

jshell> authors
authors ==> [yamamoto, kishida, sugiyama, hosoya]
3. LocalDateを格納できるArrayListを用意してdatesという変数に割り当ててみましょう
jshell> var dates = new ArrayList<LocalDate>()
dates ==> []
4. 変数datesに割り当てたArrayListに2021年9月14日を追加してみましょう
jshell> dates.add(LocalDate.of(2021, 9, 14))
$11 ==> true

jshell> dates
dates ==> [2021-09-14]

配列

配列の要素の利用

1. 要素が5つのint型の配列を用意してみましょう
jshell> var nums = new int[5]
nums ==> int[5] { 0, 0, 0, 0, 0 }
2. 用意した配列の3番目の要素に2を入れてみましょう
jshell> nums[2] = 2
$14 ==> 2

jshell> nums
nums ==> int[5] { 0, 0, 2, 0, 0 }
3. [2, 3, 5, 7]が入ったint型の配列を用意してみましょう
jshell> var nums2 = new int[]{2, 3, 5, 7}
nums2 ==> int[4] { 2, 3, 5, 7 }
4. 用意した配列の4番目の要素を得てみましょう(7が入っているはずです)
jshell> nums2[3]
$17 ==> 7

レコードで違う種類の値を組み合わせる

レコードのオブジェクトを生成する

1. String型のenglish、String型のjapaneseをコンポーネントにもったレコードWordを定義しましょう。
jshell> record Word(String english, String japanese){}
|  次を作成しました: レコード Word
2. Wordレコードのオブジェクトをいくつか作ってみましょう。
jshell> var apple = new Word("apple", "りんご")
apple ==> Word[english=apple, japanese=りんご]

jshell> var grape = new Word("grape", "ぶどう")
grape ==> Word[english=grape, japanese=ぶどう]
3. LocalDate型のdate、int型のprice、String型のmemoをコンポーネントにもったレコードSpendingを定義しましょう。
jshell> record Spending(LocalDate date, int price, String memo) {}
|  次を作成しました: レコード Spending
4. Spendingレコードのオブジェクトをいくつか作ってみましょう。
jshell> var s1 = new Spending(LocalDate.of(2022, 4, 6), 200, "たまご")
s1 ==> Spending[date=2022-04-06, price=200, memo=たまご]

jshell> var s2 = new Spending(LocalDate.of(2022, 3, 19), 3278, "プロになるJava")
s2 ==> Spending[date=2022-03-19, price=3278, memo=プロになるJava]

Mapで辞書をつくる

Map

変更可能なMap

1. 「dog」に対応する値をgetメソッドで取ってみましょう。
jshell> animals.get("dog")
$25 ==> "いぬ"
2. 「horse」に対して「うま」をputメソッドで格納してみましょう。
jshell> animals.put("horse", "うま")
$26 ==> null

jshell> animals
animals ==> {horse=うま, dog=いぬ, fox=きつね, cat=猫}

HashMapは順番を気にしないため、順番はここで示したものと違うことがあります。もし追加した順を保ちたい場合にはLinkedHashMapを使います。

3. sizeメソッドで件数を確認してみましょう。
jshell> animals.size()
$28 ==> 4

第9章 繰り返し

ループ構文

for文の基本

1. 3回「Hello」と表示するプログラムをForHelloというクラス名で作ってみましょう。
package projava;

public class ForHello {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            System.out.println("Hello");
        }
    }
}
2. 今回のサンプルプログラムの変数iの名前をnに変えてみましょう。
public static void main(String[] args) {
    for (int n = 0; n < 5; n++) {
        System.out.println(n);
    }
}

こういった場合、変数iに入力カーソルを置いて[Shift] + [F6]を押すと、利用箇所を含めまとめて名前を変更することができます。

3. 今回のサンプルプログラムを0から9まで表示するようにしてみましょう。
for (int n = 0; n <= 9; n++) {
    System.out.println(n);
}

次のようにしても動作は同じですが、やりたいことに出てくる数字がそのままコードに出てくるほうが望ましいです。

for (int n = 0; n < 10; n++) {
    System.out.println(n);
}
4. 今回のサンプルプログラムを1から10まで表示するようにしてみましょう。
for (int n = 1; n <= 10; n++) {
    System.out.println(n);
}

次のようにしても動作は同じですが、やりたいことに出てくる数字がそのままコードに出てくるほうが望ましいです。

for (int n = 0; n < 10; n++) {
    System.out.println(n + 1);
}

for文の応用

1. 0から35まで5ずつ増やしながら表示してみましょう
for (int i = 0; i <= 35; i += 5) {
    System.out.println(i);
}
2. 20から0まで3ずつ減らしながら表示してみましょう
for (int i = 20; i >= 0; i -= 3) {
    System.out.println(i);
}

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

20
17
14
11
8
5
2

ループのcontinueとbreak

continue文
1. 0から9まで表示してください。ただし3は表示を飛ばしてください
for (int i = 0; i <= 9; i++) {
    if (i == 3) {
        continue;
    }
    System.out.println(i);
}
2. 0から9まで表示してください。ただし3と5は表示を飛ばしてください
for (int i = 0; i <= 9; i++) {
    if (i == 3 || i == 5) {
        continue;
    }
    System.out.println(i);
}

switchを使うと次のようになります。

for (int i = 0; i <= 9; i++) {
    switch(i) {
        case 3, 5: continue;
    }
    System.out.println(i);
}

->を使って書く場合には、continue文は中カッコで囲む必要があります。

for (int i = 0; i <= 9; i++) {
    switch(i) {
        case 3, 5 -> {
            continue;
        }
    }
    System.out.println(i);
}
3. 0から9まで表示してください。ただし3から6は表示を飛ばしてください
for (int i = 0; i <= 9; i++) {
    if (i >= 3 && i <= 6) {
        continue;
    }
    System.out.println(i);
}
break文

ここでの練習問題は、編集作業の不整合で意味のないものになってしまっています。本来の意図になるように書き換えています。

1. projava.BreakSampleという名前でクラスを作って次のループを動かしてみましょう。
for (int i = 0; i < 5; i++) {
    System.out.println(i);
    if (i < 3) {
        continue;
    }
    System.out.println("finish");
    break;
}
2. この例を動かすとどうなるか考えてみましょう。
3. 実際に動かしてみましょう。

ループに慣れる

デバッガでループを覗く

1. ほかのコードについてもデバッガ―で動作を確認してみましょう

解答略

2重ループ

1. この表は5x9までしか表示されていません。9x9が表示されるようにしてみましょう。
for (int i = 1; i <= 9; i++) {
    for (int j = 1; j <= 9; j++) {
        System.out.printf("%2d | ", i * j);
    }
    System.out.println();
}

次のように表示されます。

 1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 
 2 |  4 |  6 |  8 | 10 | 12 | 14 | 16 | 18 | 
 3 |  6 |  9 | 12 | 15 | 18 | 21 | 24 | 27 | 
 4 |  8 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 
 5 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 
 6 | 12 | 18 | 24 | 30 | 36 | 42 | 48 | 54 | 
 7 | 14 | 21 | 28 | 35 | 42 | 49 | 56 | 63 | 
 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 
 9 | 18 | 27 | 36 | 45 | 54 | 63 | 72 | 81 | 
内側のループ回数が変わる場合
1. 次のように表示されるようにしてみましょう
OOOOO
OOOO
OOO
OO
O
for (int i = 5; i >= 1; i--) {
    for (int j = 0; j < i; j++) {
        System.out.print("O");
    }
    System.out.println();
}

もう少しループの練習

丸を並べる
1. 上から4番目を赤くしてみましょう。

for (int x = 0; x < 12; x++) {
    for (int y = 0; y < 12; y++) {
        if (y == 3) {
            g.setColor(Color.RED);
        } else {
            g.setColor(Color.WHITE);
        }                
        g.fillOval(x * 30 + 50, y * 30 + 20, 25, 25);
    }
}
2. ななめに赤くしてみましょう。

for (int x = 0; x < 12; x++) {
    for (int y = 0; y < 12; y++) {
        if (x == y) {
            g.setColor(Color.RED);
        } else {
            g.setColor(Color.WHITE);
        }                
        g.fillOval(x * 30 + 50, y * 30 + 20, 25, 25);
    }
}

迷路ゲームをつくる

1. 右上がゴールになるようにしてみましょう

変数goalの初期値を次のようにします。

var goal = new Position(4, 1);
2. 左下がスタートになるようにしてみましょう

変数currentの初期値を次のようにします。

var current = new Position(1, 3);
3. もっと大きい迷路を定義してみましょう

4. waszが上左右下になっていますが、uhjnを上左右下になるようにしてみましょう。

switch式を次のように変更します。

var next = switch(ch) {
    case 'h' -> new Position(current.x()-1, current.y());
    case 'u' -> new Position(current.x()  , current.y()-1);
    case 'j' -> new Position(current.x()+1, current.y());
    case 'n' -> new Position(current.x()  , current.y()+1);
    default -> current;
};
5. ゴールの位置にGと表示するようにしてみましょう

変数goalと変数ijを比較するelse ifを挿入します。

} else if (map[i][j] == 1) {
    System.out.print("*");
} else if (i == goal.y() && j == goal.x()) {
    System.out.print("G");
} else {
    System.out.print(".");
}

次のようになります。

******
*.*.G*
*...**
*o*..*
******
6. 一歩進むごとに現在位置の表示を「o」と「O」で切り替えるようにしてみましょう。

これは少し難しいです。作戦には2通りあるのですが、まずは正攻法を紹介します。

現在の状態をあらわす変数を用意します。ここでは大文字かどうかをあらわすということでupperとします。初期値にはfalseを割り当てます。

var current = new Position(1, 3);
var upper = false;

表示のとき、uppertrueであればOfalseであればoを表示するようにします。ここでは条件演算子を使っています。

if (i == current.y() && j == current.x()) {
    System.out.print(upper ? "O" : "o");

そして、移動するときにupperを反転するようにします。

if (map[next.y()][next.x()] == 0) {
    if (!current.equals(next)) {
        upper = !upper;
    }
    current = next;
}

これで移動するごとにoOが切り替わります。

さてもうひとつの方法です。
今回は1マス動くごとに切り替わり、斜めには動けないので、マップ上でoOのどちらが表示されるかは固定で、次のような感じになります。

oOoOoOo
OoOoOoO
oOoOoOo
OoOoOoO
oOoOoOo

そうすると、横位置と縦位置の合計が偶数のときにo、奇数のときにOを表示すれば移動ごとに切り替わるようになります。

System.out.print((i + j) % 2 == 0 ? "o" : "O");

7. 現在地のまわり2マスだけ表示するようにしてみましょう。つまり5x5マスが表示されるようにします。

ループの範囲をcurrentの前後2つになるようにします。
そしてmapからはみ出したときの処理を加えます。なにも表示しないという手もありますが、なにかを表示するようにすると自分が必ず中心になるので、よりゲームらしさが出ます。ここでは#を表示します。

for (int i = current.y() - 2; i <= current.y() + 2; ++i) {
    for (int j = current.x() - 2; j <= current.x() + 2; ++j) {
        if (i < 0 || i >= map.length || j < 0 || j >= map[i].length) {
            System.out.print("#");
        }else if (i == current.y() && j == current.x()) {

mapのデータを次のように壁を厚くして、mapからはみ出す部分が表示されることがないようにすると、「mapからはみ出したときの処理」を省けます。処理スピードが求められる場合に使われるテクニックです。

int[][] map = {
    {1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 0, 1, 0, 0, 1, 1},
    {1, 1, 0, 0, 0, 1, 1, 1},
    {1, 1, 0, 1, 0, 0, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1}
};

このように、処理がはみださないように置かれるデータを番兵と呼ぶことがあります。番兵を置くことで処理を効率化します。

8. なにも入力せずに「Enter]キーを押したり、2文字入力して[Enter]キーを押したりすると、[z]キーなどを押しても移動しなくなります。どのような操作をすれば移動が行えるようになるか考えてみましょう。

[Enter]キーを押します。

次のように、文字を取得したあと[Enter]キーを表す\nを受け取った場合に処理を飛ばすようにすると、文字を入力しても動かないということが起きなくなります。

int ch = System.in.read();
if (ch == '\n') continue;

第10章 データ構造の処理

データ構造を拡張for文で扱う

基本for文でのListの要素の処理

1. 次のように用意されたListのすべての要素を表示するプログラムを基本for文を使って書いてみましょう。
var names = List.of("yusuke", "kis", "sugiyama");
package projava;

import java.util.List;

public class ExListForBasic {
    public static void main(String[] args) {
        var names = List.of("yusuke", "kis", "sugiyama");
        
        for (int i = 0; i < names.size(); i++) {
            System.out.println(names.get(i));
        }
    }
}

拡張for文でのListの要素の処理

1. 次のように用意されたListのすべての要素を拡張for文を使って表示するプログラムを書いてみましょう。
var names = List.of("yusuke", "kis", "sugiyama");
package projava;

import java.util.List;

public class ExListForExtended {
    public static void main(String[] args) {
        var names = List.of("yusuke", "kis", "sugiyama");
        
        for (String name : names) {
            System.out.println(name);
        }
    }
}

拡張for文での配列の要素の処理

1. 次の配列のすべての要素を表示するプログラムを拡張for文を使って書いてみましょう。
var names = new String[]{"yusuke", "kis", "sugiyama"};
package projava;

public class ExArrayForExtended {
    public static void main(String[] args) {
        var names = new String[]{"yusuke", "kis", "sugiyama"};
        
        for (String name : names) {
            System.out.println(name);
        }
    }
}

値の集合の処理のパターン

共通するパターン
1. List.of("apple", "banana", "grape")について、次の処理を考えてみましょう。
  • 5文字ちょうどの文字列を表示する
var data = List.of("apple", "banana", "grape");
for (var fruit : data) {
    if (fruit.length() == 5) {
        System.out.println(fruit);
    }
}
  • 5文字ちょうどの文字列を取り出した新たなListを作る
var data = List.of("apple", "banana", "grape");
var result = new ArrayList<String>();
for (var fruit : data) {
    if (fruit.length() == 5) {
        result.add(fruit);
    }
}
System.out.println(result);
  • 5文字ちょうどの文字列の個数を数える
var data = List.of("apple", "banana", "grape");
var result = 0;
for (var fruit : data) {
    if (fruit.length() == 5) {
        result++;
    }
}
System.out.println(result);
  • 5文字ちょうどの文字列のすべてが「p」を含むか確認する

まず「共通するパターン」に沿って書くと次のようになります。

var data = List.of("apple", "banana", "grape");
var result = true;
for (var fruit : data) {
    if (!fruit.contains("p")) {
        result &= false;
    }
}
System.out.println(result);

「初期値」がtrue、「結果を加える処理」が&=になっています。 初期値には、計算の結果に影響がない値を設定することになります。足し算の場合は、0を足しても計算の結果に影響を与えません。掛け算の場合は1を掛けても計算の結果に影響を与えません。Listの場合は空のリストを追加しても影響がない、という考え方です。
このように、計算の結果に影響を与えない値を、その計算の単位元 といいます。 この場合、&演算での単位元trueになります。

「加える処理」なので&=にしましたが、この場合は=で構いません。 また、一度pが含まれない文字列が来ると次以降のデータを確認しなくても「すべてがpを含む」が満たされないことになるので、次以降のループの処理は不要になります。

for (var fruit : data) {
    if (!fruit.contains("p")) {
        result = false;
        break;
    }
}
  • 5文字ちょうどの文字列のどれがひとつでも「p」を含むか確認する
var data = List.of("apple", "banana", "grape");
var result = false;
for (var fruit : data) {
    if (fruit.contains("p")) {
        result |= true;
    }
}
System.out.println(result);

「初期値」がfalseで、「結果を加える処理」が|=になっています。
|演算の単位元falseになります。

また、一度pが含まれる文字列が来ると次以降のデータを確認しなくても「どれがひとつでもpを含む」が満たされることになるので、次以降のループの処理は不要になります。

for (var fruit : data) {
    if (fruit.contains("p")) {
        result = true;
        break;
    }
}

Stream

全体に対する処理
1. var strs = List.of("apple", "banana", "orange", "pineapple");

があるとき、次の処理をStreamを使って書いてみましょう。 ・6文字以上のものを大文字にして表示 ・6文字以上のものの文字数の合計を表示 ・すべての文字列がaを含んでるかどうか判定 ・cを含むものがひとつでもあるかどうか判定

基本型でのStream

StreamとIntStreamの行き来

1. StringクラスのrepeatメソッドはJava 11で導入されたためJava 8では使えません。IntStreamを利用して"test"を3回連結して"testtesttest"を出力する処理を実装してみましょう。
package projava;

import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ExRepeat {
    public static void main(String[] args) {
        var result = IntStream.range(0, 3)
                .mapToObj(n -> "test")
                .collect(Collectors.joining());
        System.out.println(result);
    }

}

第11章 メソッド

メソッドの宣言

JShellでのメソッド宣言

基本的なメソッド
1. 「Hi!」と表示するhiメソッドを宣言してみましょう
jshell> void hi() { System.out.println("Hi!");}
|  次を作成しました: メソッド hi()
2. 宣言したhiメソッドを呼び出してみましょう。
jshell> hi()
Hi!
引数のあるメソッド
1. greetingメソッドとまったく同じく、"Hello "に続いて受け取った引数を表示するメソッドを`void salutation(String person)に続けて宣言してみましょう。

salutationもgreetingと同じく挨拶という意味です。

jshell> void salutation(String person) { System.out.println("Hello " + person);}
|  次を作成しました: メソッド salutation(String)

jshell> salutation("kis")
Hello kis
2. 引数として数値を受け取って、その回数だけ「Hello」と表示するメソッドを宣言してみましょう。hellohello(1)として呼び出すと「Hello」、hellohello(2)として呼び出すと「hellohello」が表示されます。
jshell> void hellohello(int count) { System.out.println("hello".repeat(count));}
|  次を作成しました: メソッド hellohello(int)

jshell> hellohello(1)
hello

jshell> hellohello(2)
hellohello
3. hellohello(3)として呼び出して動きを確認してみましょう。
jshell> hellohello(3)
hellohellohello
戻り値のあるメソッド
1. 与えられた数字を2倍するメソッドを int dbl(int n)から始めて宣言してみましょう。(doubleは「予約語」となっていてメソッド名に使えません)
jshell> int dbl(int n) { return n * 2;}
|  次を作成しました: メソッド dbl(int)
2. 宣言したメソッドdblを呼び出してみましょう。
jshell> dbl(3)
$10 ==> 6

jshell> dbl(5)
$11 ==> 10
3. 与えられた数字を3倍するメソッドtripleを宣言して呼び出してみましょう。
jshell> int triple(int n) { return n * 3;}
|  次を作成しました: メソッド triple(int)

jshell> triple(3)
$13 ==> 9

jshell> triple(5)
$14 ==> 15
4. 与えられた文字列を2回繰り返すメソッドを宣言して呼び出してみましょう。
jshell> String twice(String s) { return s.repeat(2);}
|  次を作成しました: メソッド twice(String)

jshell> twice("Hello")
$16 ==> "HelloHello"
5. 与えられた2つの整数のうち大きいほうを返すメソッドmax2を宣言してみましょう。条件演算子を使います。
jshell> int max2(int n, int m) { return n > m ? n : m;}
|  次を作成しました: メソッド max2(int,int)

jshell> max2(5, 3)
$18 ==> 5

jshell> max2(1, 4)
$19 ==> 4
6. 与えられた3つの整数のうち一番大きい数値を返すメソッドmax3を宣言してみましょう。
jshell> int max3(int n, int m, int l) { return max2(n, max2(m, l));}
|  次を作成しました: メソッド max3(int,int,int)

jshell> max3(1, 4, 5)
$21 ==> 5

jshell> max3(5, 1, 4)
$22 ==> 5

jshell> max3(4, 5, 1)
$23 ==> 5

インスタンスメソッドの宣言

インスタンスメソッドを宣言する
1. 「(名前)さんの平均点は(平均)点です」と表示するshowResultメソッドをStudentレコードに用意してみましょう。
record Student(String name, int englishScore, int mathScore){
    int average() {
        return (this.englishScore() + this.mathScore()) / 2;
    }
    void showResult() {
        System.out.println("%sさんの平均点は%d点です".formatted(name(), average()));
    }
}

ラムダ式とメソッド参照

ラムダ式

1. 次のメソッドをラムダ式で表してみましょう。
boolean check(String s) {
  return s.contains("y");
}
s -> s.contains("y")
2. 次のメソッドをラムダ式で表してみましょう。
void print(String s) {
  System.out.println(s);
}
s -> System.out.println(s);
3. 次のラムダ式upperという名前のメソッドにしてみましょう。引数と戻り値の型はどちらもStringです。
s -> s.toUpperCase()
String upper(String s) {
    return s.toUpperCase();
}
4. 次のラムダ式をemptyという名前のメソッドにしてみましょう。引数の型はString、戻り値の型はbooleanです。
s -> s.isEmpty()
boolean empty(String s) {
    return s.isEmpty();
}

メソッド参照

1. 次のコードをメソッド参照を使って書き換えてみましょう
IntStream.of(nums).mapToObj(n -> "*".repeat(n)).toList()
IntStream.of(nums).mapToObj("*"::repeat).toList()

IntelliJ IDEAでラムダ式とメソッド参照の変換

1. 次のラムダ式をメソッド参照を使って書き換えましょう
names.stream().map(s -> s.toUpperCase()).toList()
names.stream().map(String::toUpperCase).toList()
2. 次のラムダ式をメソッド参照を使って書き換えましょう
names.stream().map(s -> "%sさん".formatted(s)).toList()
names.stream().map("%sさん"::formatted).toList()
3. メソッド参照をラムダ式を使って書き換えましょう
names.stream().map(String::toLowerCase).toList()
names.stream().map(s -> s.toLowerCase()).toList()

メソッド使いこなし

メソッド呼び出しの組み合わせ

変数を使ってメソッド呼び出しの組み合わせを分解する
1. "three times".repeat("abc".length())を変数を使って分解してみましょう。
var length = "abc".length();
"three times".repeat(length);

再帰とスタック

再帰によるループ
1. 次のforループでの処理を再帰に書き換えてみましょう。
for (int i = 3; i > 0; i--) {
    System.out.println(i);
}

次のようになります。

public class ExRecLoop {
    public static void main(String[] args) {
        loop(3);
    }
    
    static void loop(int i) {
        if (i <= 0) {
            return;
        }
        System.out.println(i);
        loop(i - 1);
    }
}

「プロになるJava」 第2部「Javaの基本」の練習問題解答

「プロになるJava」の第2部「Javaの基本」の練習問題の解答です。

第3章 値と計算

3.2 値と演算

3.2.1 整数

1. 7+2を計算してみましょう
jshell> 7 + 2
$1 ==> 9
2. 7-2を計算してみましょう
jshell> 7 - 2
$2 ==> 5
3. 7×2を計算してみましょう
jshell> 7 * 2
$3 ==> 14
4. 7÷2を計算して整数部分の結果を出してみましょう
jshell> 7 / 2
$4 ==> 3
5. 7を2で割った余りを計算してみましょう
jshell> 7 % 2
$5 ==> 1

3.2.3 実数

  1. 7÷2を小数点以下の結果も出るように計算してみましょう
jshell> 7. / 2
$7 ==> 3.5

3.3 メソッドの呼び出し

3.3.2 文字列の掛け算や引き算?

掛け算の代わりにrepeatメソッド
1. repeatメソッドを使って"test"を4回繰り返してみましょう
jshell> "test".repeat(4)
$8 ==> "testtesttesttest"
引き算の代わりにreplaceメソッド
1. replaceメソッドを使って"test"から"t"を取り除いて"es"が残るようにしてみましょう。
jshell> "test".replace("t", "")
$9 ==> "es"
2. replaceメソッドを使って"test""es""alen"に置き換えて"talent"にしてみましょう。
jshell> "test".replace("es", "alen")
$10 ==> "talent"

3.3.4 メソッドの使い方がわからないとき

1. 文字列の長さを返すメソッドがあります。Javadocから探して試しに呼び出してみましょう。
jshell> "test".length()
$11 ==> 4
2. 文字列の一部を返すメソッドがあります。Javadocから探して「"test"」の2文字目以降を取り出して"est"を表示されるようにしてみましょう。
jshell> "test".substring(1)
$12 ==> "est"

第4章 変数と型

4.1 変数

1. varをつけて変数sを使えるようにしてみましょう。
jshell> var s = "site"
s ==> "site"

4.2 型

4.2.3 変数の型を指定する

1. 「var c = 5」をvarを使わずintを使って書いてみましょう。
jshell> int c = 5
c ==> 5
2. 「var u = "UFO"」をvarを使わずStringを使って書いてみましょう。
jshell> String u = "UFO"
u ==> "UFO"
3. 「var w = "watch"」をvarを使わずintStringのどちらかを使って書いてみましょう。
jshell> String w = "watch"
w ==> "watch"
4. 「var d = 12」をvarを使わずintStringのどちらかを使って書いてみましょう。
jshell> int d = 12
d ==> 12

第5章 標準API

5.1 日付時刻

5.1.3 パッケージとimport

1. パッケージ名を省略して現在の日付を表示させてみましょう。
jshell> LocalDate.now()
$2 ==> 2022-03-27
2. パッケージ名を省略して現在の時刻を表示させてみましょう。
jshell> LocalTime.now()
$3 ==> 21:00:36.528563700

5.1.4 日付時刻の操作

1. LocalDateクラスを使って明日の日付を求めてみましょう
jshell> LocalDate.now().plusDays(1)
$7 ==> 2022-03-28
2. LocalDateクラスを使って2週間後の日付を求めてみましょう
jshell> LocalDate.now().plusWeeks(2)
$8 ==> 2022-04-10

5.1.6 日付時刻の整形

1. java17date変数に用意したJava17のリリース日を2021年09月14日という形式で表示してみましょう。
jshell> "%tY年%<tm月%<td日".formatted(java17date)
$11 ==> "2021年09月14日"
2. java17dateTime変数に用意したJava17のリリース日時を2021年09月14日 14時30分という形式で表示してみましょう。
jshell> "%tY年%<tm月%<td日 %<tH時%<tM分".formatted(java17dateTime)
$13 ==> "2021年09月14日 14時30分"

5.1.7 staticメソッドとインスタンスメソッド

1. LocalDateクラスを使って2020年2月28日の次の日を求めてみましょう
jshell> LocalDate.of(2020,2,28).plusDays(1)
$14 ==> 2020-02-29
2. LocalDateクラスを使って2020年2月28日の2週間後の日付を求めてみましょう
jshell> LocalDate.of(2020,2,28).plusWeeks(2)
$15 ==> 2020-03-13
文字列整形のformattedメソッドとformatメソッド
1. "%tm月".formatted(today)String.formatを使って書き換えてみましょう。
jshell> String.format("%tm月", today)
$17 ==> "03月"
2. "%sは%d".formatted("two", 2)String.formatを使って書き換えてみましょう。
jshell> String.format("%sは%d", "two", 2)
$18 ==> "twoは2"
3. String.format("%tY年", today)formattedメソッドを使って書き換えてみましょう。
jshell> "%tY年".formatted(today)
$19 ==> "2022年"

5.2 BigDecimal

5.2.2 BigDecimalでの計算

1. BigDecimalクラスを使って119999×0.1を誤差なく計算してみましょう
jshell> BigDecimal.valueOf(119999).multiply(BigDecimal.valueOf(0.1))
$21 ==> 11999.9

5.2.3 newによるBigDecimalオブジェクトの生成

オブジェクト
1. 1.4142135623730950488×1.4142135623730950488を計算してみましょう。同じ数同士を掛けています。
jshell> var root2 = new BigDecimal("1.4142135623730950488")
root2 ==> 1.4142135623730950488

jshell> root2.multiply(root2)
$24 ==> 1.99999999999999999999522356663907438144

第6章 SwingでのGUI

6.1 Swingでのウィンドウ表示

6.1.2 ウィンドウを表示してみる

1. setLocationメソッドを使ってウィンドウを右に動かしてみましょう。
jshell> f.setLocation(300,200)

※ getLocationメソッドで得たx座標より大きい値を最初の引数に指定してください。

6.1.3 入力領域の配置

1. 入力領域に「Hello Swing」を表示してみましょう。
jshell> t.setText("Hello Swing")

6.1.4 ふたつめの入力領域

1. 上側の入力領域に入力された文字列を下側の入力領域に表示してみましょう
jshell> t2.setText(t.getText())

2. 上側の入力領域に入力された文字列の大文字を小文字に変換して下側の入力領域に表示してみましょう
jshell> t2.setText(t.getText().toLowerCase())

6.2 画面に絵を描いてみる

6.2.3 図形の描画

1. 左下から右上に向かって直線を描いてみましょう
jshell> g.drawLine(0, 400, 600, 0)

jshell> label.repaint()
2. g.setColor(Color.BLUE)として色が指定できるようにjava.awt.Colorクラスのimportを行ってみましょう
jshell> import java.awt.Color

3. 青く塗りつぶされた円を描いてみましょう
jshell> g.setColor(Color.BLUE)

jshell> g.fillOval(50, 200, 150, 150)

jshell> label.repaint()

6.3 Javaの基本文法

6.3.1 Javaの文法

名前の付け方のガイドライン
1. 「my bag」をクラス名にするとしたらどうなるか考えてみましょう。

MyBag

2. 「my bag」を変数名にするとしたらどうなるか考えてみましょう。

myBag

3. 「get bag」をメソッド名にするとしたらどうなるか考えてみましょう。

getBag

4. 「画面に絵を描いてみる」でやったことをプログラムにしてみましょう
package projava;

import javax.swing.*;
import java.awt.image.BufferedImage;
import java.awt.Color;

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

        var f = new JFrame("drawing");
        f.setVisible(true);

        var label = new JLabel("test");
        f.add(label);

        var image = new BufferedImage(600, 400, BufferedImage.TYPE_INT_RGB);
        label.setIcon(new ImageIcon(image));
        f.pack();

        var g = image.createGraphics();
        g.drawLine(0, 0, 600, 400);
        g.setColor(java.awt.Color.RED);
        g.fillRect(300, 200, 150, 100);
        g.drawLine(0, 400, 600, 0);

        g.setColor(Color.BLUE);
        g.fillOval(50, 200, 150, 150);
        label.repaint(); 
    }
}

型理論を理解するためのロードマップ

基本的には少しずつオーバーラップしているはず。
「論理と計算のしくみ」はぜひ読んでほしいです。この本に論理の説明もあるので、先にこれを買って、わからなければさかのぼる感じでいいかも。

論理学に入門する

論理学をもすこし勉強する

計算とつなげる

プログラミング言語を学ぶ

壁にたちむかう
これは、「プログラミング言語の基礎概念」で ものたりないって思ったときだけ手を出すくらいでいいです。

教えるということ - 漂流開発者の日記(WEB+DB PRESS VOL.20, 2004-3-26)

2004年のWEB+DB PRESS VOL.20に掲載された記事です。絵もかいてます。

開発してない

開発者の日記といいながら、最近とんと開発してないことに気付きました。
じゃあ何してるかというと、絵を描いたり、プログラムを教えたりしてます。
教えるのも楽しいんですけど、開発してないと、実際のプログラムの話がしにくくなるんですよね。
教えるときに絵がうまく描けると評判がいいですけど。

あの人が先生?

教えるときにスーツを着てないこともあって、「え、あの人が先生?」と言われることがよくあります。髪の色がすごいことなってることも多いですからね。
最初は話の調子も悪いですし。
もちろん、いきなり言われるんじゃなくて、全部の過程が終わったあとの飲み会とかで「実は最初はどうなるかと思った」という話を聞くわけですね。まぁ、最終的にそういう話ができるようになっているので、ヨシとしましょう。
それより、苦手なのは「先生」と呼ばれることです。いまでこそ慣れましたけど、最初は講義中にそう呼ばれるのもなんだかくすぐったいものがありました。
すでにかなり前に教える期間が終わった人たちから「先生」と呼ばれるのは、いまだに慣れませんね。年上の人が多いのに。「先生」で馴染んでるのでしかたないと思いますけど。
飲み屋の店員さんに「あの人が先生?」と思われてそうで、それが一番こっぱずかしいです。

はじめてのプログラム

ぼくが教えてる人たちは、プログラムの勉強が初めてのことが多いです。
で、そんな人たちにJavaを教えるのですが、最初はコマンドプロンプトからjavacコマンドでコンパイルしてjavaコマンドで実行、ということをやってもらってたわけです。エディタでプログラムを入力してもらって。
ところがあるとき受講生の人数が多くて、コマンドプロンプトじゃやってられなくなってForteというツールを使うことにしました。今はSun Java Studioっていう名前になってて、NetBeansというオープンソース版も出てるツールです。
最初だけはやっぱりコマンドプロンプトHello worldプログラムをやってもらって、それからForteを使って、ウィンドウ上のボタンを押したらメッセージを出すプログラムを作ってもらうようにしました。
そしたら受講生のひとりが、ウィンドウ出すプログラムが動いたときに「あ、プログラムが作れた気がする」って言うんですよ。「えっ」と思いましたね。だってHello worldの時点で一応プログラム作ってるわけですから。

やっぱりWindowsからパソコンをさわった人にとっては、プログラムっていうのはウィンドウが出てナンボということを実感しました。
それ以来、サンプルプログラムは動きを見て何をしてるものかがわかるようにしています。
なんにしても、そうやってプログラム初体験だった人たちが、ソフトウェア関係の仕事に就いたという話を聞くと、うれしくなります。仕事じゃないにしても、プログラムを作ってみた、という話を聞くのもうれしいですね。
もっとプログラムの楽しさを伝えれるようになりたいものです。

プログラミングの最初の壁は逐次実行 #projava

プログラミングの入門書で、変数だとかfor文なんかは丁寧に例えなどを使って説明されていることが多いのですけど、逐次実行はほとんど説明されていることがありません。
入門書を書く人にとって、逐次実行は自明であって説明が必要なものではないという認識があると思います。

プログラムに慣れた人にとって、プログラムが上から順に実行されるというのは当たり前で学習が必要なことには思えないと思います。
「見たままやん」
となるのではないかと。

けど、実際には上から順に動くというのがよくわからないようです。
「あ、プログラムって上から順番に実行されるんですね、わかってなかった」
と言われたことがあります。そういうふうに言ってくれる人がいるということは、言わないけどわかってなかったという人が何倍もいるはずです。

通常の文章というのは、基本的には一定の状態を仮定して書かれていて、前部と後部で表す状態が違うということはありません。文章の順番を入れ替えても、表したい内容には違いがありません。物語は状態が変わっていくように見えますが、実は冒頭から、結末の状態を書き記しているだけだったりします。読み込みの遅さを利用して結末の状態が徐々に明らかになることで物語が進行します。
数学で複数の式が提示されても、それはすべての式が同時に常になりたつ関係式で、式を評価するごとに関係がかわるということはないです。
つまり、文章にしろ数学の式にしろ、一行ずつ処理が進んで状態が変わる、ということに慣れていないわけです。

考えてみると、上から順に動くという場合にはどの行を実行しているのかというステートの管理が必要です。
プログラミング初心者の場合、そういった自明に思えるステートというのが頭のなかに構築できていません。

そして、重要なステートのひとつには変数があります。
変数の説明のために箱だと例えてみたり、いや箱ではなく場所だと言ってみたり、変数の特性についてあの手この手で説明がされてきました。でも結局のところ、変数がわからないということは、逐次実行がわかっていないことが原因の可能性があります。
少なくとも、逐次実行がわかっていないと、変数の内容が変化していくことがわかりません。

int a;
a = 3;
print(a);
a = 5;
print(a);

のようなコードがわからないというとき、変数のふるまいがわかっていないように見えますが、実際はコードが順に実行されていくということがわかっていない可能性があります。だとすると、変数の特性をいくら説明しても無駄ということになります。

また、逐次実行がわかっていないのであれば、その一部が繰り返されるループも当然わからないということになります。
逐次実行を丁寧に習得させれば、変数やループではつまづきにくくなるかもしれません。

そのため、「プロになるJava」では、まず最初にJShellを使って自分の手でコードを一行ずつ動かすことで、プログラムが一行ずつ実行されて状態が変化していくことを体感してもらうようにしています。
JShellでウィンドウを出すコードを動かしてもらったあとでそのコードをまとめたプログラムを作ってもらって、ソースコードに書いたものは一行ずつ実行されるんだということが実感できるようにしています。

またforループではデバッガを使って処理の動きと変数の値の遷移を追うようにしています。デバッガで処理や変数の動きを追うことも、プログラムや変数の理解には大切なので、プログラミングの学習にIDEが必要だと思っています。

残念ながら「創るJava」と違って、「プロになるJava」の元ネタを使って実際に人に教えてないので、実際のところはこれからほんとの初心者の人が読んだり研修に使われたりすることでわかってくるとは思うのですが、少なくとも今までの、ひとまとめのソースコードを提示して一気に動かして一行ずつ解説するというアプローチよりはうまくいくのではないかなと思っています。

「プロになるJava」はこんな人におすすめ #projava

「プログラミング未経験ですがJavaをやることになりました。いい本ないですか?」
→ JShellを使って動作を1ステップずつ確認しながら進めれる唯一のJava入門書「プロになるJava」をおすすめ

「プログラムの入門書なのに文字ばっかり出してプログラムの作り方の説明がありませんでした。いい本はありませんか?」
→ ウィンドウだしたり絵を描いたりしてプログラム感のあるサンプルもでてきて最後はWebアプリケーションまでつくる「プロになるJava」をおすすめ

「なんかJavaの本って1冊だけじゃ終わらなくて実践編とかサーブレットの本とか必要だから高くないですか?」
→ 一冊でJavaの基本からクラスを使ったプログラム、テストやビルドツール、Spring BootでのWebアプリ、データベースまで解説した「プロになるJava」をおすすめ

「ナントカなJavaという本を終わらせたので次に勉強する本ないですか?」
→きっとその本に書いてない必須知識がたくさんまとめてある「プロになるJava」をおすすめ

「一応Javaでプログラム組めるようになったんだけど、ちょっと複雑な処理になるとどう書いていいかわからないのでいい本ないですか?」
→複雑な処理の書き方を、なぜそれが複雑で難しいのか段階を追って説明している「プロになるJava」をおすすめ

Javaオブジェクト指向がうまくできないんですけど、いい本ないですか?」
オブジェクト指向なんかやらなくていいって教えてくれる「プロになるJava」をおすすめ

「でもクラスとか継承とか使うのにオブジェクト指向が必要なんでしょ?いい本ないですか?」
→継承の使い方をオブジェクト指向という言葉を使わずに解説している「プロになるJava」をおすすめ

hogeという言語を使っててJavaには興味ないんだけど、もうすこしプログラミング能力を高めたいと思ってます。いい本ないですか?」
→ 複雑な処理の書き方の解説もHTTPやHTTPSプロトコルの解説もある「プロになるJava」のサンプルをhoge言語でやっていくのをおすすめ。

「なんかヒマなんだけど時間つぶしにいい本ないですか?」 → このブログ程度の読みやすい文章で進めれる「プロになるJava」をおすすめ。ついでにJavaがわかっちゃう

このブログにたどりつきましたね?「プロになるJava」をおすすめ

オブジェクト指向は差分プログラミングとデータ分類をまとめて扱おうとしたのが弱点

オブジェクト指向の最大の特徴は、モジュールと型を一体に扱ったことです。
メイヤーの本では次のような「オブジェクト指向の基準」があげられています。

  • クラスが唯一のモジュールでなければならない
  • すべての型はクラスに基づいていなければならない

つまり、クラスはモジュールであり型であるということです。
ここで、モジュールにとって必要な、クラスで実現できる機能は、モジュール間で異なる部分だけをそのモジュールで実装するという差分プログラミングです。
型に求められるのは、データの分類です。
ということは、オブジェクト指向は差分プログラミングとデータの分類を同時に扱おうとしていたということになります。

けれども、データの分類と差分プログラミングを同時に行うのは大変です。
「できらぁ!データの分類と同時に差分プログラミングして、いいソフトウェアができるっていったんだよ!!」
というのがオブジェクト指向だったのですが、いざこうやって改めて「データの分類と同時に差分プログラミングを行ってうまいソフトウェアをつくってもらおう」って言われると
「え!!データ分類と同時に差分プログラミングを!?」
ってなるわけです。

現在のプログラミング言語の型は、オブジェクト指向が流行った時代よりもかなり発展しています。型によるデータの分類は、オブジェクト指向を離れて発展した型理論をもとに考えるほうが、整理され新しい考え方にもついていきやすいです。

差分プログラミングは、クラスと継承をもとに考えてもコーディング作業として煩雑で、またムダな制約も持ち込んでしまいます。 いまや差分プログラミングはラムダ式など関数ベースで行うほうが主流でしょう。

ということで「継承を使って差分プログラミングをすると便利だよー、でもあれれ~ラムダ式でもっと簡単にできちゃったね」という説明したり継承による型の分類を紹介したりする本が3/19に出ますね。

FAQ
Q 電子版はでますか?
A 紙版のあとに出る予定

Q 目次等は?
A もうしばらくお待ちを

あと、メイヤーの本はこちら。