6月29日

プロジェクト SpringMyApp を作成して動かすまで

プロジェクトを作成する

以下の手順でプロジェクトを作成する。(テキストp.39~)

  1. メニューから[新規]-[その他]を選択する。
  2. 「ウィザードを選択」のダイアログで[Spring]-[Srpingプロジェクト]を選択して「次へ」をクリック。
  3. プロジェクト名に「SpringMyApp」を入力する。
  4. テンプレートは「Simple Spring Web Maven」を選択して「完了」をクリック。

プロジェクトを更新する

パッケージエクスプローラで作成したプロジェクトを右クリックし、メニューで[Maven]-[プロジェクトの更新]を選択する。
Eclipse画面の右下でビルド中の表示が消えれば完了。

Maven installを実行する

パッケージエクスプローラで作成したプロジェクトを右クリックし、メニューで[実行]-[Maven install]を選択する。
コンソールに以下のように出力されれば完了。

[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.169 s
[INFO] Finished at: 2015-06-29T10:02:49+09:00
[INFO] Final Memory: 12M/220M

Tomcat7サーバーに追加

以下の手順で作成したプロジェクトをTomcat7サーバーに追加する。

  1. Eclipseのサーバータブで、Tomcat7サーバーを右クリック。
  2. メニューで「追加および除去」を選択。
  3. 作成したプロジェクトを選択し「追加」をクリックして右の枠に移動させる。
  4. 「完了」をクリック。
  5. http://localhost:8080/ProjectName にアクセスする。
  6. click to enter と表示されればOK。

コントローラを作成する

プロジェクト配下の特定のURLにアクセスされたときの処理を記述するには、コントローラを作成する必要がある。
以下の手順でコントローラを作成する。(テキストp.160~)

  1. パッケージエクスプローラで作成したプロジェクトを展開する。
  2. 「Javaリソース」を展開する。
  3. 「src/main/java」を右クリックし[新規]-[クラス]を選択する。
  4. パッケージ名に「jp.abc」を入力。
  5. クラス名に「MyAppController」を入力。
  6. 「完了」をクリック。
  7. MyAppController.java のエディタが開く。

MyAppController.javaのコードを編集する。テキストp.163のリスト3-3とほぼ同じ。パッケージ名だけは異なるので注意すること。

MyAppController.java

package jp.abc;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class MyAppController {

	@RequestMapping(value = "/helo", method = RequestMethod.GET)
	public String helo(Model model) {
		model.addAttribute("message", "this is sample. ok?");
		return "showMessage";
	}
}

mvc-config.xmlを設定する

パッケージエクスプローラで プロジェクト名 > src > main > webapp > WEB-INF の下にある mvc-config.xml を開く。
以下の部分を書き換える。

mvc-config.xml

    <!-- Uncomment and your base-package here:
         <context:component-scan
            base-package="org.springframework.samples.web"/>  -->

書き換え後は以下のとおり。

mvc-config.xml

    <context:component-scan base-package="jp.abc"/>

設定ファイルを変更したので、Tomcat7サーバーを再起動する。
http://localhost:8080/(ProjectName)/helo にアクセスすると、「this is sample. ok?」と表示される。

入力フォームを追加する

SpringMVCで入力フォームを作るには、データをやり取りするためのオブジェクトを作成する必要がある。
jp.abc パッケージにFormModelクラスを作成する。

「src/main/java」の下の「jp.abc」を右クリックして[新規]-[クラス]を選択する。
名前に「FormModel」と入力して完了をクリック。
以下のコードを入力する。

FormModel.java

package jp.abc;

public class FormModel {
	private String input1;

	public String getInput1() {
		return input1;
	}

	public void setInput1(String input1) {
		this.input1 = input1;
	}
}

次にJSPを編集する。
「src > main > webapp > WEB-INF > view」の下にある showMessage.jsp を開き、以下のようにコードを追加する。2行目の taglib ディレクティブを忘れないように!

showMessage.jsp

<!DOCTYPE html>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<html>
	<head>
		<meta charset="utf-8">
		<title>Welcome</title>
	</head>
	<body>
		<h2>${message}</h2>
		<form:form modelAttribute="formModel">
			<form:input path="input1"/>
			<input type="submit">
		</form:form>
	</body>
</html>

MyAppController を編集する。
GETメソッドで呼ばれたときに、FormModelを渡すようにコードを追加する。
また、POSTメソッドで呼ばれたときに、渡された文字列を message に渡すようにする。

MyAppController.java

package jp.abc;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class MyAppController {

	@RequestMapping(value = "/helo", method = RequestMethod.GET)
	public String helo(Model model) {
		FormModel fm = new FormModel();
		fm.setInput1("ここに書く");
		model.addAttribute("formModel", fm);
		model.addAttribute("message", "何か書いてください。");
		return "showMessage";
	}

	@RequestMapping(value = "/helo", method = RequestMethod.POST)
	public String form(@ModelAttribute FormModel fm, Model model) {
		model.addAttribute("message", "you typed: " + fm.getInput1());
		return "showMessage";
	}
}

文字化けの対策

日本語文字列を入力して送信すると文字化けが発生する。
フィルターを設定して文字化け対策をする。

「src > main > webapp > WEB-INF」にある web.xml を開き、ソースタブを選択してフィルターの設定を追加する。

最後の </web-app> タグの直前に追加する。

	<filter>
		<filter-name>CharacterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
</web-app>

6月26日

STSのインストール

テキスト「SpringMVC/Roo」のp.21「EclipseへのSTSのネットワーク経由インストール」の手順に従って、EclipseにSTSプラグインをインストールする。

Springプロジェクトの作成

  • [ファイル]-[新規]-[プロジェクト] を選択
  • プロジェクトウィザードで「Springプロジェクト」を選択
  • プロジェクト名は SpringMyApp と入力。
  • 「次へ」をクリック
  • テンプレートで「Simple Spring Web Maven」を選択して「完了」をクリック

DIの仕組み

プロジェクト MyDIApp を作成する。

Controllerを作成する

src/main/java を右クリックして[新規]-[クラス]を選択する。
パッケージ: jp.abc
クラス名: MyAppController

MyAppController.java

package jp.abc;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class MyAppController {

	@RequestMapping(value = "/helo", method = RequestMethod.GET)
	public String helo(Model model) {
		model.addAttribute("message", "this is sample. ok?");
		return "showMessage";
	}
}

mvc-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="jp.abc"/>


    <mvc:annotation-driven />

	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	        <!-- Example: a logical view name of 'showMessage' is mapped to '/WEB-INF/jsp/showMessage.jsp' -->
	        <property name="prefix" value="/WEB-INF/view/"/>
	        <property name="suffix" value=".jsp"/>
	</bean>

</beans>

修正した mvc-config.xml を読み込ませるために、Tomcat7サーバーを再起動する。
再起動後、 http://localhost:8080/SpringMyApp/helo にアクセスすると、「this is sample. ok?」と表示され、showMessage.jsp が表示されたことがわかる。

6月22日

JSPの暗黙オブジェクト

参考サイトを参照。
暗黙オブジェクト

index.jsp に暗黙オブジェクト request から取得できるデータを表示してみる。

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello World!</title>
</head>
<body>

<h1>Hello World!</h1>
<p>
日本語の表示を確認
</p>

<h3>式</h3>
<%=1+1 %>
<br />
<%=new java.util.Date() %>
<!-- HTMLコメント -->
<%-- JSPコメント --%>

<h3>JSP宣言</h3>
<%! int count = 10; %>
<%=count %>

<h3>スクリプトレット</h3>
<%
	out.print("scriptlet<br />");
%>

<h3>requestの情報</h3>
authType=<%=request.getAuthType() %><br />
characterEncoding=<%=request.getCharacterEncoding() %><br />
contentLength=<%=request.getContentLength() %><br />
contentType=<%=request.getContentType() %><br />
contextPath=<%=request.getContextPath() %><br />
localAddr=<%=request.getLocalAddr() %><br />
localName=<%=request.getLocalName() %><br />
localPort=<%=request.getLocalPort() %><br />
method=<%=request.getMethod() %><br />
pathInfo=<%=request.getPathInfo() %><br />
pathTranslated=<%=request.getPathTranslated() %><br />
protocol=<%=request.getProtocol() %><br />
queryString=<%=request.getQueryString() %><br />
remoteAddr=<%=request.getRemoteAddr() %><br />
remoteHost=<%=request.getRemoteHost() %><br />
remotePort=<%=request.getRemotePort() %><br />
remoteUser=<%=request.getRemoteUser() %><br />
requestedSessionId=<%=request.getRequestedSessionId() %><br />
requestURI=<%=request.getRequestURI() %><br />
scheme=<%=request.getScheme() %><br />
serverName=<%=request.getServerName() %><br />
serverPort=<%=request.getServerPort() %><br />
servletPath=<%=request.getServletPath() %><br />

</body>
</html>

パラメータ

URL にパラメータを追加すれば、request.getParameter() で値を取得できる。

index.jsp

<h3>パラメータ</h3>
a=<%=request.getParameter("a") %><br />
b=<%=request.getParameter("b") %><br />
c=<%=request.getParameter("c") %><br />

EL式

EL式は、${式} の書式で記述できる。

index.jsp

<h3>EL式</h3>
${100 * 100}<br />
<% request.setAttribute("test", new java.util.Date()); %>
${test}<br />

JSTL

2つのJARファイルを以下の場所からコピーして、WEB-INF/lib の下に貼り付ける。
\\kgakusei1\share\澤田\jstl\lib

c:setタグ

c:set タグで、値を設定できる。
実際には、暗黙オブジェクト request に対して、setAttribute() メソッドを呼んでいる。

jstl.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSTL</title>
</head>
<body>

<h1>JSTL</h1>

<c:set var="data" value="てすと!" />
${data}<br />

</body>
</html>

c:ifタグ

c:if タグで、単一の条件分岐を記述できる。

jstl.jsp

<c:set var="count" value="10" />
<c:if test="${count >= 10}" >
  count=${count}
</c:if>
<c:if test="${count >= 11}" >
  count=${count}
</c:if>

c:chooseタグ

c:choose タグで複数の条件分岐を記述できる。

jstl.jsp

<c:set var="age" value="9" />
<c:choose>
  <c:when test="${age >= 30}">30代以上</c:when>
  <c:when test="${age >= 20}">20代</c:when>
  <c:when test="${age >= 10}">10代</c:when>
  <c:otherwise>10歳以下</c:otherwise>
</c:choose>

c:forEachタグ

c:forEach タグでループを記述できる。
items属性にループの対象となる配列またはコレクションを指定し、var属性に取得した要素を格納する変数名を指定する。

jstl.jsp

<h3>c:forEach</h3>
<%
	List<String> list = new ArrayList<String>();
	list.add("Wii U");
	list.add("PS Vita");
	list.add("Nintendo 3DS");
	request.setAttribute("list",list);
%>

<c:forEach var="s" items="${list}">
  ${s}<br />
</c:forEach>

fmt:formatDateタグ

fmt:formatDateタグで書式を指定して日時を出力できる。

jstl.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>



<h3>fmt:formatDate</h3>
<%
	request.setAttribute("date", new Date());
%>
<fmt:formatDate value="${date}" pattern="yyyy年MM月dd日(E) a KK時mm分ss秒" /><br>

6月19日

Webアプリケーション

Webアプリケーションを作る前に、Webアプリケーションとはどういうものかを考えてみる。

WebApp

昨年の設定

プロジェクトを作成する

Eclipseで[新規]-[プロジェクト]を選択
Webの下にある[動的Webプロジェクト]を選択
プロジェクト名: jsp
ターゲットランタイム: Apache Tomcat v7.0
「次へ」→「次へ」→「完了」でプロジェクトを作成する

Eclipseの設定

[ウィンドウ]>[設定]>[Web]>[JSPファイル]を選択
エンコードを「UTF-8」に設定
[ウィンドウ]>[設定]>[Tomcat]を選択
Tomcatバージョンは「バージョン7.x」を選択
Tomcatホームは「C:\All-in-One-Eclipse4.3\tomcat\7」を選択
「OK」をクリック

jspプロジェクトを右クリック
[Tomcatプロジェクト]-[コンテキスト定義を更新]を選択

JSPファイルを作成

パッケージエクスプローラでWebContentを右クリック
[新規]-[JSPファイル]を選択
ファイル名を index.jsp に設定

index.jsp を右クリックして、[実行]-[サーバーで実行]を選択。
ブラウザタブが開いて、index.jsp の内容が表示される。

index.jspの編集

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello World!</title>
</head>
<body>

<h1>Hello World!</h1>
<p>
日本語の表示を確認
</p>

<h3>式</h3>
<%=1+1 %>
<br />
<%=new java.util.Date() %>
<!-- HTMLコメント -->
<%-- JSPコメント --%>

<h3>JSP宣言</h3>
<%! int count = 10; %>
<%=count %>

<h3>スクリプトレット</h3>
<% %>

</body>
</html>

6月15日

Mapを使用したクラス

ゲームのタイトルをキーにして会社名を値として保存するクラスの続き。
ひとつのデータを格納して会社名を取得するテストを追加する。

GamesTest.java

	@Test
	public void testGet() {
		Games g = new Games();
		g.add("スーパーマリオブラザーズ", "任天堂");
		String s = g.get("スーパーマリオブラザーズ");
		assertThat(s, is("任天堂"));
	}

このテストにパスするには、Fakeすればよい。

Games.java

import java.util.HashMap;
import java.util.Map;


public class Games {
	private Map<String, String> map = new HashMap<String, String>();

	public void add(String title, String company) {
		map.put(title, company);
	}

	public int getCount() {
		return map.size();
	}

	public String get(String string) {
		return "任天堂";
	}
}

さらにテストを追加する。

	@Test
	public void testGet1() {
		Games g = new Games();
		g.add("パズドラ", "ガンホー");
		String s = g.get("パズドラ");
		assertThat(s, is("ガンホー"));
	}

両方のテストをパスするためには、Mapを使用してきちんと実装するのがよい。

Games.java

	public String get(String key) {
		return map.get(key);
	}

ファイルから読み込む

map.txt を作成して、タイトルと会社名のデータを書き込んでおく。

map.txt

スーパーマリオブラザーズ,任天堂
パズドラ,ガンホー
妖怪ウォッチ,レベルファイブ
ドラゴンクエスト,スクエアエニックス

このファイルのデータを読み込むテストを作成する。

GamesTest.java

	@Test
	public void testLoad() {
		Games g = new Games();
		g.load("map.txt");
		int n = g.getCount();
		assertThat(n, is(4));
	}

Gamesクラスにload()メソッドを追加して、指定されたファイルの内容を読み込み、マップに追加する。
ファイルを読み込む部分は、TextDocumentクラスのload()メソッドが参考になる。

Games.java

	public boolean load(String path) {
		File f = new File(path);
		if (!f.exists()) return false;
		try (BufferedReader in = new BufferedReader(new FileReader(path))) {
			while (in.ready()) {
				String s = in.readLine();
				String[] sa = s.split(",");
				map.put(sa[0], sa[1]);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return true;
	}

テキストファイルに出力する機能を追加する

格納している内容をテキストファイルに出力し、出力したファイルを読み込んで行数が一致するかを確認するテストを作成する。

GamesTest.java

	@Test
	public void testSave() {
		Games g = new Games();
		g.add("パズドラ", "ガンホー");
		g.save("map1.txt");
		boolean result = g.load("map1.txt");
		assertThat(result, is(true));
		int n = g.getCount();
		assertThat(n, is(1));
	}

保存の処理内容は、TextDocumentクラスのsave()とほぼ同じ。
タイトルと会社名をカンマで区切って出力すればよい。
ループする部分はテキストp.444が参考になる。

Games.java

	public void save(String path) {
		try (PrintWriter out = new PrintWriter(new FileWriter(path))) {
			Set<String> keys = map.keySet();
			for (String key : keys) {
				String v = map.get(key);
				out.println(key + "," + v);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

要素の削除

指定したタイトルを削除する機能を追加する。
まずテストを作成する。

GamesTest.java

	@Test
	public void testDelete() {
		Games g = new Games();
		g.load("map.txt");
		int n = g.getCount();
		assertThat(n, is(4));
		g.delete("パズドラ");
		n = g.getCount();
		assertThat(n, is(3));
		String s = g.get("パズドラ");
		assertNull(s);
	}

テストにパスするようにGames#delete()を実装する。
要素の削除はテキストには掲載されてないが、Eclipseの補完機能で表示される候補から探せば見つかる。

6月12日

ストリームとコレクション

前回、TextDocumentクラスをテスト駆動開発で作成し始めたので、その続きを実施する。

  1. 指定した行の文字列取得
  2. 指定した行を削除(削除した行の文字列を返す)
  3. 指定した文字列を行として末尾に挿入
  4. 指定した場所に指定した文字列を行として挿入
  5. 出力先ファイル名を指定してファイル出力

指定した場所に指定した文字列を行として挿入

TextDocument.java

	public void addLine(int i, String s) {
		lines.add(i, s);
	}

前回は、4まで実施したが、先頭行に挿入するテストしかやっていないので、別のテストを追加して動作確認を実施する。

まずは、途中に挿入するテストを追加してみる。

	@Test
	public void testAdd2() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		int lines = td.getLines();
		assertThat(lines, is(10));
		td.addLine(3, "1234567890");
		lines = td.getLines();
		assertThat(lines, is(11));
		String s = td.getLine(0);
		assertThat(s, is("ab"));
		s = td.getLine(3);
		assertThat(s, is("1234567890"));
		s = td.getLine(4);
		assertThat(s, is("abcde"));
	}

範囲外の -1 行目を引数で指定した場合は例外が発生する。
例外の発生をテストで書くには、Testアノテーションの引数に発生する例外を記述する。

TextDocumentTest.java

	@Test(expected = IndexOutOfBoundsException.class)
	public void testAdd3() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		td.addLine(-1, "1234567890");
	}

10行しかないときに11行目に追加しようとしても例外が発生する。
まずは、例外が発生してエラーになるテストを書く。
TextDocumentTest.java

	@Test
	public void testAdd4() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		td.addLine(11, "1234567890");
	}

例外が発生してエラーになることを確認してから、アノテーションに expected 引数を追加する。

	@Test(expected = IndexOutOfBoundsException.class)
	public void testAdd4() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		td.addLine(11, "1234567890");
	}

次はファイルに書き込む機能を追加する。
まずはテストを書く。
ファイルに書けたかどうかは、ファイルを読み込んでテストしてみる。
TextDocumentTest.java

	@Test
	public void testSave() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		td.addLine(0, "1234567890");
		td.save("out.txt");
		td.load("out.txt");
		int lines = td.getLines();
		assertThat(lines, is(11));
	}

TextDocumentに、保存の処理を実装する。

	public void save(String path) {
		try (PrintWriter out = new PrintWriter(new FileWriter(path))) {
			for (String s : lines) {
				out.println(s);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

Mapの使用例

ゲームタイトルをキーにして会社名を保存するクラスを作成する。

Games.java

public class Games {
}

テストを作る。
GamesTest.java

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;


public class GamesTest {

	@Test
	public void testAdd() {
		Games g = new Games();
		g.add("スーパーマリオブラザーズ", "任天堂");
		int n = g.getCount();
		assertThat(n, is(1));
		g.add("パズドラ", "ガンホー");
		n = g.getCount();
		assertThat(n, is(2));
	}

}

テストにパスするように Games を実装したら、さらにテストを追加する。
GamesTest.java

	@Test
	public void testGet() {
		Games g = new Games();
		g.add("スーパーマリオブラザーズ", "任天堂");
		String s = g.get("スーパーマリオブラザーズ");
		assertThat(s, is("任天堂"));
	}

6月8日

テスト駆動開発でストリームとコレクションを復習する

TextDocumentクラスと、テストケースのTextDocumentTestクラスを作成する。

ファイルを読み込む機能を用意する。
そのために、load()メソッドを作成する。
まずはテストを書く。

TextDocumentTest.java

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;

public class TextDocumentTest {

	@Test
	public void testLoad() {
		TextDocument td = new TextDocument();
		boolean b = td.load("hoge.txt");
		assertThat(b, is(true));
	}

}

コンパイルエラーを解消するために、TextDocumentクラスにload()メソッドを追加する。
TextDocument.java

public class TextDocument {
	public boolean load(String path) {
		return false;
	}
}

テストを実行するとエラーになるので、load()メソッドの戻り値をtrueに変更する。(Fake)
TextDocument.java

public class TextDocument {
	public boolean load(String path) {
		return true;
	}
}

テストにパスするのでめでたしめでたし。
テストケースに、パスしないテストを追加する。
TextDocumentTest.java

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;

public class TextDocumentTest {

	@Test
	public void testLoadNG() {
		TextDocument td = new TextDocument();
		boolean b = td.load("aaa.txt");
		assertThat(b, is(false));
	}

	@Test
	public void testLoad() {
		TextDocument td = new TextDocument();
		boolean b = td.load("hoge.txt");
		assertThat(b, is(true));
	}

}

このテストをパスするようにするには、まじめに実装しないといけない。
TextDocument.java

import java.io.File;

public class TextDocument {
	public boolean load(String path) {
		File f = new File(path);
		if (!f.exists()) return false;
		return true;
	}
}

次に、ファイルの行数を取得するメソッドを作ることにする。
hoge.txt は11行なので、テストコードは以下のようになる。
TextDocumentTest.java

	@Test
	public void testLines() {
		TextDocument td = new TextDocument();
		boolean b = td.load("hoge.txt");
		assertThat(b, is(true));
		int lines = td.getLines();
		assertThat(lines, is(11));
	}

このテストをパスするには、Fakeすればよい。
TextDocument.java

import java.io.File;

public class TextDocument {
	public boolean load(String path) {
		File f = new File(path);
		if (!f.exists()) return false;
		return true;
	}

	public int getLines() {
		return 11;
	}
}

Fakeコードではテストにパスしないようにするために、1行だけのファイル 1.txt を用意し、そのファイルの行数を検査するテストを追加する。
TextDocumentTest.java

	@Test
	public void testLines() {
		TextDocument td = new TextDocument();
		boolean b = td.load("hoge.txt");
		assertThat(b, is(true));
		int lines = td.getLines();
		assertThat(lines, is(11));
	}

	@Test
	public void testLines1() {
		TextDocument td = new TextDocument();
		boolean b = td.load("1.txt");
		assertThat(b, is(true));
		int lines = td.getLines();
		assertThat(lines, is(1));
	}

このテストをパスするために、TextDocumentクラスをきちんと実装する。
java.util.List を使用してコレクションフレームワークの復習をする。
List の要素として、ファイルの各行を追加する。

TextDocument.java

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class TextDocument {
	private List<String> lines;

	public boolean load(String path) {
		lines = new ArrayList<String>();
		File f = new File(path);
		if (!f.exists()) return false;
		try (BufferedReader in = new BufferedReader(new FileReader(path))) {
			while (in.ready()) {
				String s = in.readLine();
				lines.add(s);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return true;
	}

	public int getLines() {
		return lines.size();
	}
}

ファイル読み込みができた人は、以下の機能を追加しましょう!

  1. 指定した行の文字列取得
  2. 指定した行を削除(削除した行の文字列を返す)
  3. 指定した文字列を行として末尾に挿入
  4. 指定した場所に指定した文字列を行として挿入
  5. 出力先ファイル名を指定してファイル出力

上記機能追加ができたら、さらに以下の機能を追加しましょう!

  1. 文字列検索(行番号と、その行の文字列を、Listで返す)
  2. 文字列置換(検索と同じように返す)

指定した行の文字列を取得

まずはテストを書く。

TextDocumentTest.java

	@Test
	public void testGetLine() {
		TextDocument td = new TextDocument();
		boolean b = td.load("hoge.txt");
		String s = td.getLine(0);
		assertThat(s, is("ab"));
	}

TextDocumentにはgetLine()メソッドがないので、Eclipseに作ってもらう。
TextDocument.java

	public String getLine(int i) {
		return null;
	}

テストを実行するとエラーになるので、テストをパスするように “ab”を返すようにFakeする。
TextDocument.java

	public String getLine(int i) {
		return "ab";
	}

テストを追加する。
TextDocumentTest.java

	@Test
	public void testGetLine() {
		TextDocument td = new TextDocument();
		boolean b = td.load("hoge.txt");
		String s = td.getLine(0);
		assertThat(s, is("ab"));
		s = td.getLine(1);
		assertThat(s, is("abc"));
	}

テストを実行するとエラーになるので、テストにパスするようにコードを修正する。
TextDocument.java

	public String getLine(int i) {
		return lines.get(i);
	}

指定した行を削除

まずはテストを書く。
削除した行の文字列を返す。

TextDocumentTest.java

	@Test
	public void testDelete() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		String s = td.deleteLine(0);
		assertThat(s, is("ab"));
	}

EclipseにdeleteLine()メソッドを作ってもらう。
TextDocument.java

	public String deleteLine(int i) {
		return null;
	}

テストを実行してもパスしないので、”ab”を返すようにFakeする。
TextDocument.java

	public String deleteLine(int i) {
		return "ab";
	}

テストを追加する。
1行目を削除したら、もともと2行目だったものが1行目になるはず。
TextDocumentTest.java

	@Test
	public void testDelete() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		String s = td.deleteLine(0);
		assertThat(s, is("ab"));
		s = td.getLine(0);
		assertThat(s, is("abc"));
	}

TextDocumentのdeleteLine()を修正。

TextDocument.java

	public String deleteLine(int i) {
		return lines.remove(i);
	}

指定した文字列を行として末尾に挿入

まずはテストを書く。
1行追加すれば、行数が増えるはず。

TextDocumentTest.java

	@Test
	public void testAdd() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		int lines = td.getLines();
		assertThat(lines, is(10));
		td.addLine("1234567890");
		lines = td.getLines();
		assertThat(lines, is(11));
	}

EclipseにaddLine()を作ってもらう。
テストするとエラーになるので、テストにパスする実装を追加する。
TextDocument.java

	public void addLine(String s) {
		lines.add(s);
	}

指定した場所に指定した文字列を行として挿入

まずはテストを書く。
どこに追加するのかを引数で渡す。
先頭に1行追加すれば、行数が増えるはず。
先頭行は追加した文字列に一致するはず。
2行目は、もともと1行目だった文字列に一致するはず。

TextDocumentTest.java

	@Test
	public void testAdd1() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		int lines = td.getLines();
		assertThat(lines, is(10));
		td.addLine(0, "1234567890");
		lines = td.getLines();
		assertThat(lines, is(11));
		String s = td.getLine(0);
		assertThat(s, is("1234567890"));
		s = td.getLine(1);
		assertThat(s, is("ab"));
	}

6月5日

スレッドの同期

スレッドを同期しないと、値の代入などが期待通りに動かない場合がある。
DojiSample.java では、countに1をプラスすることを2つのスレッドから同時に行うことで、誤った結果となる礼を示している。

DojiSample.java

package c25;

public class DojiSample {
	Integer count = new Integer(1);
	
	void countUp() {
		int tmpValue = count;
		tmpValue = tmpValue + 1;
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		count = tmpValue;
	}
	class CountThread extends Thread {
		public void run() {
			countUp();
			System.out.println(getName() + " count=" + count);
		}
	}
	
	void doSample() {
		new CountThread().start();
		new CountThread().start();
	}

	public static void main(String[] args) {
		new DojiSample().doSample();
	}

}

DojiSample.javaを実行した結果は以下のようになる。

Thread-0 count=2
Thread-1 count=2

count変数に同時に複数のスレッドからアクセスできないようにするために、synchronizedブロックを使用する。
DojiSample.java

	void countUp() {
		synchronized (count) {
			int tmpValue = count;
			tmpValue = tmpValue + 1;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			count = tmpValue;
		}
	}

synchronizedブロック内でカウントアップする処理を行うようにすることで、countがきちんとカウントアップされるようになる。

Thread-0 count=2
Thread-1 count=3

練習問題

テキストp.508の問題3を入力して実行してみる。

Renshu3.java

class BarThread extends Thread {
	StringBuffer sb;
	public BarThread(StringBuffer sb) {
		this.sb = sb;
	}
	public void run() {
		addNextChar();
	}
	void addNextChar() {
		char lastChar = sb.charAt(sb.length() - 1);
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		sb.append((char)(lastChar + 1));
		System.out.println(sb.toString());
	}
}

public class Renshu3 {

	public static void main(String[] args) {
		StringBuffer sb = new StringBuffer();
		sb.append("a");
		for (int i = 0; i < 10; i++) {
			BarThread t = new BarThread(sb);
			t.start();
		}
	}

}

実行結果(例)

abbbb
abbbbbbbbbb
abbbbbbbbb
abbbbbbbbb
abbbbbbb
abbbbbbb
abbbbb
abbbb
abbbb
abbbb

addNextChar() メソッドの内部を synchronized(sb) ブロック内に入れることで、期待通りに動作するようになる。

Renshu3.java

	void addNextChar() {
		synchronized (sb) {
			char lastChar = sb.charAt(sb.length() - 1);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			sb.append((char)(lastChar + 1));
			System.out.println(sb.toString());
		}
	}

出力結果は以下のとおり。

ab
abc
abcd
abcde
abcdef
abcdefg
abcdefgh
abcdefghi
abcdefghij
abcdefghijk

例題

Renshu3.java の出力内容を、hoge.txt に出力する。

mainメソッド内でPrintWriterのインスタンスを生成し、BarThreadのコンストラクタに渡すようにする。
BarThreadでは、受け取ったPrintWriterに対して文字列を出力する。

Renshu3.java

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

class BarThread extends Thread {
	StringBuffer sb;
	PrintWriter out;
	public BarThread(StringBuffer sb, PrintWriter out) {
		this.sb = sb;
		this.out = out;
	}
	public void run() {
		addNextChar();
	}
	void addNextChar() {
		synchronized (sb) {
			char lastChar = sb.charAt(sb.length() - 1);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			sb.append((char)(lastChar + 1));
			System.out.println(sb.toString());
			out.println(sb.toString());
		}
	}
}

public class Renshu3 {

	public static void main(String[] args) throws IOException {
		PrintWriter out = new PrintWriter(new FileWriter("hoge.txt"));
		StringBuffer sb = new StringBuffer();
		sb.append("a");
		for (int i = 0; i < 10; i++) {
			BarThread t = new BarThread(sb, out);
			t.start();
		}
	}
}

ファイルには何も書かれていない。
それは、BarThreadがファイルに書き込む前にmainスレッドが終了し、出力先のファイルがクローズされてしまうため。
mainスレッドの終了を Thread.sleep() で遅延させれば書き込みが行われる。

Renshu3.java

public class Renshu3 {

	public static void main(String[] args) throws IOException {
		PrintWriter out = new PrintWriter(new FileWriter("hoge.txt"));
		StringBuffer sb = new StringBuffer();
		sb.append("a");
		for (int i = 0; i < 10; i++) {
			BarThread t = new BarThread(sb, out);
			t.start();
		}
		try {
			Thread.sleep(2000);
			out.close();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

作成した hoge.txt を読み込むためのクラス TextDocument クラスを作る。

TextDocument.java

public class TextDocument {

}

TextDocumentの単体テストクラスを作る。
パッケージエクスプローラでTextDocumentクラスを右クリックし、[新規]-[JUnitテストケース]を選択する。

TextDocumentTest.java

import static org.junit.Assert.*;

import org.junit.Test;

public class TextDocumentTest {

	@Test
	public void test() {
		fail("まだ実装されていません");
	}
}

6月1日

テスト駆動開発で自動販売機を作る

前回は10円玉の釣り銭を使い果たすテストを用意したところまで。
テストを実行すると失敗するので、テストにパスするよう実装する。

VendingMachineTest.java

 
	@Test
	public void testChangeStock6() {
		VendingMachine vm = new VendingMachine();
		for (int i = 0; i < 3; i++) {
			vm.throwInto(100);
			vm.throwInto(50);
			vm.purchace();
		}
		int s10 = vm.get10YenStock();
		assertThat(s10, is(1));
		vm.throwInto(100);
		vm.throwInto(50);
		Output o = vm.purchace();
		s10 = vm.get10YenStock();
		assertThat(s10, is(1));
		assertThat(o.getJuiceCount(), is(0));
		assertThat(o.getChange(), is(0));
		int total = vm.getTotal();
		assertThat(total, is(150));
	}

お釣りが足りないときは購入できないようにすればいい。ということは、購入可能かどうかを調べているisPurchacable()メソッド内でお釣りが足りるかどうかを確認すればいい。

そこで、お釣りが足りるかどうかを調べる isChangeShorted()メソッドを追加する。お釣りが足りるならfalse、足りないならtrueを返す。

常にfalseを返すように作れば、従来どおりの動作となる。

	public boolean isPurchacable() {
		if (getJuiceStock() == 0) return false;
		if (total < getJuicePrice()) return false;
		if (isChangeShorted()) return false;
		return true;
	}

	private boolean isChangeShorted() {
		return false;
	}

お釣りが50円よりも少ないときは10円玉のお釣りを出す必要がある。
10円玉のストックとお釣りの金額を比較して、釣り銭が足りるかどうかを調べる。

	private boolean isChangeShorted() {
		int change = total - getJuicePrice();
		if (change < 50) {
			if (stock10yen * 10 < change) return true;
		}
		return false;
	}

100円玉の釣り銭が足りなくなるテストを作る。
VendingMachineTest.java

	@Test
	public void testChangeStock7() {
		VendingMachine vm = new VendingMachine();
		for (int i = 0; i < 2; i++) {
			vm.throwInto(500);
			vm.throwInto(10);
			vm.throwInto(10);
			vm.purchace();
		}
		int s100 = vm.get100YenStock();
		assertThat(s100, is(2));
		vm.throwInto(500);
		Output o = vm.purchace();
		s100 = vm.get100YenStock();
		assertThat(s100, is(2));
		assertThat(o.getJuiceCount(), is(0));
		assertThat(o.getChange(), is(0));
	}

10円玉の釣り銭不足と同様の実装を追加する。

VendingMachine.java

	private boolean isChangeShorted() {
		int change = total - getJuicePrice();
		if (change < 50) {
			if (stock10yen * 10 < change) return true;
		}
		if (change < 500) {
			if (stock100yen * 100 < change) return true;
		}
		return false;
	}

これを追加すると、testPurchace1000() が通らなくなってしまった!
テストコードを確認してみると、お釣りのチェックをしなければパスするコードを書いていることがわかる。
このテストでは、ジュースの在庫がなくなるのを確認したいので、お釣りが不要になるようにテストコードを修正する。

VendingMachineTest.java

	@Test
	public void testPurchace1000() {
		VendingMachine vm = new VendingMachine();
		vm.throwInto(1000);
		Output o = vm.purchace();
		assertThat(o.getJuiceCount(), is(1));
		assertThat(o.getChange(), is(880));
		int stock = vm.getJuiceStock();
		assertThat(stock, is(4));
		for (int i = 0; i < 4; i++) {
			//vm.throwInto(500); 500円ではなく、釣り銭が出ない120円で購入
			vm.throwInto(100);
			vm.throwInto(10);
			vm.throwInto(10);
			o = vm.purchace();
		}
		stock = vm.getJuiceStock();
		assertThat(stock, is(0));
		vm.throwInto(500);
		o = vm.purchace();
		assertThat(o.getJuiceCount(), is(0));
		assertThat(o.getChange(), is(0));
		int total = vm.getTotal();
		assertThat(total, is(500));
	}

50円玉の釣り銭不足になるテストを作りたいが、ジュースの在庫が5個しかないのでできない。
500円玉の釣り銭不足も同様。
ここまでで、テスト駆動開発の実習を終わりにする。

VendingMachineTest.java

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;


public class VendingMachineTest {

	@Test
	public void testThrowInto() {
		VendingMachine vm = new VendingMachine();
		int change = vm.throwInto(10);
		assertThat(change, is(0));
		int total = vm.getTotal();
		assertThat(total, is(10));
		change = vm.throwInto(50);
		assertThat(change, is(0));
		total = vm.getTotal();
		assertThat(total, is(60));
	}

	@Test
	public void testThrowIntoNotAccept() {
		VendingMachine vm = new VendingMachine();
		int change = vm.throwInto(1);
		assertThat(change, is(1));
		int total = vm.getTotal();
		assertThat(total, is(0));
		change = vm.throwInto(5);
		assertThat(change, is(5));
		change = vm.throwInto(2000);
		assertThat(change, is(2000));
		change = vm.throwInto(5000);
		assertThat(change, is(5000));
		change = vm.throwInto(10000);
		assertThat(change, is(10000));
	}

	@Test
	public void testThrowIntoRefund() {
		VendingMachine vm = new VendingMachine();
		int change = vm.throwInto(10);
		assertThat(change, is(0));
		int total = vm.getTotal();
		assertThat(total, is(10));
		int refund = vm.refund();
		assertThat(refund, is(10));
		total = vm.getTotal();
		assertThat(total, is(0));
		change = vm.throwInto(100);
		assertThat(change, is(0));
		total = vm.getTotal();
		assertThat(total, is(100));
		change = vm.throwInto(500);
		assertThat(change, is(0));
		total = vm.getTotal();
		assertThat(total, is(600));
		refund = vm.refund();
		assertThat(refund, is(600));
	}

	@Test
	public void testGetJuice() {
		VendingMachine vm = new VendingMachine();
		String name = vm.getJuiceName();
		assertThat(name, is("コーラ"));
		int price = vm.getJuicePrice();
		assertThat(price, is(120));
		int stock = vm.getJuiceStock();
		assertThat(stock, is(5));
	}

	@Test
	public void testIsPurchacable() {
		VendingMachine vm = new VendingMachine();
		boolean b = vm.isPurchacable();
		assertThat(b, is(false));
		vm.throwInto(500);
		b = vm.isPurchacable();
		assertThat(b, is(true));
	}

	@Test
	public void testPurchace() {
		VendingMachine vm = new VendingMachine();
		vm.throwInto(100);
		vm.throwInto(10);
		vm.throwInto(10);
		Output o = vm.purchace();
		assertThat(o.getJuiceCount(), is(1));
		assertThat(o.getChange(), is(0));
		int total = vm.getTotal();
		assertThat(total, is(0));
	}

	@Test
	public void testPurchace500() {
		VendingMachine vm = new VendingMachine();
		vm.throwInto(500);
		Output o = vm.purchace();
		assertThat(o.getJuiceCount(), is(1));
		assertThat(o.getChange(), is(380));
		int total = vm.getTotal();
		assertThat(total, is(0));
	}

	@Test
	public void testPurchace1000() {
		VendingMachine vm = new VendingMachine();
		vm.throwInto(1000);
		Output o = vm.purchace();
		assertThat(o.getJuiceCount(), is(1));
		assertThat(o.getChange(), is(880));
		int stock = vm.getJuiceStock();
		assertThat(stock, is(4));
		for (int i = 0; i < 4; i++) {
			vm.throwInto(100);
			vm.throwInto(10);
			vm.throwInto(10);
			o = vm.purchace();
		}
		stock = vm.getJuiceStock();
		assertThat(stock, is(0));
		vm.throwInto(500);
		o = vm.purchace();
		assertThat(o.getJuiceCount(), is(0));
		assertThat(o.getChange(), is(0));
		int total = vm.getTotal();
		assertThat(total, is(500));
	}

	@Test
	public void testSale() {
		VendingMachine vm = new VendingMachine();
		int sale = vm.getSale();
		assertThat(sale, is(0));
		vm.throwInto(500);
		vm.purchace();
		sale = vm.getSale();
		assertThat(sale, is(120));
	}

	@Test
	public void testChangeStock() {
		VendingMachine vm = new VendingMachine();
		int s10 = vm.get10YenStock();
		assertThat(s10, is(10));
		int s50 = vm.get50YenStock();
		assertThat(s50, is(10));
		int s100 = vm.get100YenStock();
		assertThat(s100, is(10));
		int s500 = vm.get500YenStock();
		assertThat(s500, is(10));
		vm.throwInto(100);
		vm.throwInto(50);
		Output o = vm.purchace();
		assertThat(o.getJuiceCount(), is(1));
		assertThat(o.getChange(), is(30));
		s10 = vm.get10YenStock();
		assertThat(s10, is(7));
	}

	@Test
	public void testChangeStock2() {
		VendingMachine vm = new VendingMachine();
		vm.throwInto(100);
		vm.throwInto(50);
		Output o = vm.purchace();
		assertThat(o.getJuiceCount(), is(1));
		assertThat(o.getChange(), is(30));
		int s10 = vm.get10YenStock();
		assertThat(s10, is(7));

		vm.throwInto(100);
		vm.throwInto(50);
		vm.purchace();
		s10 = vm.get10YenStock();
		assertThat(s10, is(4));
	}

	@Test
	public void testChangeStock3() {
		VendingMachine vm = new VendingMachine();
		vm.throwInto(100);
		vm.throwInto(100);
		vm.purchace();
		int s10 = vm.get10YenStock();
		assertThat(s10, is(7));
		int s50 = vm.get50YenStock();
		assertThat(s50, is(9));
	}

	@Test
	public void testChangeStock4() {
		VendingMachine vm = new VendingMachine();
		vm.throwInto(500);
		vm.purchace();
		int s10 = vm.get10YenStock();
		assertThat(s10, is(7));
		int s50 = vm.get50YenStock();
		assertThat(s50, is(9));
		int s100 = vm.get100YenStock();
		assertThat(s100, is(7));
	}

	@Test
	public void testChangeStock5() {
		VendingMachine vm = new VendingMachine();
		vm.throwInto(1000);
		vm.purchace();
		int s10 = vm.get10YenStock();
		assertThat(s10, is(7));
		int s50 = vm.get50YenStock();
		assertThat(s50, is(9));
		int s100 = vm.get100YenStock();
		assertThat(s100, is(7));
		int s500 = vm.get500YenStock();
		assertThat(s500, is(9));
	}

	@Test
	public void testChangeStock6() {
		VendingMachine vm = new VendingMachine();
		for (int i = 0; i < 3; i++) {
			vm.throwInto(100);
			vm.throwInto(50);
			vm.purchace();
		}
		int s10 = vm.get10YenStock();
		assertThat(s10, is(1));
		vm.throwInto(100);
		vm.throwInto(50);
		Output o = vm.purchace();
		s10 = vm.get10YenStock();
		assertThat(s10, is(1));
		assertThat(o.getJuiceCount(), is(0));
		assertThat(o.getChange(), is(0));
		int total = vm.getTotal();
		assertThat(total, is(150));
	}

	@Test
	public void testChangeStock7() {
		VendingMachine vm = new VendingMachine();
		for (int i = 0; i < 2; i++) {
			vm.throwInto(500);
			vm.throwInto(10);
			vm.throwInto(10);
			vm.purchace();
		}
		int s100 = vm.get100YenStock();
		assertThat(s100, is(2));
		vm.throwInto(500);
		Output o = vm.purchace();
		s100 = vm.get100YenStock();
		assertThat(s100, is(2));
		assertThat(o.getJuiceCount(), is(0));
		assertThat(o.getChange(), is(0));
	}
}

VendingMachine.java


public class VendingMachine {
	private int total = 0;
	private int stock = 5;
	private int sale = 0;
	private int stock10yen = 10;
	private int stock50yen = 10;
	private int stock100yen = 10;
	private int stock500yen = 10;

	public int throwInto(int yen) {
		if (yen == 1) return 1;
		if (yen == 5) return 5;
		if (yen == 2000) return 2000;
		if (yen == 5000) return 5000;
		if (yen == 10000) return 10000;
		total += yen;
		return 0;
	}

	public int getTotal() {
		return total;
	}

	public int refund() {
		int refund = total;
		total = 0;
		return refund;
	}

	public String getJuiceName() {
		return "コーラ";
	}

	public int getJuicePrice() {
		return 120;
	}

	public int getJuiceStock() {
		return stock;
	}

	public boolean isPurchacable() {
		if (getJuiceStock() == 0) return false;
		if (total < getJuicePrice()) return false;
		if (isChangeShorted()) return false;
		return true;
	}

	private boolean isChangeShorted() {
		int change = total - getJuicePrice();
		if (change < 50) {
			if (stock10yen * 10 < change) return true;
		}
		if (change < 500) {
			if (stock100yen * 100 < change) return true;
		}
		return false;
	}

	public Output purchace() {
		if (!isPurchacable()) {
			return new Output(0, 0);
		}
		total -= getJuicePrice();
		int change = total;
		decreaseStock(change);
		total -= change;
		stock--;
		sale += getJuicePrice();
		return new Output(1, change);
	}

	private void decreaseStock(int change) {
		if (change >= 500) {
			change -= 500;
			stock500yen--;
		}
		if (change >= 100) {
			int c = change / 100;
			change -= c * 100;
			stock100yen -= c;
		}
		if (change >= 50) {
			change -= 50;
			stock50yen--;
		}
		stock10yen -= change / 10;
	}

	public int getSale() {
		return sale;
	}

	public int get10YenStock() {
		return stock10yen;
	}

	public int get50YenStock() {
		return stock50yen;
	}

	public int get100YenStock() {
		return stock100yen;
	}

	public int get500YenStock() {
		return stock500yen;
	}
}

スレッド

スレッドは、並行処理を行う仕組み。
Javaでは言語仕様で並行処理をサポートしている。

Threadクラスを継承してクラスを作成する。そのクラスのインスタンスを生成し、start() メソッドを呼ぶとスレッドが起動される。スレッドが起動されると、run() メソッドが呼び出される。

Threadを使ったサンプル

MyThread.java

public class MyThread extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println("Threadの実行中" + i);
		}
	}
}

MyThreadTest.java

public class MyThreadTest {

	public static void main(String[] args) {
		Thread t = new MyThread();
		t.start();
		for (int i = 0; i < 5; i++) {
			System.out.println("mainの実行中" + i);
		}
	}

}

Runnableインタフェースを使用する方法もある。

MyRunnable.java

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println("Runnableの実行中" + i);
		}
	}
}

MyRunnableTest.java

public class MyRunnableTest {
	public static void main(String[] args) {
		Runnable r = new MyRunnable();
		Thread t = new Thread(r);
		t.start();
		for (int i = 0; i < 5; i++) {
			System.out.println("mainの実行中" + i);
		}
	}
}

スレッドのライフサイクル

スレッドのライフサイクルは重要!(試験問題に出す!)

SleepThread.java

public class SleepThread extends Thread {
	@Override
	public void run() {
		try {
			System.out.println(getName() + " sleep前");
			Thread.sleep(3000);
			System.out.println(getName() + " sleep後");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

SleepThreadTest.java

public class SleepThreadTest {

	public static void main(String[] args) {
		SleepThread t = new SleepThread();
		t.start();
		System.out.println("mainのEND");
	}

}

複数のスレッドを同時に起動するサンプル。
SleepThreadTest2.java

public class SleepThreadTest2 {

	public static void main(String[] args) {
		for (int i = 0; i < 3; i++) {
			SleepThread t = new SleepThread();
			t.start();
		}
		System.out.println("mainのEND");
	}

}