■サーブレット編2■内部ルーチン
次は内部ルーチンについて説明します。ルーチン = メソッドと思ってください。VBの Private Function や、強いて言えばCOBOLのPERFORMの飛び先のセクションや段落に近いです。
・メソッド定義の書き方
書き方は
・書く場所と使い方
書く場所はクラス内ならどこでもいいんですが、public のメソッド定義の後にしましょう。今回は doGet もしくは doPost メソッドがあるので、それの後ろにしましょう。
■パッケージ化
さてHTMLEncodeという内部ルーチンを作ったので、サーブレットからの出力が複数項目ある場合でも簡単に変換できるようになりました。
・パッケージ名の仕組み
インポートする場合の基本的な記述方法は パッケージ名.クラス名 でした。このうちパッケージ名の付け方について説明します。
・ディレクトリ構造との対応
さてパッケージ化したソースファイルとクラスファイル(実行ファイル)ですが、それを置いておくディレクトリ構造はパッケージ名と対応付いたものにしておく必要があります。
・名前無しパッケージ
それと「名前無しパッケージ」についても説明しておきましょう。これはパッケージ名が無い、パッケージ化していないクラスのことです。
◇ソースの構成・クラス名
Javaの基本は1クラス分のソース = 1ファイルです。HTMLEncodeはメソッド名なので、パッケージ化するにあたりクラス名を付ける必要があります。ここは本来のオブジェクト指向からは若干外れますが、機能中心にして Tools というクラス名にしておきましょう。つまりHTMLEncodeメソッドを記述するソースファイルは Tools.java にする、ということです。
・パッケージ化の宣言
パッケージ化するクラスのソースでは一番先頭に、このクラスをどのパッケージに含めるかを指定します。書き方は
・コンストラクタ
一番異なるのは コンストラクタ という特別なメソッド記述が必要な点です。これはクラス名と同じ名前を持つメソッドになります。
package yok;
public class Tools
{
// Toolsクラスのコンストラクタ
public Tools()
{
}
public String HTMLEncode(String p_src_str)
{
if (p_src_str == null)
{
return (null);
}
else
{
return (p_src_str.replaceAll("&", "&").
replaceAll("/", "/").
replaceAll("<", "<").
replaceAll(">", ">").
replaceAll("\"", """).
replaceAll("!", "!").
replaceAll("\\?", "?").
replaceAll("=", "=").
replaceAll("%", "%"));
}
}
}
※ なおこれは文字列操作の練習の意味合いが強く、実行効率は全く考慮していません。それを内部ルーチンやパッケージ化の練習の材料として、そのまま使ってきました。
・使い方
まずはこれを使うほうのサーブレットもしくはJSPソースで yok.Tools をインポートします。
Tools w_Tools = null; // Toolsクラスのインスタンス用
w_Tools = new Tools(); // Toolsクラスを実体化(インスタンス化)する
というように実体化するクラス名の前に new という演算子を付けて、クラス型の参照データ型変数に代入します。Toolsクラスのコンストラクタの定義を見るとわかるように、引数を必要としていないのでこうしてください。
■文字列関連の追記さてコンストラクタの説明も済んだので、前に説明した文字列( String )クラスのコンストラクタと、文字列バッファ( StringBuffer )について簡単に触れておきます。 ・String クラスの主なコンストラクタ
ここまでは String クラスを使うときは例えば、 String型変数 = p_request.getParameter("パラメータ名"); のようにしてました。これは request クラスの getParameter メソッドは戻り値として文字列を返す、つまり String のインスタンスを返すので、そこへの参照を String 型変数に格納していました。このように他が生成したインスタンスを代入するのではなく、自分で String のインスタンスを生成するためのコンストラクタについて主なものを説明します。
◇文字列バッファ( StringBuffer )
文字列は変更不可でした。文字列の一部を変更すると、変更後の新しい文字列が生成されました。文字列バッファ( StringBuffer )はこれと違い変更可能です。最初に確保した文字列バッファに対してその一部を変更することができ、新しい文字列バッファが生成されることはありません。そのため一部や全体を頻繁に変更をするような場合、新しいインスタンスが生成されないので String よりも効率的です。なおここでいう変更とは既存の文字の並びの後ろに別のものを追加したり、削除することも含みます。
・StringBuffer クラスの主なメソッド
まずメソッドに先立ち、 StringBuffer クラスのコンストラクタについて説明します。
■配列の話
やっとここでデータ構造の配列の話になります。
・基本的な配列の使い方
これはそれほど特別なことは無いのですが、強いて言うならば「変数が配列であることの宣言」と「実際にある要素数分の配列を生成する」ことが、原則として別になる、ということでしょうか。
・指標の範囲
Javaで宣言する配列の大きさは、その配列の要素数です。またJavaでは配列の先頭の要素を指す指標値は0になります。
例えばVBでは配列指定は
Dim 変数名(最大の指標値) as データ型 となります。最小の指標値は Option Base宣言に依り、デフォルトは0です。Option Base 1と設定すると最小の指標値は1になります。 従ってVBの場合、実際の要素数はOption Baseの設定で変ることになります。 Javaでは配列の大きさは要素数で指定ですし、先頭の指標は0と決まっています。
・配列の仕組み
配列宣言時に指定するデータ型は、基本データ型だけではなく参照データ型も可能です。
・配列の次元
先ほどの配列定義の例では2次元配列の記述も示しました。しかし実はJavaには1次元配列しかありません。2次元の場合は配列の配列、配列そのものがクラス型の参照データ型ということで、配列への参照(ポインタ)の配列が出来ているのです。
この1次元配列しかなく高次元配列は配列の配列の・・・で実現する、という点についてはCOBOLと同じです。COBOLでは配列はOCCURSという繰返し項目宣言をします。例えば
05 変数名 PIC S9(9) COMP OCCURS 10. といった具合に。これは4バイトの符号付2進整数が10個の配列、という意味になります。これは基本項目ですが2次元の配列なら集団項目を使い、 05 変数名1 OCCURS 10. 10 変数名2 PIC S9(9) COMP OCCURS 10. とすると変数名1は10個の配列であり、その内訳として4バイトの符号付2進整数が10個の配列を含んでいる、という意味です。変数名2を使えば2次元配列で4バイトの符号付2進整数が10×10個、という意味になります。 COBOLの集団項目はCの構造体やその他の言語のRecord型と同じようなものと考えてください。 ちなみにJavaでは構造体やRecord型というものはありません。これらは全てクラスとして定義することになります。
・配列のコピー
「配列の仕組み」で説明したように基本データ型の配列の場合、配列の個々の要素は基本データ型ですが、その配列そのものはクラス型の参照データ型になります。そのため配列をコピーしようとして = を使うと文字列型と同じようなことが起きます(ポインタ情報のみコピーされ実体はコピーされない)。
<%@ page
contentType="text/html; charset=Shift_JIS"
%>
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head>
<title> Java配列のテスト1 </title>
</head>
<body bgcolor="#aaddaa">
<%
int w_i = 0;
int w_j = 0;
int[] w_tb1 = {1, 2, 3};
int[] w_tb2 = {4, 5};
int[][] w_tb3 = {w_tb1, w_tb2};
int[] w_tb4 = null;
int[] w_tb5 = null;
out.print("<br />");
out.print("w_tb3 size=" + w_tb3.length);
for (w_i = 0; w_i < w_tb3.length; w_i++)
{
for (w_j = 0; w_j < w_tb3[w_i].length; w_j++)
{
out.print("<br />");
out.print(" w_tb3[" + w_i + "][" + w_j + "] = " + w_tb3[w_i][w_j]);
}
}
out.print("<br /><br />");
out.print("Before w_tb1 size=" + w_tb1.length);
for (w_i = 0; w_i < w_tb1.length; w_i++)
{
out.print("<br />");
out.print(" w_tb1[" + w_i + "] =" + w_tb1[w_i]);
}
w_tb4 = w_tb1; // w_tb4 に w_Tb1 自体を代入
w_tb4[0] = 7;
w_tb4[1] = 8;
w_tb4[2] = 9;
out.print("<br /><br />");
out.print("After w_tb1 size=" + w_tb1.length);
for (w_i = 0; w_i < w_tb1.length; w_i++)
{
out.print("<br />");
out.print(" w_tb1[" + w_i + "] =" + w_tb1[w_i]);
}
out.print("<br /><br />");
out.print(" w_tb4 size=" + w_tb4.length);
for (w_i = 0; w_i < w_tb4.length; w_i++)
{
out.print("<br />");
out.print(" w_tb4[" + w_i + "] =" + w_tb4[w_i]);
}
w_tb5 = (int[])w_tb1.clone(); // w_tb5 に w_Tb のコピーを代入
w_tb5[0] = 10;
w_tb5[1] = 11;
w_tb5[2] = 12;
out.print("<br /><br />");
out.print("After w_tb1 size=" + w_tb1.length);
for (w_i = 0; w_i < w_tb1.length; w_i++)
{
out.print("<br />");
out.print(" w_tb1[" + w_i + "] =" + w_tb1[w_i]);
}
out.print("<br /><br />");
out.print(" w_tb5 size=" + w_tb5.length);
for (w_i = 0; w_i < w_tb5.length; w_i++)
{
out.print("<br />");
out.print(" w_tb5[" + w_i + "] =" + w_tb5[w_i]);
}
%>
</body>
</html>
■Javaの引数渡し
メソッド呼び出しにおける引数の受渡し方法ですが、Javaでは全て「値渡し」(値呼び、ということもある)になります。これは呼び出し側が渡す引数のコピーを作り、呼ばれたほうはコピーを参照して操作する、というものです。
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.IOException;
public class parm_test extends HttpServlet
{
private PrintWriter wc_out;
public void doGet (HttpServletRequest p_request,
HttpServletResponse p_response)
throws IOException, ServletException
{
int w_i = 0; // 配列インデックス用
int[] w_tb1 = {1, 2, 3, 4, 5};
int w_dat = 0;
String w_str_dat = null;
w_dat = 0;
w_str_dat = "initial text";
p_request.setCharacterEncoding("Shift_JIS");
p_response.setContentType("text/html; charset=Shift_JIS");
wc_out = p_response.getWriter();
wc_out.print(
"<!doctype html public \"-//W3C//DTD HTML 4.0 Transitional//EN\" "
+ "\"http://www.w3.org/TR/REC-html40/loose.dtd\">");
wc_out.print("<html>");
wc_out.print("<head>");
wc_out.print("<title> Javaの引数渡しのテスト </title>");
wc_out.print("</head>");
wc_out.print("<body bgcolor=\"#aaddaa\">");
wc_out.print("<br /><br />");
wc_out.print("Before Call : w_tb1 size=" + w_tb1.length);
for (w_i = 0; w_i < w_tb1.length; w_i++)
{
wc_out.print("<br />");
wc_out.print(" w_tb1[" + w_i + "] =" + w_tb1[w_i]);
}
wc_out.print("<br />");
wc_out.print(" w_dat =" + w_dat);
wc_out.print("<br />");
wc_out.print(" w_str_dat =" + w_str_dat);
wc_out.print("<br /><hr /><br />");
// 内部メソッド呼び出し
this.innr_method(w_tb1, w_dat, w_str_dat);
wc_out.print("After Call : w_tb1 size=" + w_tb1.length);
for (w_i = 0; w_i < w_tb1.length; w_i++)
{
wc_out.print("<br />");
wc_out.print(" w_tb1[" + w_i + "] =" + w_tb1[w_i], w_dat);
}
wc_out.print("<br />");
wc_out.print(" W_dat1 =" + W_dat1);
wc_out.print("<br />");
wc_out.print(" w_str_dat =" + w_str_dat);
wc_out.print("<br />");
wc_out.println("</form>");
wc_out.println("</body>");
wc_out.println("</html>");
}
// 内部メソッドの定義
private void innr_method(int[] p_tb, int p_dat, String p_str_dat)
{
int w_i = 0; // 配列インデックス用
for (w_i = 0; w_i < p_tb.length; w_i++)
{
p_tb[w_i] = p_tb[w_i] + 10;
}
p_dat = p_dat + 10;
p_str_dat = p_str_dat + " add text";
// wc_outはインスタンス変数なので、parm_testクラス内であれば
// アクセス可能
wc_out.print("innr_method : p_tb size=" + p_tb.length);
for (w_i = 0; w_i < p_tb.length; W_i++)
{
wc_out.print("<br />");
wc_out.print(" p_tb[" + w_i + "] =" + p_tb[w_i]);
}
wc_out.print("<br />");
wc_out.print(" p_dat =" + p_dat);
wc_out.print("<br />");
wc_out.print(" p_str_dat =" + p_str_dat);
wc_out.print(""<br />"<br />"<hr />"<br />");
}
}
変数名のつけ方2
ここの説明やサンプルについて、メソッドやコンストラクタが受け取る引数(パラメータ)の名前に関してはp_を頭に付ける、としてきました。実を言うとこれをやめてa_にし、説明・サンプルとも変更しようかと思ったことがありました。
昔からプログラミングをしている人にとっては、引数をパラメータと呼ぶことに違和感はないと思います。サブルーチンに渡すパラメータ(引数)、関数が受けるパラメータ(引数)といったように。 しかし引数はどちらかというとパラメータ(Parameter)ではなくアーギュメント(Argument)です。それでa_という接頭辞に変えようかとも思ったのです。 しかし今説明したように、Javaの引数渡しの仕組みがあります。クラス型の参照データ型の場合は呼ばれたほうが引数として受けるのはポインタ情報だけでした。ということで極私的な名前付けの引数はパラメータだからp_は、その引数が参照データ型の場合は同時にポインタのp_も表せて二重の意味をもたせられるので、このままで行きます。 ■もっとよいHTMLEncode
さて配列の説明も終わりましたので、約束どおり「文字列の変換」いらい説明に使用した HTMLEncode の、もう少し処理効率を考慮したものを例示します。
public class Tools
{
// 変換対象文字の文字コード(unicode)範囲上限と下限
private final int c_MIN_CVT_CH = 33;
private final int c_MAX_CVT_CH = 63;
// 変換対象判定と変換後文字列テーブル
// 文字コードに対応するエントリがnullなら変換対象外、null以外ならその
// 文字列に変換する
private final String[] c_CV_TBL =
{"!", """, null, null, "%", "&", null, null, null, // 33-41
null, null, null, null, null, "/", null, null, null, null, // 42-51
null, null, null, null, null, null, null, null, "<", "=", // 52-61
">", "?"}; // 62-63
// Toolsクラスのコンストラクタ
public Tools()
{
}
public String HTMLEncode(String p_src_str)
{
StringBuffer w_ret_str = null;
Character w_ch = null;
char[] w_tst_ch = null;
int w_ix = 0;
int w_ch_cd = 0;
if (p_src_str == null)
{
return (null);
}
else
{
// 変換後文字列長として元データの倍のサイズを仮確保
// (StringBufferは自動拡張されるが、とりあえず倍とした)
w_ret_str = new StringBuffer(p_src_str.length() * 2);
// 元データの文字列を文字配列に変換
w_tst_ch = p_src_str.toCharArray();
// 元データを1文字ずつ変換対象かチェックしていく
for (w_ix = 0; w_ix < w_tst_ch.length; w_ix++)
{
// 取り出した1文字をCharacterクラス型に変換し、
// 文字コードを取得する
w_ch = new Character(w_tst_ch[w_ix]);
w_ch_cd = w_ch.hashCode();
// 文字コードが変換対象範囲外ならそのまま変換後にセット
if ( (w_ch_cd < c_MIN_CVT_CH)
|| (w_ch_cd > c_MAX_CVT_CH))
{
w_ret_str = w_ret_str.append(w_tst_ch[w_ix]);
}
else
{
// 文字コードから配列要素位置に変換
w_ch_cd = w_ch_cd - c_MIN_CVT_CH;
if (c_CV_TBL[w_ch_cd] == null)
{
// 変換対象外ならそのまま変換後にセット
w_ret_str = w_ret_str.append(w_tst_ch[w_ix]);
}
else
{
// 変換対象なら変換後文字列をセット
w_ret_str = w_ret_str.append(c_CV_TBL[w_ch_cd]);
}
}
}
// 変換後のStringBufferをStringに変換して返す
return (w_ret_str.toString());
}
}
・考え方このやり方の考え方(アルゴリズム)を先に説明します。
といった仕組みです。
実際のところ変換する文字がこの程度の数ならば、こんなこった仕組みを使わずに1文字ずつ if 〜 else if で判定して変換してもよかったんです。パフォーマンスにも差がそれほどない、どころかむしろその方が若干速いかもしれません。
・実行結果
最初に例示したHTMLEncodeと今回直したHTMLEncodeの実行結果の差ですが、
■staticメソッド
ここで HTMLEncode を利用して、違った形の実装方法を説明しておきます。
・staticメソッドの違い
staticメソッドと通常のメソッドの違いはまず、そのメソッドのクラスをインスタンス化する必要がない、ということです。メソッドを使うときはそれを持っているクラスをインスタンス化して参照データ型変数に代入しておき、 そのクラスの参照データ型変数.メソッド名 とする必要があります。例えばHTMLEncodeメソッドであればToolsクラスをインスタンス化してTools型の変数w_Toolsに代入しておき、 w_Tools.HTMLEncode としていました。
public String HTMLEncode(String p_src_str) となっていました。これをstaticメソッドにするときはstaticという修飾子を付けて public static String HTMLEncode(String p_src_str)
とします。
・staticメソッドで操作(参照)可能な変数のレベル通常のメソッドが操作できる変数は、
でした。なおここでいう変数とは final 宣言をした定数も含みます。
// 変換対象文字の文字コード(unicode)範囲上限と下限
private final int c_MIN_CVT_CH = 33;
private final int c_MAX_CVT_CH = 63;
となっていたものを
// 変換対象文字の文字コード(unicode)範囲上限と下限
private static final int c_MIN_CVT_CH = 33;
private static final int c_MAX_CVT_CH = 63;
とします。そうするとこの変数(この場合 final 宣言しているので定数扱いですが)はクラス変数になり、staticメソッドから操作(参照)できるようになります。 ※ 実際にはHTMLEncodeでインスタンス変数にしていたものは全て、メソッド内の局所変数でも良かったのです。しばらく後の方で出てくる説明の都合上、あえてインスタンス変数としていました。
■メソッドの識別
多くの言語ではJavaのメソッドに対応する関数や手続きの識別は、その名前だけで決まります。しかしJavaはそうではありません。メソッドの識別はその名前と引数の組み合わせで行われます。引数の組み合わせは、引数の型、数、並び順、によります。
本当ならもっとオブジェクト指向らしくして「1文字分の変換」の ChrEncode は内部メソッドではなく、 Character クラスを拡張して encode というメソッドを持たせたいところです。
class CharacterX extends Character
{
// CharacterXクラスのコンストラクタ
public CharacterX(char p_ch)
{
// superクラス(基底クラス)のコンストラクタを呼び出す
// この場合は Character クラスのコンストラクタ
super(p_ch);
}
public String encode()
{
// superクラス(基底クラス)が保持している文字をEncodeする
// 変換対象文字の文字コード(unicode)範囲上限と下限
final int c_MIN_CVT_CH = 33;
final int c_MAX_CVT_CH = 63;
// 変換対象判定と変換後文字列テーブル
// 文字コードに対応するエントリがnullなら変換対象外、null以外ならその
// 文字列に変換する
final String[] c_CV_TBL =
{"!", """, null, null, "%", "&", null, // 33-39
null, null, null, null, null, null, null, "/", null, null, // 40-49
null, null, null, null, null, null, null, null, null, null, // 50-59
"<", "=", ">", "?"}; // 60-63
int w_ch_cd = 0;
// superクラス(基底クラス)が保持している文字の文字コードを取得する
w_ch_cd = super.hashCode();
// 文字コードが変換対象範囲外ならそのまま文字列にして返す
if ( (w_ch_cd < c_MIN_CVT_CH)
|| (w_ch_cd > c_MAX_CVT_CH))
{
return (super.toString());
}
else
{
// 文字コードから配列要素位置に変換
w_ch_cd = w_ch_cd - c_MIN_CVT_CH;
if (c_CV_TBL[w_ch_cd] == null)
{
// 変換対象外ならそのまま文字列にして返す
return (super.toString());
}
else
{
// 変換対象なら変換後文字列を返す
return (c_CV_TBL[w_ch_cd]);
}
}
return;
}
}
というようなのを作りたかったんですが、 Character クラスは public final class として宣言されています。
・さてどうするか
この内部ルーチン化したHTMLEncodeの、「1文字の変換」という共通機能をルーチン化して使いまわすというのは、プログラム設計としてはごく真っ当だと思います。
オブジェクト指向本来としてはシステム設計や保守、維持管理、他のシステムへの流用のしやすさにおいての効率性を求める意味合いが強く、こういう細かい処理性能上のいわば妥協はしないほうがいいんですが。しかし現実のシステムにおいてはどうしても処理性能は重要になります。それにJava自体が Character クラス、 Integer クラスとは別に char 型や int 型を基本データ型として持ち、処理性能上の妥協をしているわけですから良しとしましょう。。
|
Copyright © 2002-2003 YOK. All Rights Reserved.