たなかのJava日記

どんなことをやったか(学んだか)、どこで詰まったか(わからなかったか)、どこで工夫したかの記録です。

【Spring Boot】タスクを一覧表示するエンドポイントの作成

前回まで行ったことは、以下の2点になります。
・タスクの情報を保持するためのモデルの作成
・タスクを追加するモデルの作成


これでは、どんなタスクを登録したか確認することができません。
そこで今回は登録済みのタスクを一覧表示するエンドポイントも追加します。


    @GetMapping("/restlist")
    String listItems() {
        String result = taskItems.stream()
                .map(TaskItem::toString)
                .collect(Collectors.joining(", "));
        return  result;
    }


最初にアノテーションとして、 @GetMappingを指定しました。
どこで受け取るかをアノテーションの()内にvalue属性として記載します。
value属性はURLのどのパスに対するリクエストがどのメソッドで処理されるのかを指定するために使われます。
@GetMappingを指定した理由としては、
クライアントからのリクエストを処理するメソッドであることを示すためです。


@GetMapping("/restlist")


これで/restaddにリクエストが送信されたときの処理(タスクを一覧表示する処理)はこちらで行う(受け取る)と指定することができました。
また、@GetMappingを記載することでHTTP通信に関する部分の実装をSpringWebに任せることも行っています。


次に処理を行う箇所である、listitemsメソッドです。


taskItems.stream()

ここでは、コレクションからStreamを取得しています。
Streamとはコレクションの要素へのアクセスを許可されたもの、コレクションの別の形態と考えてください。
タスク情報はtaskItemsフィールドに格納されているので、その値をまとめて取得します。


.map(TaskItem::toString)


次に、Streamの中間処理を実施します。
Streamに流れる値を変更したり取捨選択するのが中間処理です。
mapメソッドは値を加工する場合に使用します。


今回はラムダ式をメソッド参照に変換し、値を加工しています。


ラムダ式の場合は以下のようになります。
. map(x -> TaskItem.toString(x))


ラムダ式で受け取った引数をメソッドにそのまま渡しているものはメソッド参照にできます。
メリットは変数名を考えなくていいことです。どういった名前が適切か考える必要がありません。


インスタンスメソッド呼び出しをメソッド参照に変換
.map(メソッドの属するクラス名::メソッド)
ここで、メソッドの属するクラス名を指定する必要があるので、どのクラスのメソッドを呼び出しているかの把握が必要になります。
わからない場合はIDEの補完に頼るなどした方が良いです。


解説ばかりでしたが、
ここではtaskItemsの各要素をtoStringメソッドで文字列に変換しています。
taskItemsの各要素はTaskItem型なのでメソッド参照の指定のクラス名はTaskItemになります。


.collect(Collectors.joining(", "));


最後に終端処理を行っています。
Streamでの処理から最終的な結果を取り出すのが終端処理です。
終端処理はStreamに直接用意されているものと、そうでないものがあります。
Collectorsクラスは直接用意されていないに該当します。
Collectorsクラスを使用した終端処理はcollectメソッドを介して使います。
今回はjoiningメソッドの引数に区切り文字を与え、与えた文字で区切られるようにし、結合を行いreturnで返しています。


今回作成した箇所を、以前作成したクラスに追加します。
そしてプロジェクトを再ビルドして実行してみます。
起動に成功したらブラウザのアドレスバーからいくつかのタスクを登録してみます。


http://localhost:8888/restadd?task=掃除&deadline=2021-05-07
http://localhost:8888/restadd?task=歯医者予約&deadline=2021-05-08
※8888の箇所は自分で指定したポート番号にする


タスクを登録したら、今度はアドレスバーに
http://localhost:8888/restlist
と打ち込んでみます。


このリクエストはHomeControllerのlistItemsメソッドに紐づいているはずなので、登録したオブジェクトが文字列として以下のように表示されれば成功です。


TaskItem[id=20b404f0, task=掃除, deadline=2021-05-07, done=false], TaskItem[id=75ec1824, task=歯医者, deadline=2021-05-08, done=false]

Day34

■時間

3.0時間

 

■行ったこと

1,Spring Bootのアノテーションについて

 

■出来たこと

@RequestMaping,@GetMappingについての理解とアウトプット

ブラウザからHTTPリクエストとして投げられたタスクを追加するエンドポイントの作成

 

