前回は、ログイン処理の実装とセッションスコープについて学びました。
属性を扱うのも2つ目でしたから、その概念には慣れてきたのではないでしょうか?
今回は、JavaBeansについて学びます。この章の結論を先取りすると、JavaBeansを使うとプロパティというものを使ってクラスのフィールド(インスタンス変数)にアクセスできるため積極的に使いましょうということです。
1. JavaBeansとは
JavaBeansとは再利用可能なJavaプログラムの部品です。JSPのELと相性が良いのがこのJavaBeansなのです。
Javaというネーミングはそもそもジャワコーヒーから来ているのですが、その豆【Beans】ということで部品という意味です。また、新種の「オブジェクトを入れる箱」が出てきたのだとお考えください。
ここでもまた、数あてゲームについて考えてみましょう。
2章で見た数当てゲームの概念図を再掲すると下図の通りでした。
本当はリクエスト属性にはインスタンスが入れられるにも関わらず、文字列と数値を入れていましたね。
まずは、これを下図の形に変えます。
この形を今回「属性にオブジェクトを入れるがJavaBeansは使わないケース」と呼びたいと思います。すなわち、リクエスト属性には1つのインスタンスだけを入れて持ち運ぶこととします。それに伴い、ELの表記が変化する点にもご注目ください。
- ①違い探しクイズ 2つの図はどこが違いますか?(ヒント:3箇所違います)
あなたの答え: |
そしてさらにJavaBeansを使うことで最終形態の下図に変えます。ここでもまた、ELの表記が変化した点に注目ください。
- ②探しクイズ 上図はどこが違いますか?(ヒント:2箇所違います)
あなたの答え: |
1.1 2章で解説した数当てゲーム(再掲)
復習です。以下の4つのファイルを使ってどのようなことをしているか説明してください。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<meta charset="UTF-8">
<title>index.jsp</title>
</head>
<body>
<h3>0-9の整数で数を当ててください!</h3>
<form method="get" action="${pageContext.request.contextPath}/Game">
<input type ="text" size ="10" name ="guess">
<button type="submit">送信</button>
</form>
</body>
</html>
package controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import model.Kazuate;
@WebServlet(urlPatterns = { "/Game" })
public class GameServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String str = request.getParameter("guess");
int guess = Integer.parseInt(str);
Kazuate kazuate = new Kazuate();
request.setAttribute("message", kazuate.checkTheAnswer(guess));
request.setAttribute("answer", kazuate.getTheAnswer());
request.getRequestDispatcher("view/result.jsp").forward(request, response);
}
}
package model;
import java.util.Random;
public class Kazuate {
private int answer;
public Kazuate() {
Random random = new Random();
this.answer = random.nextInt(10);
}
public String checkTheAnswer(int guess) {
if (answer == guess) {
return "あたり";
} else if (answer > guess) {
return "もっと大きいよ";
} else {
return "もっと小さいよ";
}
}
public int getTheAnswer() {
return this.answer;
}
public void setTheAnswer(int answer) {
this.answer = answer;
}
}
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
<head>
<meta charset=UTF-8">
<title>result.jsp</title>
</head>
<body>
<h1>ゲームの結果は!</h1>
${requestScope.message}
<br> 答えは${requestScope.answer}
</body>
</html>
1.2 属性にオブジェクトを入れるがJavaBeansは使わないケース
2章で解説した数当てゲームでは上図のとおり、リクエスト属性には2つのインスタンス(String型の文字列とInteger型の整数)を入れていました。しかし、この後、例えば、データベースで扱ったCustomerを扱うような場合に、名前、メールアドレス、住所、誕生日やポイント数などテーブルの列数だけインスタンスを属性に詰め込まなければいけないとしたら大変です。
顧客一人分のすべての情報を持ったCustomerクラスのインスタンスを一度にJSPに送って表示できたら便利だと思いませんか?
さらには複数のCustomer(今井さん、篠原さん、田渕さん、松田さんなど)のデータの入ったCustomersクラス(複数形)があって、データベースから全員分のデータを取得して一度にJSPに送ってELで表示できるとしたらどうでしょうか?
そこで、リクエスト属性にはオブジェクトをまるごと入れるというのが上図でした。
以下にそれをソースコードで実現します。
そのためには、kazuateクラスを以下のようにkazuateObjectクラスに変えます。
package p06;
import java.util.Random;
public class KazuateObject{
private int answer;
private String message;
public KazuateObject() {
Random random = new Random();
this.answer = random.nextInt(10);
}
public void checkTheAnswer(int guess) {
if (answer == guess) {
this.message = "あたり";
} else if (answer > guess) {
this.message = "もっと大きいよ";
} else {
this.message = "もっと小さいよ";
}
}
public int getAnswer() {
return answer;
}
public void setAnswer(int answer) {
this.answer = answer;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
- ①kazuate.javaとkazuateObject.javaを比較した場合フィールドの観点での変更点は何ですか?
あなたの答え: |
- ②同様にメソッドの観点での変更点は何ですか?
あなたの答え: |
answerとmessageをフィールドとしてインスタンス自身が持ち運ぶようにしたわけですね。
サーブレットは以下GameServlet5.javaのようになります。
package controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import model.KazuateObject;
@WebServlet(urlPatterns = { "/Game5" })
public class Game5Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String str = request.getParameter("guess");
int guess = Integer.parseInt(str);
KazuateObject kazuate = new KazuateObject();
kazuate.checkTheAnswer(guess);
request.setAttribute("kazuate", kazuate);
request.getRequestDispatcher("view/result1.jsp").forward(request, response);
}
}
JSPは以下result1.jspのようになります。
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset=UTF-8">
<title>result1.jsp</title>
</head>
<body>
<h1>ゲームの結果は!</h1>
${kazuate.getMessage()}<br>
答えは${kazuate.getAnswer()}<br>
</body>
</html>
EL式でインスタンスのメソッドを呼び出しています。これがこの後、JavaBeansを使うことでどのように変わるかに期待してください。
特に新しい論点はありませんが、入力画面index1.jspのindex.jspからの変更部分を以下に掲載します。
<form method="get" action="${pageContext.request.contextPath}/Game5">
1.3 JavaBeansを使うケース
まず最初に結論としてJavaBeansを使った場合のソースコードを紹介します。
以下のKazuateBean.javaはMVCのうちのモデルに該当します。
package model;
import java.io.Serializable;
import java.util.Random;
public class KazuateBean implements Serializable {
private int answer;
private String message;
public KazuateBean() {
Random random = new Random();
this.answer = random.nextInt(10);
}
public void checkTheAnswer(int guess) {
if (answer == guess) {
this.message = "あたり";
} else if (answer > guess) {
this.message = "もっと大きいよ";
} else {
this.message = "もっと小さいよ";
}
}
public int getAnswer() {
return answer;
}
public void setAnswer(int answer) {
this.answer = answer;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
今までのKazuateObjectクラスとの変更点を見つけましょう。
- ①実装しているインタフェースの観点での変更点は何ですか?
あなたの答え: |
JavaBeansがJavaBeansであるためには、以下の3つのルールを適用する必要があります。
- java.io.Serializableを実装している
- 引数のないコンストラクタを持つ
- フィールドはprivateにした上でgetter/setterメソッドを持つ
以下に順を追って解説します。
1.3.1 JavaBeansはjava.io.Serializableを実装している
Serializableインタフェースを実装することでスコープ内のインスタンスを直列化してファイル等に保存したり、ネットワークを介して送信したりすることができるようになります。
インスタンスを直列化してファイルに保存できれば、
- Tomcat等のアプリケーションサーバーが停止した場合も再起動してインスタンスを復元できます
- 一時的にメモリが溢れた場合にも退避させることができます
なお、 Serializableインタフェースはマーカーインターフェイスだということを忘れてしまった方はJavaSEのインタフェースのところで復習しておいてください。
1.3.2 JavaBeansは引数のないコンストラクタを持つ
今回のKazuateBeanクラスでは、たまたま引数のないコンストラクタがありましたので特に変更点はありません。
「コンストラクタの無いJavaクラスには見えてないけれどデフォルトコンストラクタがあるはず」そう思った方はコンストラクタの事がよく分かっています。その場合はデフォルトコンストラクタで結構です。
しかし、もしも引数のあるコンストラクタを作った場合はデフォルトコンストラクタは作られません。その場合は引数のないコンストラクタをあらためて作る必要があります。
1.3.3 JavaBeansはフィールドはprivateにした上でプロパティへのgetter/setterメソッドを持つ
上記のKazuateBeanクラスには2つのフィールド(answerとmessage)がありました。
getter/setterメソッドとは、getXxxx(isXxxx)、setXxxx としたフィールドにアクセスするためのメソッドのことです。(上記KazuateBeanのgetAnswer()がその例になります)
プロパティというのはこのメソッドの Xxxxの最初を小文字にしてxxxxとしたものです 。英語の【property】には資産という意味があります。(後で見るresult2.jspのkazuate.messageがその例です)
プロパティ化したいメソッド名の先頭にはget(is)/setを付けるというルールがあります。(本当はxxxxのところにはフィールド名以外が来てもよいのですが、当社の新人エンジニア研修ではメソッドのXxxxのところには全てフィールド名を入れることにしますので、その限りにおいてフィールドとプロパティの間に特段の区別は必要ありません)
なお、getter/setterメソッドはアクセサメソッド【accessor methods】と総称されることもあります。アクセサメソッドでは単にフィールドの値を設定取得するだけでなく、加工することもできます。アクセサメソッドはIDEを使っていれば簡単に挿入できますので方法を講師にお尋ねください。
フィールドを private で修飾するとクラス外からはアクセスできなくなるということを我々はカプセル化と情報隠蔽のところで学びました。そこでアクセサメソッドを使ってフィールドにアクセスします。
ここである程度勉強している人は「情報隠蔽をしてもアクセサメソッドがあれば同じではないか?」と考えるかも知れません。しかし、思い出していただきたいのですが、int型のフィールドにはint型の値であれば何でも入ったのに対して、メソッドを経由すれば、特定の値(例えば、マイナスの値)は入らないようにすることもできましたね。さらに、フィールドに読み込みだけ可能で、一切書き込みができないクラスを作りたいのであれば、setterメソッドを作らなければよいのです。
なお、このプロパティの考え方は他のオブジェクト指向言語にも発展的に採用されています。例えばC#、Python、Kotlin等のプロパティはより便利に使えるようになっています。
2. JavaBeansを活用した数あてゲーム
では、早速Beansを使ったプログラムがどう変わるかを見てみましょう。
入力画面<index2.jsp>は<index1.jsp>から飛び先以外大きな変更はありません。
以下のGameServlet6.javaを読み込んで質問に答えてください。
package controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import model.KazuateBean;
@WebServlet(urlPatterns = { "/Game6" })
public class Game6Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int guess = Integer.parseInt(request.getParameter("guess"));
KazuateBean kazuate = new KazuateBean();
kazuate.checkTheAnswer(guess);
request.setAttribute("kazuate", kazuate);
request.getRequestDispatcher("view/result2.jsp").forward(request, response);
}
}
- ①「request.setAttribute("kazuate", kazuate);」でリクエストスコープに格納しているのは何ですか?
あなたの答え: |
以下のresult2.jspはゲームの結果を表示するビューです。
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset=UTF-8">
<title>result2.jsp</title>
</head>
<body>
<h1>ゲームの結果は!</h1>
${kazuate.message}<br>
答えは${kazuate.answer}<br>
</body>
</html>
このELで利用しているのがプロパティです。
kazuate.messageと書くことによってkazuateBeanクラスのインスタンスのgetMessage()メソッドが働くのです。
- ①では、${kazuate.answer}という記述によって働くのはどのクラスの何というメソッドですか?
あなたの答え: |
3. 買い物かごの実装
ここまでの知識を応用して買い物かごの仕組みを作ってみましょう。どんな知識を使うかといえば主にセッション属性とJavaBeansです。
目指したいのは下図のような仕組みです。
ポイントは赤で表示されたcartオブジェクトです。このオブジェクトには複数のcarオブジェクトが入るとします。カートの合計金額(変数名:total)はどのオブジェクトが持つべきでしょうか?
そうですね、個々の車に持たせるのは変ですからcartが持つべきでしょうね。また、このcartは車の追加や削除ができないといけません。
以下のCarBean.javaは車のクラスです。セッションスコープに入れることを見越してBeansとして作成します。特に特徴のないアクセサメソッドを持つBeansですが、このあとの説明のためインスタンスを1行で作れるように引数を3つ持つコンストラクタも作成しておきました。
CarBeanクラス
package model;
import java.io.Serializable;
public class CarBean implements Serializable {
private int carId;
private String name;
private int price;
public CarBean() {
}
public CarBean(int carId, String name, int price) {
this.carId = carId;
this.name = name;
this.price = price;
}
public int getCarId() {
return carId;
}
public void setCarId(int carId) {
this.carId = carId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
Cartクラス
以下のCart.javaを読み込んで質問に答えてください。
package model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Cart implements Serializable {
private List<CarBean> carList;
public Cart() {
carList = new ArrayList<>();
}
public void addCar(CarBean aCar) {
this.carList.add(aCar);
}
public List<CarBean> getList() {
return carList;
}
public int getTotal() {
int total = 0;
for (CarBean carBean : carList) {
total += carBean.getPrice();
}
return total;
}
public List<CarBean> remove(int carId) {
Iterator<CarBean> ite = carList.iterator();
while (ite.hasNext()) {
CarBean car = ite.next();
if (car.getCarId() == carId) {
ite.remove();
}
}
return carList;
}
}
- ①このCartクラスのフィールドとメソッドのそれぞれの役割を答えなさい。
あなたの答え: |
削除のメソッドにはイテレータというものを使っています。
なぜ、イテレータが必要なのかという点に関しては、JavaSEのArrayListのところで解説しました。忘れてしまった人は見直しておいてください。
以下のCartTest.javaを読み込んで質問に答えてください。
package test;
import model.CarBean;
import model.Cart;
public class CartTest {
public static void main(String[] args) {
CarBean c1 = new CarBean(1, "セダン", 2590000);
CarBean c2 = new CarBean(2, "クーペ", 4990000);
CarBean c3 = new CarBean(2, "クーペ", 4990000);
Cart cart = new Cart();
cart.addCar(c1);
cart.addCar(c2);
cart.addCar(c3);
for (CarBean car : cart.getList()) {
System.out.print(car.getName() + ":");
System.out.println(car.getPrice());
}
System.out.print("合計:");
System.out.println(cart.getTotal());
cart.remove(2);
for (CarBean car : cart.getList()) {
System.out.print(car.getName() + ":");
System.out.println(car.getPrice());
}
System.out.print("合計:");
System.out.println(cart.getTotal());
}
}
- ①上記の処理を解説しなさい。
あなたの答え: |
これをサーブレットとJSPを使って実現できれば良いわけですね。
そのためにはJSPに繰り返しや条件分岐を使って表示できたほうが良いので先に次章でJSTLを学ぶことにします。
この章ででてきたCustomerクラスやCustomersクラス、CarクラスやCartクラスは次の章でも使いますので、しっかり復習しておいてください。
また、教室では「AddCartServlet.java」でカートへの追加、「RemoveCartServlet.java」でカートからの削除のサンプルコードを配布する場合があります。余裕があれば説明を受けてください。
今回はJavaBeansについて学びました。
次回はELとJSTLで様々な表現ができるようになる方法を学びます。