次は入力データチェックと繰り返し処理が必要な課題に進みます。
まず次の課題として作るプログラムの機能から説明します。
テキストボックスがあり、数字を入力する(要データチェック)。
入力した数字の文だけ空テーブルとリンクを表示する。
リンクをクリックすると、テーブルの対応する場所の色が変わる。
別のリンクをクリックすると、元の場所の色は戻り、対応する場所の色が変わる。
テキストボックスに数字以外を入力したり、妥当でない数(0以下や大きすぎる数、上限は各自設定する)はメッセージを表示する。
というものです。
具体的な画面イメージは次の通りです。
・画面イメージ
この課題を実現するためには
・入力データが数字か否か、という判定
・入力データの数字(文字列)を数値に変換
という処理が必要になります。クライアントから送られてくるのは全て文字列(String )ですのでこれを数値に変換する、例えば "7" という文字列を 7 という数値に変換する必要があります。
このチェック方法には2種類あり、それはJava SDKのバージョンに依存します。Java2 SDK SE1.4.x以降のバージョンだとその2種類から選べますが、Java2 SDK SE1.3.x以前だと、最初に述べる方法でしか出来ません(Java標準機能のみを使う場合)。
それではその方法について説明するのですが、先に演算子について説明しておきましょう。
Javaで使える演算子のうち、基本的なものを例示します。
種別 演算子 例 内容説明
多項演算子
+
A + B
加算。 A、Bが文字列のときは、文字列の連結になる。
"AAA" + "BBB" は "AAABBB" になる。
-
A - B
減算。
*
A * B
乗算(A×B)。
⁄
A ⁄ B
除算(A÷B)。整数のとき、結果は切り捨て。
7 ⁄ 3 は 2 になる。
%
A % B
剰余。A÷Bの余り。7 % 3 は 1 になる。
+=
A += B
加算の省略形。A = A + B と同じ意味。
-=
A -= B
減算の省略形。A = A - B と同じ意味。
*=
A *= B
乗算の省略形。A = A * B と同じ意味。
⁄=
A ⁄= B
除算の省略形。A = A ⁄ B と同じ意味。
<<
A << B
整数型に対する左シフト。
下位ビットは0で埋められる。
ASMのSLA命令と同じ。
>>
A >> B
整数型に対する右シフト。
上位ビットは符号ビットで埋められる。
ASMのSRA命令と同じ。
単項演算子
++
A++
参照後増分。
A = 3;
B = A++;
とすると、B は 3 、A は 4 になる。
char 型に対しても使用可。'a'なら'b'、'C'なら'D'になる。
A = 'D';
B = A++;
とすると、B は 'D' 、A は 'E' になる。
++ A
増分後参照。
A = 3;
B = ++A;
とすると、B は 4 、A も 4 になる。
char 型に対しても使用可。
--
A--
参照後減分。
A = 3;
B = A--;
とすると、B は 3 、A は 2 になる。
char 型に対しても使用可。'e'なら'd'、'C'なら'B'になる。
A = 'D';
B = A--;
とすると、B は 'D' 、A は 'C' になる。
-- A
減分後参照。
A = 3;
B = --A;
とすると、B は 2 、A も 2 になる。
char 型に対しても使用可。
※ 多項演算子の省略形(+=、-=、*=、/=)や単項演算子(++、--)は間違いのもと(自分が作る場合と後から直す場合、他人が見る場合)になりやすいので、なるべく使わないほうがいいでしょう。確かに短く書けますが、たかが数文字短いからってどうってことないし。
唯一 ++ に関しては、 for 文の増分指定に使うくらいです。
最初の、Javaのバージョンに関わらず可能な方法は、文字列から数値への変換と数字チェックを同時に行う方法です。
それには java.lang パッケージの Integer クラスにある parseInt メソッドを使います。
書き方は、 Integer.parseInt( 文字列) か、 Integer.parseInt( 文字列, 基数) です。このメソッドの戻り値は int 型です。
最初の書き方は指定した文字列が10進数を表す文字列になっているかチェックし、なっていれば int 型の数値に変換した値を返します。
2番目の書き方は指定した文字列が基数で表された進数を表す文字列になっているかチェックし、なっていれば int 型の数値に変換した値を返します。
基数は int 型で指定し、2進から36進まで指定出来ますが通常使うのは2、8、10、16のいずれかでしょう。もし基数に16が指定してあれば、文字列が16進数を表すものになっているかチェックし、なっていれば int 型の数値に変換した値を返します。
なお戻り値が int 型であるということは符号付ということです。従って文字列の先頭に負を表す - を指定することもできます。
では文字列が数字を表す文字列になっていない場合、あるいは int 型の範囲を越えた数字だった場合にどうなるかというと、 NumberFormatException という例外が発生します。
この例外を拾うことによって非数字のチェックも出来る、というわけです。
・例外処理
例外というのは実行時エラーと思ってください。まず例外処理の書き方を説明すると
try
{
文;
文;
}
catch ( 補足する例外 補足した例外を表す変数)
{
文;
文;
}
catch ( 補足する例外 補足した例外を表す変数)
{
文;
文;
}
もしくは
try
{
文;
文;
}
catch ( 補足する例外 補足した例外を表す変数)
{
文;
文;
}
finally
{
文;
文;
}
です。
catch は自分が補足したい例外の分だけ、いくつ書いても構いません。() の中の意味ですが、「補足する例外型の変数」を定義し引数に指定する、と捉えてください。この変数の有効範囲はその catch のブロックの中(catch の直後の { から対応する } まで)だけです。
これの動作ですが、まず try ブロックの中の文が実行されます。そのブロック内実行中に例外が発生し、その例外が try に対応する catch で定義されているものがあるとき、その catch ブロックが実行される、というものです。その例外を定義している catch が一つも無い場合は、Java実行環境で異常終了扱いになります。
例外が起きるとそれが起きた文で実行が中断されます。 catch が定義されていれば、その文を中断してすぐに catch ブロックに制御が移ります。
この catch の宣言が例えば catch ( 補足する例外 wl_excp) となっているとき、その catch ブロック内で wl_excp.getMessage() とすると、発生した例外に関するエラーメッセージが String 型で取得できます。
それから補足する例外に Exception と書いておくと、起きた例外全て、の意味になります。
二つ目の書き方の finally ブロックですが、これがあると try ブロック内で例外の有無に関わらず必ず実行されます。
例外が起きないときは try ブロック実行後に finally ブロックが実行されます。
例外が起きてかつその例外を定義している catch ブロックがあるときは、その catch ブロック実行後に finally ブロックが実行されます。
例外が起きてかつその例外を定義している catch ブロックがないときは、すぐに finally ブロックが実行されます。
ここで説明中の数値への変換を例にすると少なくとも
try
{
数値型変数 = Integer.parseInt( 入力文字列);
}
catch (NumberFormatException wl_excp)
{
⁄⁄ 数字エラーの処理
}
のような部分がプログラム中に必要、ということになります。
以上が文字列から数値への変換と数字チェックを同時に行う方法です。
Javaはこのように例外処理を統一的に体系化された方法で取り扱うことができます。そのためチェックについても例外処理で取り扱うのが普通なのかもしれません。
しかし私のようなタイプ(旧いタイプ)だと、例外はあくまで例外なので(実行時エラーだから)、それが起きるという状況そのものがあまり気に入りません。どうも「事前にチェックして例外は起きないようにしたい」という意識があります。
では数字チェックについて例外を使わないときはどうするか、というのが次に示す二つ目の方法です。
この方法はJavaのバージョンに依存し、Java2 SDK SE1.4.x以降でないとJava標準機能だけでは使えません。1.4から正規表現に関するクラスとメソッドが追加されたからです。
正規表現というのは文字列に対して、それがどんなパターンで構成されているかを表すためのパターン、と考えてください。正規表現パターン自体も文字列で表します。
例えば'A'で始まり任意の文字が1文字以上あって'Z'で終わる文字列を表す正規表現パターン、は"A.+Z"で表せます。これに似たもので、'A'で始まり'Z'で終わる文字列を表す正規表現パターン、は"A.*Z"となります。前者と後者の違いは"AZ"という文字列がパターンに合うか合わないか、という違いです(前者のパターンには合いませんが、後者のパターンには合います)。
正規表現の詳細な構文についてはAPIドキュメントのjava.util.regexパッケージに含まれるPatternクラスの説明を参考にしてください。
正規表現によるパターンマッチの仕方ですが、標準的な手順と簡易な手順があります。ここでは簡易な手順から説明していきます。
簡易な手順の書き方は本当に簡単で、 Pattern クラスの matches メソッドを使います。
書き方は
Pattern.matches( 正規表現パターン文字列, 検査対象文字列)
となります。
簡易な書き方には 文字列.matches( 正規表現パターン文字列) というのもあるのですが、説明の展開上ここでは Pattern.matches を使います。
この matches メソッドの戻り値は boolean 型ですので、 if や switch の条件式にそのまま書いたり、 boolean 型の変数に入れておいて後で判定に使う、といったことが出来ます。
標準的な手続きだと次のようになります。
Pattern 型の変数 = Pattern.compile( 正規表現パターン文字列);
Matcher 型の変数 = Pattern 型の変数.matcher( 検査対象文字列);
Matcher 型の変数.matches()
最後の matches メソッドの戻り値は簡易な手順と同じで boolean 型です。
簡易型との違いは、事前に正規表現パターンをコンパイルしておく、という点です。1プログラム中で同じ正規表現パターンを何度も使う場合は、正規表現パターンを一度だけコンパイルすればすむので効率的です(後の方で説明する、クラスのコンストラクタメソッド内で一度だけコンパイルするなど)。
今回の課題の場合、この正規表現によるパターンマッチで数字チェック後に Integer.parseInt( 文字列) で実際の数値に変換、という手順をとるわけです。
今回の課題について細かい部分の違いを述べると、
・parseInt メソッドだけでチェックする場合
変換結果が - の場合もありうるので、別途これをチェックする必要がある。
・正規表現パターンで数字チェックをする場合
0から9の文字だけを有効にできるので、変換結果が - になることはない。
ただし - も非数字エラーになるのでメッセージを分けられない(これについては先頭1文字だけに '- ' があるか、という正規表現パターンを作って別途チェックすればいいんですが)。
というものがあります。
それからコーディング上のもう一つの違いが。
parseInt は Integer クラスのメソッドで、これは java.lang パッケージに含まれています。このパッケージとクラスはJava標準で組み込まれるので、特に何もしなくてもこの配下のクラスとメソッドは使えます。
これに対して正規表現に関するもの( Macher クラスと Pattern クラス、およびその配下のメソッド)は、 java.util.regex パッケージに含まれています。これは標準では組み込まれないため、次に説明する import により明示的に取り込む必要があります。
import とは標準で組み込まれるもの(使用可能なもの)以外の、Javaが標準で持っている機能(パッケージやクラス)、あるいは外部から持ってきた機能を取り込むための命令です。パッケージというのは似たようなデータ構造あるいは機能を持つクラスをまとめたもの、と考えてください。
C言語を知っている人にとっては import はCのincludeと同じものに思えるかもしれません。しかし両者は似て非なるものです。
まずパッケージとは関連性のある、あるいは似たようなクラスを固めたもの、と思ってください。
実はJavaでは標準組み込み以外のクラスやメソッドを使うとき、インポートしなくても使えるのです。
先ほど説明した matches メソッドを例にとりましょう。
通常は java.util.regex.Pattern クラスをインポートします。そうすることにより Pattern.matches( 正規表現パターン, 検査文字列) という書き方が出来ます。
そのかわりインポートせずに java.util.regex.Pattern.matches( 正規表現パターン, 検査文字列) と、パッケージ名からフルにメソッドを指定してもいいのです。
しかし全てのメソッド記述をこう書いたのでは煩わしいし見にくいので、やはり必要なものはインポートしましょう。
・書き方と場所
import の書き方はJSPとサーブレットで若干異なります。今はJSP編なので、ここではJSPの場合だけ説明します。
まず場所ですがプログラムの一番先頭です。JSPの場合、日本語文字化け対策として先頭に
<%@ page
contentType="text/html; charset=Shift_JIS"
session = "false"
%>
と書いていると思います。この <%@ page から %> の間に書いてください。
書き方は
import = " パッケージ名. クラス名"
です。
あるいは同じパッケージ内の複数のクラスをインポートするのであれば、* で一括指定して
import = " パッケージ名. *"
と書くことも可能です。
ここで注意ですが* で一括指定できるのはクラスだけ です。
例えば日付や時刻に関することを扱うために Calender クラスというのがあります。これは java.util パッケージに含まれています。そして今ここで説明していた正規表現に関するクラスは java.util.regex パッケージに含まれています。
両者を比べるとパッケージ名のうち java.util までは同じです。だからといって両方のクラスを使うための import に、 import = "java.util.*" や import = "java.util.*.*" と書くことはできません (前者は書くことは出来ますが、 java.util.regex パッケージ下のクラスはインポートされません。後者の書き方は文法上できません)。 * で一括指定している部分に正規表現のクラスを含む パッケージの一部(regex の部分)まで含んでいるためです。
この場合には
import = "java.util.*"
import = "java.util.regex.*"
あるいは
import = "java.util.Calender"
import = "java.util.regex.Pattern"
とそれぞれの import を書く必要があります。
import は使うクラスの宣言と思ってください。指定したクラスをソースレベルや実行モジュールレベルで取り込むものではありあません。
従って * 指定であるパッケージの全てのクラスを取り込んだからといって、それによって実行ファイルサイズが大きくなる、ということはありません。
■正規表現パターンチェック用JSP
さて課題に対するこの解法では正規表現パターンを使うのですが、慣れるまではどんな正規表現パターンを使えば自分が意図したものになるか、意外と簡単に行かないと思います。
そこで課題の前に正規表現パターンチェック用JSPを作っておきましょう。後々でも使える簡単なツールになると思います。
それともう一つ。
この次の課題からはサーブレット編に移ろうと思っています。それを踏まえて新しいメソッドを導入しておきましょう。
それは out.print メソッドおよび out.println メソッドです。これはデータ出力なんですが、 print と println の違いは最後に改行コードが入る( println )か入らない( print )かの違いです。ブラウザで「ソースの表示」をして出力(送信)したHTMLを確認したいときは、改行されている分 println のほうが見やすいです
書き方は
out.print( 文字列); や out.println( 文字列);
です。
毎度のことですが文字列の部分は定数、変数、メソッド、結果が文字列になるものなら何でも書けます。
前にも説明しましたが文字列定数を書く場合で文字列中に " を含めたい場合は \" と、エスケープ記号の \ を忘れないようにしてください。\ 自体を含めたい場合は \\ となります。
JSPの場合は out.print とすると出力先は標準でクライアント、つまりブラウザに出力データが送られます。
JSPのメリットとして <% と %> で囲むことによってHTMLとJavaソースを混在できる、というのありました。しかしHTMLのある部分を行単位に、あるいはある行の部分単位にJavaソースで条件分岐により制御したいとき、 <% と %> が頻繁に現れてソースが見にくくなります。
こうしたときJSPでも文字列変数と out.print をうまく使ってあげると、すっきりしたソースになると思います。
それと out.print の変わりに System.out.print を使った場合の説明もしておきます。
System.out.print とすると「Java実行環境における標準出力」に出力されます。TOMCATの場合は %CATALINA_HOME%logs\stdout.log がそれで、ここに内容が出力されます。
実行中に意図せぬ例外が起きたときなど、クライアントにはそのメッセージが出力されて途中経過までのHTMLが出力されないことがあります。こうしたとき、デバッグ用トレースを入れたいときなどは System.out.print を入れてstdout.logを見るようにしたほうがいいでしょう。
この out は PrintWriter クラスのインスタンスです。これに対して print や println メソッドを発行する、というのが正確な言い方です。そして print や println メソッドを発行しても、すぐに結果が出力されるわけではなく、一度バッファに溜まります。そのため途中で意図しない例外が起きているのでデバッグのためにトレース情報を出力したい場合、バッファから実際に出力される前にバッファがクリアされてトレース情報が出ないことがあります。
そうしたときに便利なのが flush メソッドです。 out.print の後に out.flush(); としてあげると、その場でバッファから実際に出力されます。そのためデバッグ時に、トレース情報を取りたい場合に使うと便利なことがあります。
なおこれは System.out.flush(); も有効です。
参考までに正規表現パターンチェック用JSPのサンプルを提示しておきます。ソース名はreg_chk.jspになります。
<%@ page
contentType="text/html; charset=Shift_JIS"
session = "false"
import = "java.util.regex.*"
%>
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head>
<title> 正規表現マッチングテスト </title>
</head>
<body bgcolor="#aaddaa">
<form method="get" action="reg_chk.jsp">
<h3> 正規表現マッチングテスト </H3>
<%
String w_src_data = null;
String w_chk_ptn = null;
w_src_data = request.getParameter("src_data");
w_chk_ptn = request.getParameter("chk_ptn");
if ((w_src_data == null) || (w_chk_ptn == null))
{
%>
<p>
検査文字列 : <input type="text" name="src_data" size="50">
<br />
正規表現パターン : <input type="text" name="chk_ptn" size="30">
<br />
</p>
<input type="submit" value="送信">
<br /><hr /><br />
<%
}
else
{
%>
<p>
検査文字列 : <input type="text" name="src_data" size="50"
value=<%= w_src_data %> >
<br />
正規表現パターン : <input type="text" name="chk_ptn" size="30"
value=<%= w_chk_ptn %> >
<br />
</p>
<input type="submit" value="送信">
<br /><hr /><br />
<%
if (w_chk_ptn.equals(""))
{
out.print("正規表現パターンがありません <br />");
}
else
{
if (Pattern.matches(w_chk_ptn, w_src_data))
{
out.print("パターンにマッチしました<br />");
}
else
{
out.print("<font color=\"red\">パターンにマッチしません<br /></font>");
}
}
}
%>
<br />
</body>
</html>
データチェックの方法について二通りの方法を説明しました。続いて課題を解決するために新しい制御構造の繰り返し処理を説明します。
繰り返し処理の書き方には大きく分けて二つの方法があります。
◇for文
一つ目は for 文で、これはCやVBのfor文、COBOLのPERFORM VARYINGに対応するものです。 for 文の書き方は
for ( 変数の初期設定; 繰り返し条件; 変数の増分または減分の式)
{
⁄⁄ 繰り返し処理の内容
文;
文;
}
となります。
変数の初期設定は、繰り返し判定や繰り返し処理内で使う変数の初期値設定です。ここで変数定義をして同時に初期値設定をすることもでき、その場合の変数の有効範囲は for 文のブロック内だけです。
繰り返し条件は条件式で、これが条件を満たしている( true )間、繰り返し処理が実行されます。
変数の増分または減分は繰り返しの度に行う、通常は初期設定した変数の変更式を記述します。
例えばnの階乗、n!を求める処理なら
int w_rslt;
int w_ix;
int w_rslt = 1;
for ( w_ix = 1; w_ix <= w_n; w_ix = w_ix + 1)
{
w_rslt = w_rslt * w_ix;
}
となります。もしくは省略形を使って
for ( w_ix = 1; w_ix <= w_n; w_ix++)
{
w_rslt *= w_ix;
}
とも書けます。
この繰り返しで使う変数に char 型の変数を使って'a'から'z'まで順にとか、'z'、'x'、'v'というように'z'から一つ置きに変えながら、ということも出来ます。
◇while文とdo文
二つ目は while 文で、これはVBのDo〜Loop文、COBOLのPERFORM UNTILに対応するものです。
while 文と do 文の関係は繰り返しの終了判定を繰り返し処理の前でするか、後でするかの違いです。COBOLのWITH TEST BEFOREとWITH TEST AFTERの違い、VBでのDo While〜LoopとDo〜Loop Whileの違いと同じ意味です。
条件判定は常に while 条件、この条件を満たしている( true )間繰り返す、という書き方になります。書き方は条件判定の位置によって、
while ( 繰り返し条件)
{
⁄⁄ 繰り返し処理の内容
文;
文;
}
もしくは
do
{
⁄⁄ 繰り返し処理の内容
文;
文;
} while ( 繰り返し条件);
となります。
最初の書き方の場合、最初から繰り返し条件が偽( false )の場合は繰り返しの中身は一度も実行されませんが、後の do 文の場合は少なくとも1回は繰り返しの中身が実行されます。
◇break文とcotinue文
これらはどちらも繰り返し処理を途中で中断するためのものです。
break; 文があると繰り返し処理はそこで中断され、制御は繰り返しブロックを抜けて次の文に移ります。
continue; 文があるとその回の繰り返し処理はそこで中断され、制御は次の回の繰り返しの判定に移ります。
for 文であれば変数の増分または減分が行われ、繰り返し条件が判定されます。
while 文か do 文であればすぐに繰り返し条件判定に移り、次の繰り返しに入るか繰り返しブロックを抜けるかが判定されます。
それでは以上を踏まえて正規表現によるデータチェックを行うパターンの、課題にチャレンジしてみてください。
ポイントになるのは
・初期表示
・数字を入力して送信ボタンを押したときの応答表示
・リンクをクリックしたときの応答表示
をどう切り分けるか、どんなパラメータを引き渡せば良いか、という点だと思います。
サンプル では指定できる数字は1〜30にしてあります。
◇色指定チェック用JSP
もう一つJSP編の最後にツールを作っておきましょう。背景や文字の色を指定するのに赤(R)緑(G)青(B)ごとに数値指定で行う方法があります。
このときに自分が指定した色が、実際にどのような色になるのか、それを確認する画面を作っておきましょう。画面イメージは次の通りです。
今度の入力チェックは、HTMLの文字指定は16進数なので16進数を表す文字だけか、というチェックになります。基本的には先ほどの課題と同じですね。10進文字と16進文字の違いだけになると思います。
これのサンプル です。各色の入力は2桁必須にしています。