■その他

もっと進められると思いましたが、理解して自分の中でかみ砕くのに時間がかかっています。

【Spring Boot】@GetMapping、@RequestParamについて

前回はモデルの作成まで完了したので続きになります。

まず行うのは、「ブラウザからHTTPリクエストとして投げられたタスクを追加するエンドポイントの作成」です。


手順を簡単にまとめると、以下になります。
1,Webブラウザ(クライアント)が「HTTPリクエスト」を投げる
2,サーバ側でリクエストを解析・処理してタスク情報を受け取る
3,受け取った情報をエンドポイント(窓口)であるメソッドで処理をしてタスクを追加する


最初に行うことは手順2の、タスク情報を受け取ることです。言い換えるとタスク情報を読み取りたいということです。
そのための(GETリクエスト)アノテーションである、@GetMappingをメソッドの前に記載します。
どこで受け取るかをアノテーションの()内にvalue属性として記載します。
value属性はURLのどのパスに対するリクエストがこのメソッドで処理されるのかを指定するために使われます。


@GetMapping("/restadd")


これで、2番の手順であるwebページでID,タスク名,期限,完了か否かの4つが/restaddに対して送信されてきてタスク情報を取得することできました。
また、@GetMappingを記載することでHTTP通信に関する部分の実装をSpringWebに任せることができます。
その他、補足情報として@GetMappingは@RequestMappingのGETリクエスト用のアノテーションになります。


例えば、今回の@GetMapping("/restadd")は
@RequestMaping("/restadd", method=RequestMethod.GET)と記述したのと同じになります。
@GetMappingとすることで、記述の省略と可読性の向上が期待できます。


次に取得したタスク情報を処理するためのエンドポイントの作成です。
エンドポイントとは簡単に言うと、「Webブラウザ(クライアント)との窓口となるメソッド」のことです。
Webアプリケーションのエンドポイントと呼んだりします。
addItemというメソッドをエンドポイントとして、手順3のタスクを追加する処理を書くことになります。


    @GetMapping("/restadd")
    String addItem(@RequestParam("task") String task,
                   @RequestParam("deadline") String deadline) {
        String id = UUID.randomUUID().toString().substring(0, 8);
        TaskItem item = new TaskItem(id, task, deadline, false);
        taskItems.add(item);

        return "タスクを追加しました。";
    }


