「プロになるJava」を読みながら試した記録
- 本に書いてある通りにIntelliJ IDEAを日本語化したらすごくわかりやすくなりました。
- プロジェクトの作成時にJDKを選択するのですが、SDKMAN!を使ってインストールしたJDKも自動検出して選択肢に入れてくれるのがすごい。こんなの作れって言われても一生作れない気がする。
- JShellは便利ですね。
jshell> "オラ"
$13 ==> "オラ"
jshell> "オラ".rep
repeat( replace( replaceAll( replaceFirst(
jshell> "オラ".repeat(100)
$14 ==> "オラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオララオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラオラ"
第6章 SwingによるGUI
JShellからGUIを作るという発想はなかったです。実際動かしてみると過程がわかって面白い。
謎の何かを起動
以下の1行を実行するだけで、謎の何かが起動してちょっと感動がありました。
jshell> var f = new JFrame("ふれーむだよ")
f ==> javax.swing.JFrame[frame0,0,25,0x0,invalid,hidden ... tPaneCheckingEnabled=true]
ウィンドウを表示
setVisibleしたらウィンドウが見えました。信号機みたい。
jshell> f.setVisible(true)
テキストフィールドを追加
jshell> f.add("てきすとふぃーるどだよ",t)
| 例外java.lang.IllegalArgumentException: cannot add to layout: unknown constraint: てきすとふぃーるどだよ
| at BorderLayout.addLayoutComponent (BorderLayout.java:468)
| at BorderLayout.addLayoutComponent (BorderLayout.java:429)
| at JRootPane$1.addLayoutComponent (JRootPane.java:495)
| at Container.addImpl (Container.java:1156)
| at Container.add (Container.java:1033)
| at JFrame.addImpl (JFrame.java:554)
| at Container.add (Container.java:464)
| at (#12:1)
なんか怒られてしまった。
シグネチャを確認しようとJavaDocを見たけれど、addメソッドが見つからない。どうもjava.awt.Containerのメソッドみたい。たぶん以下のメソッドなので、シグネチャは合っているような気がするんだけれど…。インストールしているのがJDK18なのでそのせい??
JDKを17に切り替えたけれど同じ。
(解決) 本を読み進めたところ、Stringの箇所は「North」など決まった文字列しか受け付けてくれないらしいとわかった。昔作られたものだからしょーがないのかもしれないけれど、つらい作りだ…。
適当にいじっていたところ、単にadd(JTextFieldなどのインスタンス)とするだけでも良いことがわかった。setSize()やsetLoacation()は後でやれば良くて。こっちの方が性に合っている。それにしてもJShellで1個ずつ確かめながらGUIを作るのは面白みがある。
少々考えてしまった箇所
addActionListenerの引数のところでラムダ式が登場してちょっと考えてしまった。なんでここでラムダ式が登場するのかなあと。シグネチャを見ると以下のようになっている。
void AbstractButton.addActionListener(java.awt.event.ActionListener l)
このActionListenerのJavaDocを見たらなんとなく理解できた。メソッドが1つしかなく、そのメソッドの引数がActionEvent。本で説明されている通り。頭文字を取って「ae」という変数名にしている。
Swingチュートリアルではラムダ式を使っておらず、ラムダ式を使わないとこんだけ面倒くさいんだなというのがわかる。
第6章の最後に書いてあった、javaコマンドにソースファイル名を渡して起動する方法、知らなかった。必ずコンパイルしてからじゃないと実行できないものだと思っていた。
第7章にて、新しいswitch文とswitch式を試して理解した。頭では理解していたけれど、実際に手を動かすと「あれ、caseは書かないといけないんだっけ?」など、わかっていないところがあるのが明らかになった。
Recordを初めて書いてみた。宣言するときの波括弧はお作法として必要なのか?中に何か書けるのか?
あと、Record内のコンポーネントを参照するときにmyRecord.hoge
じゃなくてmyRecord.hoge()
というふうにカッコが必要になるのはメソッドみたいに見えてなんだかなあ〜と思ったりしたけれど、しょうがないのかな。
ドキュメント読んで調べてみようかな。
record調べてみた。おまじないのような波括弧の中には自分でメソッドを書いたりとか、クラスの中に書くものを書けるようだ。recordクラスという特殊クラス、というだけだそうなので、なるほど納得。
コンポーネントを参照するときがメソッド呼び出しみたいだわというのも、実際メソッド(getter)呼び出しだとわかったので納得。
コンストラクタとgetterしかないイミュータブルなクラスと理解した。
迷路ゲームを自分なりに書き直した。リテラルが点在しているのがいやだけど妥協。
ソースコード
package maze;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Stream;
public class Main {
record XY(int x, int y) {
}
public static void main(String[] args) throws IOException {
final Integer[][] mazedata = {
{1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1}
};
var player = new XY(1, 1);
// (起動時のみ)描画
drawing(mazedata, player);
boolean isGoaled = false;
while (!isGoaled) {
// 入力
XY nextPosition = input(player);
// 衝突?
player = collision(mazedata, player, nextPosition);
// 描画
drawing(mazedata, player);
// GOAL?
isGoaled = isGoal(player);
}
System.out.println("GOAL!!");
}
private static void drawing(Integer[][] mazedata, XY player) {
// merge player data
var viewdata = arrayDeepCopy(mazedata);
viewdata[player.y][player.x] = 2;
// drawing
Stream.of(viewdata).forEach(Main::drawingLine);
}
private static Integer[][] arrayDeepCopy(Integer[][] multipleArrays) {
Integer[][] newarray = new Integer[multipleArrays.length][];
for (var i = 0; i < multipleArrays.length; i++) {
newarray[i] = Arrays.copyOf(multipleArrays[i], multipleArrays.length);
}
return newarray;
}
private static void drawingLine(Integer[] line) {
var sprite = Map.of(0, " ", 1, "🌫", 2, "🐶");
Stream.of(line).forEach(x -> System.out.print(sprite.get(x)));
System.out.println();
}
private static XY input(XY player) throws IOException {
var in = System.in.read();
return switch (in) {
case 'j' -> new XY(player.x, player.y + 1);
case 'k' -> new XY(player.x, player.y - 1);
case 'h' -> new XY(player.x - 1, player.y);
case 'l' -> new XY(player.x + 1, player.y);
default -> new XY(player.x, player.y);
};
}
private static XY collision(Integer[][] mazedata, XY player, XY nextPosition) {
if (mazedata[nextPosition.y][nextPosition.x] == 1) {
return player;
} else {
return nextPosition;
}
}
private static boolean isGoal(XY player) {
var goal = new XY(5, 5);
return player.x == goal.x && player.y == goal.y;
}
}
第10章のStreamの節から。
String#lines、便利。
テキストブロックを改行コードで区切ってStreamにしてくれるんですね。
APIドキュメント
サンプルソース
package beprofessional;
public class Hello {
public static void main(String[] args) {
var s = """
複数行の作文です。
# この行はコメントだから表示しないということにしてみます。
いい感じに分割してStreamにしてくれるそうなので
どんなふうになるのか試してみます。
""";
s.lines().filter(x -> !x.startsWith("#")).forEach(System.out::println);
}
}
結果
複数行の作文です。
いい感じに分割してStreamにしてくれるそうなので
どんなふうになるのか試してみます。
第19章、ステートメント補完、便利ですね!
これ欲しかったやつです。
var a = "hoge"+"fuga"
最後のダブルコーテーションの前にカーソルがあるときに、Command + Shift + Return
をすると…
var a = "hoge" + "fuga";
こうなって便利!
特にVimプラグインを使っているときはカーソルキーを触りたくないので便利。