頑健な Java プログラムの書き方

<http://www.alles.or.jp/~torutk/oojava/codingStandard/
writingrobustjavacode.html
>
– Writing Robust Java Code(2000.1.15) の邦訳版.
 
改めてこういうものを読んで、コーディング習慣の曖昧な部分を直していこうと思う。
個人的によくやるのは、

final Set listCustomers;
final Set customerList;

とかを曖昧な基準で使ってしまう。(前者はハンガリアンの流れのつもり、後者は英語の語順)
 
複数の単語から成る変数名の場合にどういう順序にするか、という点が曖昧だったが、ハンガリアン記法で定義されていない型に対してはやは

Security Code Guidelines

– セキュアなコードを書くための、 Sun 公式ガイドライン。
Eclipse の PMD プラグインで自動チェックしてくれるみたい。
<http://java.sun.com/security/seccodeguide.html#gcg>
– Security Code Guidelines
<http://pmd.sourceforge.net/>
– PMD 公式
Eclipse(2.x/3.0.x/3.1.x) 用プラグインをインストールするときは以下の URL を指定すればよい。
 http://pmd.sourceforge.net/eclipse
<http://www.eclipsewiki.net/eclipse/?PMD>
– EclipseWiki – PMD
使えるルールと、なぜいけないかという理由が日本語でまとめられている。

ResultSet を Map に変換するには

ResultSet を Map に変換する方法として、Apache Commons の DbUtils に次のような便利なメソッドがあります。

org.apache.commons.dbutils.BasicRowProcessor#toMap(ResultSet) throws SQLException

ResultSet の行を Map へ変換します。 この実装では、大小文字を区別しないカラム名をキーとしたMapを返します。 例えば、map.get(“COL”) の呼び出しと
map.get(“col”) の呼び出しでは、 同一の値が返されるようになります。

Commons DbUtils: JDBC Utility Component
BasicRowProcessor javadoc

または、自前実装でこのような方法もあります。

public List resultSetToArrayList(ResultSet rs) throws SQLException{
  ResultSetMetaData md = rs.getMetaData();
  int columns = md.getColumnCount();
  ArrayList list = new ArrayList(50);
  while (rs.next()){
     HashMap row = new HashMap(columns);
     for(int i=1; i&lt;=columns; ++i){<br />
      row.put(md.getColumnName(i),rs.getObject(i));
     }
      list.add(row);
  }

return list;
}

参考: http://stackoverflow.com/questions/7507121/efficient-way-to-handle-resultset-in-java

PreparedStatement, ResultSet で汎用性のある引数を与えたい

– クエリ処理用のメソッドなんかを作ってると、setXxx(), getXxx() を指定するのに任意型をとりたい時がある。
最初は setString(1, o.toString()), getString(1) で全て済ませようとしていたが、実装してみると OO らしからぬ処理になって微妙だったので調べてみたところ、ちゃんといい方法があった。
<http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/sql/PreparedStatement.html#setObject(int,%20java.lang.Object)>
– PreparedStatement.setObject(int, Object)
<http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/sql/ResultSet.html#getObject(int)>
– ResultSet.getObject(int)
– 以前から使い道を疑問に思っていたが、SQL 型に変換可能(かつ変換が曖昧でない)な型ならなんでも使えるとのこと。これは汎用化には便利。

Java のバイトコード書き換え

コンパイル済みのクラスの動作を、バイトコードを書き換えることによって変更することが可能になる。既存のクラスの実装をハンドリングする事もできるため、AOP の実現のために用いられる。JBoss, Bugdel が利用している。
http://www.javassist.org/
– Javassist 公式(JBoss.com)
http://www.csg.is.titech.ac.jp/~chiba/notes/javapress03/
– Javassist 入門
http://muimi.com/j/aop/javassist/
– Javassist にさわる

JDBC の PreparedStatement で ? で置き換えられない箇所

– 次のようなコードで SQLException が出て, クエリのどこがおかしいのか数分悩んでしまった。

final int nLimit = 10;
final int nOffset = 20;
Connection con = getConnection();
try{
    PreparedStatement ps = con.prepareStatement(
            “select id from user limit ? offset ?”); // ここで例外
    try {
        ps.setInt(1, nLimit);
        ps.setInt(2, nOffset);
        // ….
    } finally { ps.close(); }
} finally { con.close(); }