処理の説明に入ります。まず、メソッド内のアノテーションについてです。
@RequestParamとは、このアノテーションが付けられた引数は、自動的にHTTPリクエストのパラメータ名に対応します。
パラメータとは、わかりやすくいえば「URLやフォームから送信された値」のことです。
このアノテーションを指定することで、URLやフォームから送られた値が、この引数に設定されるようになるということです。
例えば、ブラウザから「/restadd?task=cooking」と送信されたら、ブラウザからのtaskが(@RequestParam("task")と対応し、引数のtaskにcookingが設定されることになります。


今回、タスクには4つの値が必要ですが、引数は2つ(タスク名と期限)しかありません。
TaskItemのコンストラクタには、ID,タスク名,期限,完了か否かの4つの引数が必要です。
IDは重複が無い値にしたいので、java.util.UUIDクラスのrandomUUIDメソッドを使って生成しています。
ランダムに一意の値を生成する場合に使う常套手段です。
UUIDで生成される文字列は32文字ですが、今回はURLに埋め込まれる値が長いという都合上、先頭の8文字だけを切り出して使っています(本当はダメです)
完了か否かは初期状態では、未完了なので必ずfalseを返すのでメソッド内で指定します。
受け取った2つの引数と、メソッド内で作成したIDとfalseを使って新しくTaskItemオブジェクトを生成し、taskItemフィールドに追加します。


ここまで来たら、プロジェクトを実行します。ブラウザからタスクを追加してみましょう。
ブラウザからアクセスするためのWebページは未作成ですが、GETメソッドを使ったHTTPリクエストであればURLだけでも送ることができます。
ブラウザのアドレスバーに次のようなURLを入力したリクエストを送信してみます。


http://localhost:8888/restadd?task=部屋の掃除&deadline=2022-05-07
※8888は自分で指定したポート番号を記載する


リクエストを送信し、タスクを追加しました。と表示されれば、リクエストは成功です。

Day33

■時間

3.5時間

 

■行ったこと

1,Spring Bootのアノテーションについて

2,レコードについて

3,コンピュータはなぜ動くのか(8章)

 

■出来たこと

レコードについてのアウトプット

レコードを使用したモデルの作成

Listについて

 

■出来なかったこと

特にありません

 

【Spring Boot】タスクの情報を保持するためのモデルの作成

前回でWebサーバー上のプログラムとWebブラウザとの通信ができるようになりました。

次に「モデル」を作っていきます。

モデルとは、「アプリケーションで使用するデータを保持する部分」になります。

今回作成するWebアプリケーションでは「タスク情報を保持する部分」に該当します。

まず、今回作成するタスク管理アプリケーションで、1つのタスクにどのような情報を保持するか確認します。わかりやすく表にするのが良いです。

 

 

ここからどのようにモデルの部分を作成するかになります。

モデルは通常、保持したい情報をフィールド値に格納する形のクラスとして宣言しますが、情報を格納するためのクラスになるので、Java17以降ではレコードを使って宣言することができます。

レコードに関しては以下の記事にまとめてあります。

 

【record】違う種類の値をまとめて扱うレコードとは何が便利か・・・ - たなかのJava日記

 

レコード名をTaskItemとして宣言してみます。

record TaskItem(String id, String task, String deadline, boolean done) {}

 

このTaskItemレコードは1つのタスク情報を保持するためのものです。

タスクはいくつもあるのが当然なので、タスク管理アプリケーションでは複数のタスクを登録できるようにしなければなりません。

要はレコード(タスク)をいくつも格納できる仕組みが必要です。

いくつも格納できる仕組みがあることで、レコード(タスク)をまとめて管理できます。

 

複数の値をまとめて管理する仕組みはJavaにはいくつかありますが、ここではArrayListクラスを利用します。ArrayListは変更のできる(随時追加可能な)Listなので、いくつ追加するか未定なタスク管理にはもってこいです。次のように複数のTaskItemオブジェクトを格納するフィールドを用意します。

 

private List<TaskItem> taskItems = new ArrayList();

 

型の指定をTaskItemにしているのは、

Listで扱う(格納する)オブジェクト(タスク)がTaskItem型だからです。

 

左辺がList型になっている理由は、

「ざっくりListとして扱う方がメリットが大きい」からです。

ArrayListやLinkedListなどのListは、両者ともjava.util.Listインターフェースを実装しています。get()、remove()、size()などは全てListインターフェースに実装してあるメソッドになります。なのでListにArrayListは格納が可能であり、基本的なメソッドも使用可能です。また、メリットとしては、Listとしてざっくり扱っていてもその後の実装の違いを気にすることは少ないので、他のListも受け渡し可能にしておけば、その後の変更など含めて中身の受け渡しが容易になります。Listに関してはどうやら定石のようです。

 

引数、戻り値、ローカル変数には極力あいまいな型(インターフェース型)を利用できないかを検討し、積極的利用するのが良いとされています。

 

話がそれてしまいましたが、先ほどの2つの宣言をHomeRestControllerクラスの先頭部分に追加すると、次のようなコードになります。なお、taskItesのようなフィールドについても、以下URLでのメソッドと同様の理由でインスタンス変数として宣言することが原則となっています。

 

【Spring Boot】クラスのメソッドに決まりはあるのか - たなかのJava日記

 

これで、omeRestControllerクラスに、タスク情報を保持するための入れ物が用意できました。

 

 

 

 

 

 

 

 

 

 

【record】違う種類の値をまとめて扱うレコードとは何が便利か・・・

レコードとListや配列など、値をまとめて扱う仕組みが色々ありますが、簡単にまとめると以下になると思います。

 

■値をまとめて扱う仕組みの分類

Listや配列

⇒同じ種類の値をまとめて扱うことができる

レコード

⇒違う種類の値をまとめて扱うことができる

 

しかし、Listでは違う種類の値を扱うことができないのでしょうか。Java16でレコードが正式採用される前には何らかの形でやりくりしていたと考えられます。その結果を踏まえ、レコードの長所は何なのかを具体例を交えながら解決していけたらいいなと思います。

 

■まずListでは違う種類の値を扱うことができないのかを考える

 

例としてString型の受験者名と科目名、int型の点数をListでまとめて扱ってみます。

 

var exam = List.of("tanaka", "math", 70);

 

最初の項目に「受験者名」、

2番目に「科目名」、

3番目に「点数」を入れています。

 

最初の項目を取得すると受験者名を取得できます。

exam.get(0)

⇒tanaka

 

3番目の項目から点数がわかります。項目番号は0から始まるので3番目の項目を取るにはgetメソッドに2を渡します。

exam.get(2)

⇒70

 

ただListだと以下を自分で覚えておかないといけません。

・get(0)とすると何を得られるのか

・どの要素番号に何が入っているのか

ちょっと面倒ですね。

 

また、Listは文字列と整数の共通部分を抽出した型を扱うことになっています。

もう少し砕いて述べると【文字列と整数を両方扱えるけど文字列でも整数でもない型】になっています。

そのためget(0)で文字列が返ってくるとしてもプログラム上は文字列として扱えません。String型のメソッドを呼び出そうとしてもエラーになります。

 

exam.get(0).toUpperCase()

⇒エラー

 

このエラーの解決方法として、キャストがあります。

Listが扱うことになっているよくわからん型ではなく、実際の値の型に従ってメソッドを呼び出せばいいのです。数値以外のキャストでは値の内容は変わりませんので、プログラム上での扱いだけを変更します。

 

(String)exam.get(0)).toUpperCase()

