■初めてのJava
それでは生まれて初めてのJava言語を、JSP環境で勉強していきましょう。
ごくごく簡単な例題から初めて、ちょっとずつ機能追加していきながらそれに合わせて構文や命令等の説明をしていきます。
「全体の構成」で説明したようにJSPはHTMLの中にJavaソースを記述していきます。
そうしたときにHTMLタグ、表示文字、Javaソースの区別がつきません。そのためJavaソースの部分はそれであることを示すタグで囲みます。Java部分の開始を <% タグ、終了を %> タグで囲みます。
つまり
<%
Javaソース
%>
という形になります。
これが便利なのはJava言語の構造がブロックを越えて有効である、という点です。後の方で詳しい説明はしますが例えばif 文を例にすると
HTML部分
<%
if (条件式)
{
%>
条件式を満たす(Then側)のときのHTML部分
<%
}
else (条件式)
{
%>
条件式を満たさない(Else側)のときのHTML部分
<%
}
%>
HTML部分
ということが可能になります。
またこれに似たものに表示文字中の一部に変数の内容や命令の処理結果を表示 させるときのタグがあります。書き方は <%= タグと %> タグで囲み、囲んだ中に変数名や式を書く、というものです。
例えば
受けたデータは <%= 式 %> です。
といった具合です。もしも式に変数名を指定し、その変数に"受信データ1"という値が入っていればこの1行は実際には
受けたデータは 受信データ1 です。
と表示されます。
それでは実際のJavaプログラミングとして「たった1行だけのJavaプログラム」から始めましょう。
実現する機能は
クライアントから送信されたデータを受けてそのまま表示する
です。
これに必要なJavaの命令は request.getParameter("パラメータ名") です。
パラメータ名はHTMLで定義した入力エリアのname= に指定したものです。それの値を受け取る、という意味になります。これを先ほどのタグと組み合わせ、 <%= request.getParameter("パラメータ名") %> という部分がHTML中のどこかにあればいいわけです。
ここで request.getParameter("パラメータ名") を命令と書きましたが、厳密にはこれは request クラスの getParameter メソッドを使う、という意味になります。ここでメソッドとは、について軽く触れます。
・メソッド
メソッドとはこの段階では関数に似たようなもの、と思ってください(ちょっと乱暴ですが)。
VBやC、Pascalなどの経験者にとってはメソッドや関数は馴染み深い言葉だと思います。COBOLやASMしか経験が無い場合、これらの言語には厳密には関数の概念が無いので簡単に説明しておきましょう。
まず関数の概念から行きましょう。
関数とは文字通り数学で出てくる関数と同じものです。式の中で関数を書くと、その関数の部分は評価されると結果の値に変わります。そして結果の値を使って式の評価(計算)が行われる、という仕組みです。
例えば f(x) = 2x + 1 と関数を定義したとき、f(x) は x が引数となり、引数を2倍して1を足した値を返す関数、という意味になります。
従って 3 + 4 - f(2) という式があるとまず f(2) の部分が評価されて、これは f(2) = 2 * 2 + 1 = 5 という値になります。すると先の式は 3 + 4 - f(2) = 3 + 4 - 5 = 2 という結果になります。
今度は同時に g(x) = x + 1 という関数を定義しておき、 f(x) の引数に入れ子で関数を指定することもできます。例えば f(g(3)) とするとまず g(3) という関数が評価されて、 f(g(3)) = f(3 + 1) = f(4) = 4 * 2 + 1 = 9 という値になります。
メソッドは関数と同様、式の中で評価されて結果の値に変わります。引数を必要とするメソッドの場合も関数と同様、引数に別のメソッドを記述することもできます。
・名前と場所
JSPソースのファイル名は拡張子を .jsp にして、JSP名.jsp としてください。このJSP名がブラウザのアドレスバーに指定する名前になります。
ファイルの格納場所ですが「TOMCAT インストール・設定 」を元に話を進めますので、
%CATALINA_HOME%\webapps\TestAp の下になります。ブラウザのアドレスバーでは http://localhost:8080/TestAp/JSP名.jsp と入力するようになります。
・画面イメージ
それでは下の左のような画面を作り、データを入力して送信ボタンを押すと右のような画面になるJSPを作ってみましょう。
なお、このとき <form> タグの method= には get でも post でも、どちらを指定しても構いません(両方試してみましょう)。JSPの場合は request.getParameter でどちらの場合でも値を取得できます。
※文字化けしたら
日本語が文字化けしていて、手動でブラウザの文字エンコード設定を日本語に変えてあげないと正しく表示できない場合、作成した .jspファイルの一番最初に次の3行を入れてください。
<%@ page
contentType="text/html; charset=Shift_JIS"
%>
なお一番最初の初期状態では下図のように
null が表示されているかと思いますが、今は気にしないでください。後ほど説明します。
テキストボックスを使ったJSPが出来たら、次はラジオボタンを使った場合も試してみましょう(チェックボックスは複数選択可能になるので若干プログラムを変える必要があります。これについてはもう少し後で説明します)。
データ入力 実行結果
選択したのは です。
=送信=>
選択したのは 二番目 です。
それから送信ボタンを設けずに、画面にいくつかリンクを張り、選んだリンク個所によって受けるデータが変わるものも試してみましょう。
データ入力 実行結果
一番目
二番目
三番目
選択したのは です。
=送信=>
一番目
二番目
三番目
選択したのは 二番目 です。
ラジオボタンを使った場合の
サンプル を載せておきます。
文字化け対策として.jspファイルの一番先頭に次の3行を入れておく、という話をしました。
ここ以降では文字化けの有無に関わらず常にもう1行、 session = "false" という行を追加することにします。
<%@ page
contentType="text/html; charset=Shift_JIS"
session = "false"
%>
としてください。
この追加した行の詳しい意味は「Java超入門編」の続編で説明します。
簡単に説明すると汎用機のオンラインで言う会話型トランザクションをJavaで実現するためには、セッションオブジェクトというものを使います。そしてJSPのデフォルトではこれを生成するようになっています。しかしこの「Java超入門編」ではこの会話型は範囲外です。一般にプログラミングにおいて使わないメモリ等の領域は確保せず、使わないオブジェクトも生成しない、というのが基本です。ようするに無駄なことはしない、ということですね。そのため「Java超入門編」では使わない(無駄な)、セッションオブジェクトというものを生成しないのがこの追加した行の意味になります。
ここから先はいよいよもっとJavaっぽいプログラムを書いていくんですが、例によって一番大事なこととして、真っ先に注釈(コメント)の書き方を説明します。
Javaの注釈には行単位の注釈と、範囲指定の注釈の2種類あります。
・行単位の注釈
行単位の場合は ⁄ を使います。二つ続けて ⁄⁄ としてやると、これ以降行の最後までが注釈になります。
命令文の後ろに ⁄⁄ を書いて、一緒に注釈を記述したりするときに使います。
・範囲指定の注釈
⁄* で始まり *⁄ で終わるまでが注釈になります。行を跨って複数行に渡る注釈を書くこともできます。プログラムやメソッド、関数の先頭などに説明文を記述したり、デバッグ中などある範囲をまとめて無効にしたりするときに使います。
次に条件分岐を入れてみましょう。今までのJSPだとブラウザのアドレスバーで指定して初期表示したときに、線の下には 受けたデータは null です。 というように null という文字が表示されていました。
これを無くして初期表示のときは線の下には何も文字が出ないようにしましょう。画面イメージは次の通りです。
さてこれを実現するためには表示要求が送信ボタンによるものか(応答表示)、アドレスバー等でURLを指定されたのか(初期表示)を判定する必要があります。
まず判定処理の一つである
if の書き方(基本)について説明します。
◇If文の書き方
基本的な書き方は
if ( 条件式) 文; else 文;
です。これは当然ながら改行して
if ( 条件式)
文;
else
文;
と書いてもかまいません。
この書き方で注意が要るのはthen側、else側とも文は1行しか書けないということです。複数の文を書きたい場合は { と } で囲んで、ブロック化させる必要があります。これはif に限らず、後から出てくるfor やswitch など、Java一般の規則です。
また、各文の最後は必ず ; を付けてください。
ブロック化したif を書いてみると
if ( 条件式)
{
文;
文;
}
else
{
文;
文;
}
となります。
整理すると文が1行のときは { と } で囲む必要は無いが、複数あるときは { と } で囲んでブロック化する、です。
ですがたとえ文が1行でも、必ずブロック化するようにしたほうがいいかもしれません。
※
{ } の付け方
Javaは文法上C言語に大きく影響を受けてます。Cもそうなんですが、特にこの
{ と
} の付け方については宗教論争のようにスタイルについての論争があります(どのように付けるべきかについて)。結局はどれでもいいんですけど、個人の好き嫌いで。少なくとも自分が書いたプログラムについて統一されてればどれでもいいんです。
割と多いのがK&Rスタイルです。その昔C言語を勉強するひとは誰でも一度は目を通したであろうカーニハンとリッチー(C言語の開発者)が書いた本があって、それに出ていたスタイルです。
If文を例に説明すると
if (条件式) {
thenの処理;
}
else {
elseの処理;
}
という書き方です。
別のよくあるスタイル(ベル研スタイルだったかな? ちょっと覚えてません)だとこうなります。ちなみにSunが出してて、日本だとアスキーから出版されている「Core Java Vol.1 基礎編」、「Core Java Vol.2 応用編」という本もこのスタイルです。
if (条件式)
{
thenの処理;
}
else
{
elseの処理;
}
という書き方です。
ここの一連のページではGNUスタイルを採用しています。
if (条件式)
{
thenの処理;
}
else
{
elseの処理;
}
という書き方です。
要するに
{ } と字下げの関係で、どう書くとブロックの構造が分かりやすいか、という問題です。外側ブロックとその
{ } 、内側ブロックの字下げとの関係ですね。
「なぜこう書いてるの?」と聞かれたら、「GNU(グニューと発音)スタイルの真似です」って答えましょう。それで相手がGNUが何か知らなかったら・・・ お手上げですが・・・
条件式は結果が論理値(boolean)つまり真(true)か偽(false)になるものであれば、何でもOK です。
比較演算子を使った条件式、boolean型の変数、trueかfalseの定数だけでなく、戻り値としてboolean型を返す関数やメソッド、およびそれらを論理演算子でつないだ論理式、なんでもありです。
種別 演算子 例 内容説明
比較演算子
==
A == B
等しい。AとBが等しい。
<
A < B
AはBより小さい。
<=
A <= B
未満。AはBより小さいか等しい。
>
A > B
AはBより大きい。
>=
A >= B
以上。AはBより大きいか等しい。
!=
A != B
等しくない。AとBは等しくない。
論理演算子
&&
(A) && (B)
かつ。AかつBである(A、Bは条件式であること)
||
(A) || (B)
または。AまたはBである(A、Bは条件式であること)
!
! (A)
否定。Aでない(Aは条件式であること)
※ 論理演算子で条件式をつなぐとき、各条件式は必ず ( と ) で括ってください(そうしたほうが後々見やすいし間違いもないと思う)。
さてこの説明を見ると初期状態の判定は、「request.getParameter("パラメータ名") の値を == で何かと判定すればいいんじゃないか?」と思うかもしれません。
request.getParameter("パラメータ名") の意味をもう一度繰り返しておくと、"パラメータ名"で指定したパラメータの値を受け取る、です。HTML編の「入力画面(送信) 」で説明しましたが、クライアントからサーバーにデータを送る方法には post と get という二種類の方法があります。このうち get の方が説明しやすいので、これで説明します(原理は post も同じです。仕組み上 get の方が理解もしやすいと考えました)。
get の場合は送信データのパラメータ名とパラメータ値は、送信先の(次に表示される)URLに含まれます。画面で送信ボタンを押すと、 送信先URL?Text1=SendData のように送信されるわけです。それに対し request.getParameter("Text1") とすると、"Text1"の値である"SendData"が受け取れるわけです。
さて初期表示、つまりブラウザのアドレスバーで http://localhost:8080/TestAp/JSP名.jsp と指定するということは、この ? 以降のパラメータが存在しません。この状態をJavaでチェックできればいいのです。存在しないパラメータ名に対して request.getParameter("パラメータ名") を実行すると、結果は null という特別な値が返ります。ですからこれをチェックしてやることになります。
ここではこれ以上触れませんが、次の課題では「パラメータが存在しない」と「パラメータの値が無い」の違いについて説明します。
さて応答画面を返すとき、テキストボックスの中身が消えちゃわないようにプログラムを組んでくださいね。
この演習課題のサンプル を載せておきます。
次は多重分岐を入れてみましょう。それから「パラメータが存在しない」と「パラメータの値が無い」の違い、Javaのデータ型についても触れます。
まず次の課題として作るプログラムの機能から説明します。
テキストボックスがあり、文字を入力する。
ラジオボタンがいくつかあり、色を指定できる。
初期表示では枠線だけのテーブルのセルが表示されている。
テキストボックスに文字を入力し、色を選択した上で送信ボタンを押すと、入力した文字がテーブル内に表示され、背景色が選択した色に変わる。
テキストボックスに未入力であったり、色を選択していないときはメッセージを表示する。
というものです。
具体的な画面イメージは次の通りです。
ここまでの説明では request.getParameter("パラメータ名") でサーバーに送られたデータ(パラメータ値)を受け取れる、という説明しかしてきませんでした。
request.getParameter("パラメータ名") が返すのは常に文字列になります。例えばvalueに1と指定したラジオボタンがあったとして、サーバーで受け取れるのは1という数字ではなく"1"という文字列になります。そしてこの文字列なんですが、これはデータ型が文字列型ではなくて、 String クラス(文字列クラス)ということなんです。
ではここでJavaのデータ型について簡単に説明しましょう。
・データ型
Javaには基本データ型というものと、参照データ型というものがあります。
基本データ型というのは通常の手続き型言語(アセンブラ、COBOL、C、Basic・・・)におけるデータ型と同じものです。以下にJavaと他の言語との、データ型の対応表を示します。
SmalltalkやRubyという言語だと基本データ型のようなものはありません。全てはオブジェクトになります。
Javaがなぜオブジェクトで徹底せずに基本データ型を設けたかというと、悪く言えば「妥協の産物」なんですが、「実行性能を考慮した現実的な対応」といったところでしょうか。
オブジェクトを使うときはどうしても(内部的に)オーバーヘッドがかかるんですが、Javaはこのオーバーヘッドを嫌って、よく使う基本的なデータに関してはオブジェクトにせず、基本データ型として残した、ということだと思います。
Javaの基本データ型
データ型 説明 他の言語との比較
ASM COBOL C VB
byte
1バイトの2進整数
該当なし。 強いて言えばFL1 やHL1
該当なし。
char
該当なし。
short
2バイトの2進整数
H もしくはFL2
S9(4) COMP.
short
Integer
int
4バイトの2進整数
F
S9(9) COMP.
厳密には該当なし。 強いて言えばint
Long
long
8バイトの2進整数
該当なし。
S9(15) COMP.
厳密には該当なし。 強いて言えば最近はlong かもしれない
該当なし。
char
1文字
該当なし。
該当なし。
強いて言えばchar
該当なし。
float
4バイトの浮動小数点数
E
COMP-1.
float
Single
double
8バイトの浮動小数点数
D
COMP-2.
double
Double
boolean
論理値
該当なし。
該当なし。
該当なし。
Boolean
※ int 型とlong 型について、C言語では「厳密には該当なし」としました。たしかCの言語仕様上、バイト数は処理系依存で確定していません。int は現在のCPUではほぼ32bitと思って問題ないと思いますが、かつてはint が16bitというコンパイラの時代もありました(今でもOSと対応コンパイラによってはあります)。
long も同じで32bit以上の整数という意味です。昔はほぼ32bit、4バイト整数でしたが、最近は64bit CPUも出てきているしlong が64bit、8バイト整数というコンパイラもあるかもしれません。
※ Cにおけるunsigned はJavaにはありません。必ず符号が付きます。
※ Javaのchar 型はあくまでも1文字です。Javaは文字コードにunicodeを採用しているため、1文字≠1バイトです。
Javaの参照データ型
参照データ型というのはVBでいうオブジェクト型と同じようなものと考えてください。
Javaはオブジェクト指向言語なんですが、「オブジェクトとかクラスって何?」という話になるとそれなりに奥が深いんです。ここは「超入門編」ってことでごく簡単に言ってしまうと、データとそのデータに対して適用可能な操作(メソッド)をまとめて定義したもの ってことでなんとなく分かりますかね。まずそういう定義があってこれが雛型になり、クラスと呼びます。そしてその雛型に実際の値が入ったもの、雛型(クラス)から作られた(生成された)実体をインスタンスやオブジェクトと呼ぶ、と思ってください。この実体を生成することをインスタンス化といいます。
参照データ型というのは実は、値を持った実体であるインスタンスへのポインタである、という解釈がいいと思います。基本データ型というのは、そのものがデータの実体を示しますが、参照データ型の場合は実体ではなく実体への参照(ポインタ)を表しているのです。
Javaはプログラマが意識してポインタを使うことは出来ないんですが、裏側では実はポインタが使われているんです。
この概念を図示するとこうなります。
それでJavaの場合は文字列は基本データ型ではなく、文字列クラス(String クラス)の参照データ型になるんです。
変数定義のしかたは、
データ型 変数名;
です。
データ型は基本データ型や参照データ型としてクラス名を指定します。同じ型の変数を複数定義する場合、 , で変数を区切って一度に定義できるんですが、これは止めましょう。
必ず1行1変数としてコメントを付け、次のような書き方にしましょう。
データ型 変数名; ⁄⁄ 変数の説明
次のように書くと変数定義と同時に初期値を設定できます。
データ型 変数名 = 式;
式は値、計算式、もしくは値を返す関数、メソッドです。
この書き方を利用すると変数定義と処理を同時に記述できます。多くの本、雑誌、Webサイトなどのサンプルではこれを使い、例えばクライアントから送られたデータを変数に入れておくときは
String w_in_dat = request.getParameter(" Text1")
というように書いています。あるいはデータベースから検索した結果のときは
ResultSet rs = stmt.executeQuery( SQL文);
というような、変数定義と処理を一緒にした書き方が多いです。
ただ私がこういった記述方法が好きじゃないので、これ以降のサンプルではこの書き方はとりません。必ず変数定義と処理は分けて記述し、変数定義は初期値設定だけとします。
今の例でいうと
String w_in_dat = null;
w_in_dat = request.getParameter(" Text1")
や、
ResultSet w_rslt_set = null;
w_rslt_set = stmt.executeQuery( SQL文);
というように変数定義と処理は分けて記述することにします。
・変数の初期値
変数を定義しただけで何も値を設定していないときの初期値ですが、これはクラスレベルのクラス変数、インスタンス変数と(これらについては「サーブレット編」で説明しますので、今はそういうものがあるとだけ覚えていてください)、ローカル変数とで異なります。
・クラスレベルの変数
クラス変数、インスタンス変数の場合はデータ型に応じた初期値が設定されます。
数値型(byte 、short 、int 、long 、float 、double )はそれぞれの型に合わせた 0 になります。
文字型(char )は null 文字(\u0000)になります。
論理値型(boolean )は false になります。
すべての参照データ型は null になります。これは変数定義の段階では、変数が参照する実体(インスタンス)が存在しないことを意味します。
・ローカル変数
クラスレベル以外のローカル変数は、定義しただけでは初期値は不定、入りません。ですから明示的に自分で初期化する必要があります。そこで変数を定義するときは
データ型 変数名 = 式;
の書き方を使い、
データ型 変数名 = 初期値;
としましょう。
ここのサンプルでは今後、設定する初期値はデータ型に合わせて、クラスレベルの変数の初期値と同じ物にすることにします。
・有効範囲
この辺は「サーブレット編」の中でもう少し説明します。変数の有効範囲、つまりプログラム中である変数名を使ったとき、どこまでその変数名で参照できるか、という範囲のことです。
基本的な有効範囲は、その変数定義の直前にある { から、対応する } までのブロックの間 です。
変数名のつけ方
変数名を付けるときに、接頭辞としてデータ型を表すものを先頭に付ける、というのがWin系での流行りだと思います(ハンガリー記法)。例えばint 型ならintKINGAKU 、文字列ならstrEmpNo という風に。
しかし私はこれがあまり好きじゃありません。ですのでここではこれはあまり意識してません。
普段自分が使っている付けかたは、頭に変数の有効範囲を付ける、というものです。それが自分のワークエリアなのか、そのプログラムが呼ばれるときに受け取るパラメータなのか、という違いですね。
COBOLだとWORKING-STORAGEの変数については頭にW-を付ける。集団項目や、COPY句を使う場合もその集団項目に対しては頭にW-を付けたり先頭をWで始める。LINKAGE-SECTIONならL-を付けたり先頭をLで始める、というコーディング規約があったりしますし、私はそうしてました(あるいはArgumentということでA-を付けるところもありますね)。
アセンブラのときも自分のワーク項目については頭にW_を付けて、パラメータ項目なら頭にP_を付けてました。「パラメータならDSECTを切るからD_じゃないの?」という意見もあるかもしれませんが、GETMAINのときだってDSECTを切ります。GETMAINは自分で発行するので、その領域の管理主体は自分です。そうするとやはりワークと同じでW_になります。パラメータは自分内の領域ではない、管理主体が自分以外ということでP_としてました。
プログラムを組むときに見ている、もしくは操作しようとしている領域(データ)は、自分が管理している場所なのか人様が管理している場所(パラメータなど)なのか、そこら辺のミスによるバグのほうが厄介なことが多いと思うため、それを意識するような変数名にしています。
Javaでも基本的に同じです。自分の内部の変数はw_、パラメータとして受け取る変数はp_としてサンプルは記述していきます。
さらに大域変数(グローバル変数)もあるんですが基本的に使わないし、さらに局所的なローカル変数(あるブロック内だけで有効な)についてはwl_として記述します。
それから定数として扱う項目については、c_を使っています。また定数の名前については全て大文字にするのが慣例としてあります。そこで定数の場合はc_の後ろは全て大文字を使った名前付けにします。
ただこの極私的な変数名の付け方は、あまり世間の時流に乗ってないことをお断りしておきます。きっとどんな本やwebサイトを見ても、Javaにおいてはこういった変数名の付け方はしていないと思います(Windows系のCやVB上がりの人が多いだろうし、そっちの流儀で付けてると思います)。
それから実務においては、その作業場所、プロジェクトにおけるコーディング規約があるのなら、それに従った名前付けにすることは言うまでもありません。
定数定義については final というキーワードを付け、同時に値を設定します。
final データ型 変数名 = 式; ⁄⁄ 定数の説明
式は値、計算式、もしくは値を返す関数、メソッドです。
final を指定すると、その変数は値の変更ができなくなります。
プログラム中の命令や式で定数を使うときは特別なことはありません。そのまま値をかくだけです。
注意がいるのは文字定数と文字列定数の違いです。文字定数は ' で囲んだ1文字 で、文字列定数は " で文字列を囲みます。
例えば 'A' は A という文字 ですが、 "A" だと長さが1文字の A という文字列 になります。
データ型で説明したように文字は基本データ型のchar 型ですが、文字列は基本データ型ではなくString の参照データ型になります。両者では使える命令、演算子も違ってくるので特に注意しましょう。
文字列定数を書くときの補足です。 " は文字列定数の開始か終了を意味しますが、文字列中に " 自体を入れたいときはどうすれば良いでしょうか。
この場合はエスケープ記号である \ を使い、\" としてください。例えば A="Sample" という文字列定数なら、 "A=\"Sample\"" と定義します。
さらに \ 自体を文字列に含めたい場合も同じように \ を付け、 \ \ としてください。 "Price=\"\\1,900\"" とすると、Price="\1,900" という文字列を意味します。
比較演算子の説明で等しいかどうか判定する場合は == を使うと説明しましたが、文字列の場合は null との判定を除いてこれは原則使えません。
文字列の比較をする場合は String クラスのメソッドである、equals メソッドを使い、
文字列.equals(比較文字列) としてください。
例によって文字列および比較文字列の部分は、変数・定数・文字列を返す関数やメソッド・式が指定出来ます。
この文字列に対しては原則 == 比較演算子が使えないというのは、JavaにおけるString クラスの実装によります。次にこれについて説明しておきます。
まず文字列型変数の実体なんですが、参照データ型で説明したように裏側ではポインタになってます。ある文字列があったとき、Java内部の共有領域があってそこに文字列が確保され、変数はそこを指すポインタになってます。
ASMだとこんな風(例えば) Cだとこんな風
W_STRDAT1 DC A(C_STRDAT1)
C_STRDAT1 DC AL2(L'C_STRVAL1)
C_STRVAL1 DC C'ABCDE'
char *w_str_dat1 = "ABCDE";
そしてJavaでは確保した文字列の一部を変更することはできません。今の例で"ABCDE"の"A"という文字を"*"に書き換える場合は元の文字列をコピーしながら変更する、結果として"ABCDE"と"*BCDE"という二つの文字列が確保されます。
文字列型に対して replace というメソッドがあり、これは 文字列.replace("A", "*") と記述しますが、これは元の文字列を変更するのではなくて、変更された新しい文字列を返します。
そうではなく確保した文字列自体を変更するような処理が必要な場合、String クラスの参照データ型ではなく、StringBuffer クラスという別のクラスの参照データ型として定義する必要があります。
また別の文字列型変数であっても同じ文字列を設定する場合、実際に確保される文字列は一つだけで、各文字列型変数にはそこへのポインタ(アドレス)が入るだけです(Javaソースコンパイル時の、コンパイラの解析結果による)。
ただし同じ文字列は常に一つだけ確保される訳ではなく、処理過程の結果によっては同じ文字列が別の領域に複数確保されていることもありえます。
さて文字列型に対して == で比較をするということは、ポインタ値が同じか、という比較、つまり同じ実体を指しているか、という意味になります。複数の変数が同じ実体のアドレスを指しているなら、その実体の文字列がどんなに長くてもアドレスだけなのでせいぜい4か8バイトの比較ですむため効率的です。
しかしたまたま同じ文字列の実体が複数の場所に確保されていた場合、この比較だと結果が偽(else)になってしまうこともあるわけです。同じ文字列であれば比較したときに常に真(true)になるためには、 String クラスの equals メソッドを使わないといけない、ということです。
ASMだとこんな風(例えば) Cだとこんな風
* 文字列を==で比較するとこれと同じ
CLC W_STRDAT1,W_STRDAT1B
BNE NOT_EQ
* この場合はここが処理される
CLC W_STRDAT1,W_STRDAT2
BNE NOT_EQ
* この場合は本来はここにくるはずがこなくなる
* 正しくはこうなるべき(equalsメソッド対応)
L R2,W_STRDAT1
L R4,2(R2) * データアドレス
LH R5,0(R2) * データ長
L R2,W_STRDAT1B
L R6,2(R2) * データアドレス
LH R7,0(R2) * データ長
CR R5,R7 * データ長比較
BNE NOT_EQ
CLCL R4,R6 * データ比較
BNE NOT_EQ
* この場合はここが処理されるし
L R2,W_STRDAT1
L R4,2(R2) * データアドレス
LH R5,0(R2) * データ長
L R2,W_STRDAT2
L R6,2(R2) * データアドレス
LH R7,0(R2) * データ長
CR R5,R7 * データ長比較
BNE NOT_EQ
CLCL R4,R6 * データ比較
BNE NOT_EQ
* この場合でもここが処理される
W_STRDAT1 DC A(C_STR_DAT1)
W_STRDAT1B DC A(C_STR_DAT1)
W_STRDAT2 DC A(C_STR_DAT2)
C_STRDAT1 DC AL2(L'C_STRVAL1)
C_STRVAL1 DC C'ABCDE'
C_STRDAT2 DC AL2(L'C_STRVAL2)
C_STRVAL2 DC C'ABCDE'
char *w_str_dat1 = "ABCDE";
char *w_str_dat1B = w_str_dat1;
char *w_str_dat2 = "ABCDE";
// 文字列を==で比較するとこれと同じ
if (w_str_dat1 == w_str_dat1B)
{
// この場合はここが処理される
}
if (w_str_dat1 == w_str_dat2)
{
// この場合はここが処理されるはずがされない
}
// 正しくはこうなるべき(equalsメソッド対応)
if (strcmp(w_str_dat1, w_str_dat1B))
{
// この場合はここが処理される
}
if (strcmp(w_str_dat1, w_str_dat2))
{
// この場合でもここが処理される
}
// 長さ1文字の文字列を例にすると
char *w_str_dat1 = "A";
char *w_str_dat1B = w_str_dat1;
char *w_str_dat2 = "A";
// 文字列を==で比較するとこれと同じ
if (w_str_dat1 == w_str_dat1B)
{
// この場合はここが処理される
}
if (w_str_dat1 == w_str_dat2)
{
// この場合はここが処理されるはずがされない
}
// 正しくはこうなるべき(equalsメソッド対応)
if (*w_str_dat1 == *w_str_dat1B)
{
// この場合はここが処理される
}
if (*w_str_dat1 == *w_str_dat2)
{
// この場合でもここが処理される
}
ここは実際に次のサンプルJSPを実行してみてください。ソースを見て実行結果のtrue 、false と見比べてみてください。説明の意味がわかると思います。
<%@ page
contentType="text/html; charset=Shift_JIS"
session = "false"
%>
<!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">
<%
String w_str = null;
String w_str2 = null;
String w_str3 = null;
w_str = "ABC";
w_str2 = "DEF";
w_str3 = w_str1 + w_str2;
%>
<br />
w_str1= <%= w_str1 %><br />
w_str2= <%= w_str2 %><br />
w_str3= <%= w_str3 %><br />
<br />
<b>==</b> で比較してみる<br />
<br />
w_str3 == "ABCDEF" の結果は : <%= w_str3 == "ABCDEF" %> です。
<br />
<br />
<b>equals</b>メソッド で比較してみる<br />
<br />
w_str3.equals("ABCDEF") の結果は : <%= w_str3.equals("ABCDEF") %> です。
<br />
</body>
</html>
文字列の比較は equals メソッドを使わないといけないのに、ついつい == 演算子で比較してしまうというのは、Javaに慣れないうちはやりがちです。
プログラムをいくら追ってみても、その条件判定では必ず等しくなるはずがなっていない。30分くらい悩んだ後にやっと equals メソッドを使ってなかったことに気付く、というのはありがちなので注意しましょう。
・パラメータの有無
それでは課題に戻りましょう。
パラメータの有無については「パラメータが存在しない」と「パラメータの値が無い」があると書きましたが、両者の違いを説明します。
「パラメータが存在しない」とはいわばパラメータが入る箱そのものが無い、ということです。
これに対して「パラメータの値が無い」とはいわばパラメータが入る箱はあるけど中身は空っぽ、ということです。
また method="get" の場合を例にパラメータ名がText1の場合で説明すると、初期表示のように 送信先URL のようになっているとText1という「パラメータが存在しない」ということです。あるいは 送信先URL?Text2=SendData の場合だとText2というパラメータはあるが、Text1というパラメータは存在しない、になります。
これに対して 送信先URL?Text1= のようになっているのが、Text1という「パラメータの値が無い」状態です。同様に 送信先URL?Text1=&Text2=SendData だとText2というパラメータがあり値もあるが、Text1というパラメータは値が無い、になります。
「パラメータの値が無い」という状態は request.getParameter("パラメータ名") を実行すると、空文字列、長さ0の文字列が返されます。空文字列は "" で表せますし、文字列の長さを調べるときは文字列に対して length メソッドを適用し、 文字列.length() としてください。結果は int で返ります。
ここで clone メソッドについて説明しておきます。結構重要で、これを忘れると痛い目に遭います。
clone メソッドは参照データ型の代入のとき、オブジェクト(インスタンス)のコピーを作成するのに使います。
Javaの場合(大抵の言語がそうですが)代入には = 演算子を使い、
受取り側 = 送り側
と記述します。この代入というのはデータの移送ではなくコピーを意味します。
COBOLの場合はMOVEという命令を使いますが、命令名に反して実際の動作はコピーです。
基本データ型の場合は受取り側の変数に対して、送り側に書いたデータの実体がコピーされます。
それに対し参照データ型の場合、もう一度少し上の参照データ型の説明を見て欲しいのですが、コピーされるのは実体ではなく実体への参照(ポインタ)だけです。
そうではなく参照型データの実体もコピーしたいときに clone メソッドを使い、送り側のほうは
送り側.clone() としてください。
ただし注意点があり clone メソッドの戻り値は Object 型になります。これは全てのオブジェクト(インスタンス)の元になるものです(このあたりはもっと後ろの方でもう少しだけ詳しく説明します)。実際にこれを使うときには受取り側に合わせて型変換を行い、
受取り側 = (受取り側の型)送り側.clone() とする必要があります。
以上の説明を図示するとこうなります。
プログラム中である変数の値をとっておき、その変数自体は変更して後で変更前と変更後の値を使って何かする、というのはよくあることです。しかし参照データ型に対して clone メソッドを忘れてしまうと、とっておいたはずの変更前データも変更後の値に変わってしまっていて処理がおかしくなる、ということが起こります。
参照データ型を clone メソッドを使わずにコピー(代入)してもコピーされるのは参照(ポインタ)だけなので、実際に参照している実体は一つだけです。送り側、受取り側のどちらの参照(変数)を使って値を変更しても、どちらも同じ変更後の実体を参照しているためです(文字列型の場合には、変更後は新しい文字列が生成されるのでこの限りではありません)。
clone を忘れたが為の不具合は、この仕組みに慣れるまでは見つけるまでに30分くらいは悩むでしょう。
さて今度の課題では色指定によって背景色を変えるので、ここで多重分岐を使います。
多重分岐の書き方には2種類あるので、それぞれについて説明していきます。
・多重分岐の書き方1(switch文)
まず一つ目の書き方は switch case を使う方法です。
基本的な書き方は
switch ( 判定対象)
{
case 比較値:
文;
break;
case 比較値:
文;
break;
default:
文;
break;
}
となります。
判定対象は変数・式・メソッド・関数、と何でもいいんですが一つだけ制約がありそれは結果が基本データ型でなければならない というものです。ということは文字列はswitch では判定できない ということになります。
文字列を使って多重分岐パターンを書きたいときは、次に説明する「書き方2」を使ってください。
比較値は判定対象と同じ型の定数値を指定します。判定対象 == 比較値を満たす、最初の case の分が実行されます。 break; 文が現れるか switch ブロックを抜けるまで、順に処理を進めます。
default: は省略可能です。いずれの case にも当てはまらないときに実行させたい文をここに記述します。これが無い場合はすべての case に当てはまらない場合、何も処理されません。
※ 特に注意がいるのが break; 文で、これは書かなくても文法エラーにはなりません。動作が変わるだけです。 case の意味は、そのブロックだけを実行するというものではありません 。処理を実行する「開始位置を示す入り口点」という意味になります。ある case のブロックから処理を開始し、break; があるか、または switch のブロックの最後まで処理を続ける、という意味なんです。
従って break; が無いと自分が期待した case のブロックを処理したあと、(処理を期待していない)次の case のブロック以降も処理されてしまいます。
例えばプログラムの別の部分をコピーしてきて修正するときに、間違えて break; を消してしまうと動きがおかしくなり、非常に分かりにくいバグの原因となったりします。
そのため多重分岐をコーディングするときは switch を使わずに、次に説明する else if 文の書き方にしたほうが思わぬミスを無くせるので安全です。
あるいは break; を書かない、入り口点への分岐を強く意識したコーディングが必要なときだけ switch を使うようにしたほうがいいと思います。
COBOLのEVALUATEなみに強力だと使い勝手もあるんですが、Cと同じような機能しかないので特別にswitch を使う必要性は、 break; を使わないで分岐の入り口点だけ分けるというくらいしか感じられないです。
ちなみにCOBOLのEVALUATEは「判定対象」の部分に複数の式や変数を書いて、判断表のイメージそのままにコーディングしたり、「判定対象」にTRUE、FALSEや定数を書いて、WHEN(Javaのcaseに対応)句に論理式や式を書くという書き方も出来ます。
「判定対象」が複数あるとき、あるWHEN句では任意の判定対象に対しANY(何でもよい)を指定できたり、ちゃんと覚えるとなかなかに強力なんです。
参考までに break; の有無による違いをフローチャートで示します。
・多重分岐の書き方2(else if文)
書き方2は if 文の変形で、if に対する else のところを else if と書くだけです。
書き方は
if ( 条件式)
{
文;
文;
}
else if ( 条件式)
{
文;
文;
}
else if ( 条件式)
{
文;
文;
}
else
{
文;
文;
}
となります。基本的に if 文の書き方と同じです。
最後の else を書いておくと、 switch 文における default: と同じ意味になります。
だいぶ説明が長くなりましたが、以上を踏まえて今回の課題 にチャレンジしてみてください。
この演習課題のサンプル を載せておきます。