パラメータマーカー “?” は limit, offset には使えないというのが答え(com.mysql.jdbc(mysql-connector-java) 3.1.6 で確認)。次のようにするしかないのかな…

“select id from user limit ” + nLimit + ” offset ” + nOffset

オフセットや表示数を外部から受け取る場合はもちろん int に変換してからにすること。

Tomcat 5.x で Servlet をマップできない

– web.xml で定義とマッピングをちゃんとしても、その URL でアクセスすると 500 エラーになって表示されず、*.war でやるとそのファイルだけデプロイされないという状態になっていた。

javax.servlet.ServletException: ラッパがサーブレットクラス com.example.my.FooServlet 又はそれが依存するクラスを見つけることができません

となって、

java.lang.ClassNotFoundException: com.example.my.FooServlet

が続く。
– 原因は一言では $TOMCAT_HOME/server/lib に servlet-api.jar が無かったこと($TOMCAT_HOME/common/lib にはあったがだめだった…何かが原因で親である common の設定がクリアされた?)。サンプルのサーブレットが動いてたかは確認してないが、とりあえずこんな感じで対処:

cp -p common/lib/server-api.jar server/lib/

– 参考:
http://jakarta.apache.org/tomcat/tomcat-5.0-doc/class-loader-howto.html
– The Apache Jakarta Tomcat 5 Servlet/JSP Container – Class Loader HOW-TO
http://www.atmarkit.co.jp/fjava/javatips/049jspservlet025.html
– @IT: Java TIPS — クラスローダの仕組みを知る

JSP 基本文法

– <% 〜 %> で囲んだ部分は全体で1つのメソッドのように Java コードが書ける.

<body>
  <%
    out.println(“Hello, world!”);
  %>
</body>

– <%= 〜 %> で囲んだ部分は一つの式として評価され、戻り値をその部分に表示する( つまり void は不可)。複文になってはならないため、セミコロンは用いない。

Your name is <%= name %>

– <%! 〜 %> で囲んだ部分はクラス内の定義とみなす

  <%!
    private static final int FOO = 1;
    private void final doSomething(String value){ /* do something */ }
  %>

– <%@ 〜 %> で囲んだ部分は JSP の制御タグとする

  <%@ page contextType="text/html; charset=EUC-JP" %>
  <%@ page import="java.sql.*" %>
  <%@ page import="java.util.*" %>

– JSP のインクルード構文:
— <jsp:include page=”foo.jsp” /> – 対象のページを 別に実行して, 結果を動的に(コンパイル後に)インクルードする。ローカル変数, request パラメータはファイルごとに独立になる。

view.jsp:
  <body>
    Hi, <jsp:include page=”inc.jsp”/>.
  </body>
 
inc.jsp:
  <%= "John" %>

— <%@ include file = "foo.jsp" %> – 対象のページをそのまま読み込み、静的に(コンパイル時に)インクルードする。ローカル変数は共存になり、インクルード対象(exp. inc.jsp)のファイルが更新されてもインクルード元(ex. foo.jsp)は再コンパイルされないため、全てのインクルード元について明示的に更新してやらなければならない。

view.jsp:
 <body>
   <%@ include file = "inc.jsp" %>
   Hi, <%= name %>.
 </body>
  
inc.jsp:
  <% String name = "John"; %>

– 参考:
http://www.javaroad.jp/servletjsp/index.html
– Java の道: Servlet, JSP

Java で MD5

java.security.MessageDigest を使うと簡単。

MessageDigest md;
try {
    md = MessageDigest.getInstance(“MD5”);
} catch(NoSuchAlgorithmException e) {
    e.printStackTrace();
    return;
}
// digest を求めるバイト列設定
// update(“Hello, world!”.getBytes()) と同じ。
md.update(“Hello, “.getBytes());
md.update(“world!”.getBytes());
// 計算して求めるがそのままだとバイト列なので文字列化.
final byte[] digest = md.digest();
final String sHexDigest; {
final StringBuffer buf = new StringBuffer(“”);
    for(int i=0; i < digest.length; i++){
        final int n = digest[i] & 0xFF;
        if(n < 16) buf.append("0");
        buf.append(Integer.toString(n, 16));
    }
    sHexDigest = buf.toString();
}
 
System.out.println(sHexDigest);