⇒TANAKA

 

数値が入っている3番目の要素に対して間違えてString型へのキャストを行うと、ClassCastExceptionという例外が発生します。

 

((String) exam.get(2).toUpperCase()

⇒例外発生

 

ここでのキャストは値を扱うための型を変更するものです。しかし、その方では扱えない値を処理しようとすると、このような例外が発生します。

 

詳しくはわかりませんが、とりあえず型としてデータの内容をうまく表現できずに面倒なことになっていることがわかりました。考えないといけないことや踏まないといけない手順が多いです。

 

■レコードで違う種類の値をまとめて扱い、Listと比べてみる

この課題を解決するためには、項目に名前を付けて、型を指定して扱えると良いわけですが、その際に使用を検討できるのがJava 16から正式採用【レコード】です。

 

それでは早速、先ほどの違う種類の3つの値をまとめて扱ってみます。

 

record Exam(String name, String subject, int score) {}

 

【構文】レコードの定義

record レコード名(コンポーネントの型 コンポーネントの名前, ...) {}

 

【レコード名に使える文字】

⇒変数と同じくアルファベットや数字など。数字から始めることはNG。

小文字で始めることも出来ますが、慣習としては大文字で始めます。

 

また、レコードの要素を「コンポーネント」と呼びます。

今回のExamレコードはString型のname、subjectとint型のscoreの3つのコンポーネントを持っています。

 

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

var student = new Exam("tanaka", "math", 70);

コンポーネントの値は、コンポーネントを定義し順で指定します。

 

【レコードオブジェクトからコンポーネントの値を取得する】

student.name()

⇒tanaka

 

student.score()

⇒70

 

■まとめ

Listを使用した場合に比べ、名前を取るにはname()、得点を取るにはscore()とすればよく、コードを見てどんな値を取ってくるのかが格段にわかりやすくなりました。

 

名前を取得する

List・・・get(0)

record・・・name()

 

得点を取得

List・・・get(2)

record・・score()

 

そして、それぞれのコンポーネントに型を指定しているので、そのまま適切なメソッドを呼び出せます。

 

String型のメソッドを呼び出す

student.name().toUpperCase()

⇒TANAKA

 

このように、レコードを使用すると正しくわかりやすいプログラムが作りやすくなることがわかりました。

 

 

 

 

 

 

 

 

Day32

■時間

4.0時間

 

■行ったこと

1,Spring Bootのアノテーションについて

 

■出来たこと

JavaによるWebアプリケーション仕組みの理解とアウトプット

@RestController,@RequestMappingの理解とアウトプット

Spring Bootでのメソッドはインスタンスメソッドとして宣言しなければならないことについてのアウトプット

@GetMappingについての理解

 

■出来なかったこと

特にありません

 

■課題、コメント、その他

GW序盤であまり理解が出来なく楽しめなかったので、自分なりに洗い出してブログとしてアウトプットしたところ、数倍理解できました。一か月前の何も知らない自分に向けて説明をする口調で行うことで、不明点がはっきりしたのが良かったです。