ラベル java の投稿を表示しています。 すべての投稿を表示
ラベル java の投稿を表示しています。 すべての投稿を表示

2012年1月5日木曜日

Servlet3.0 と Guice

最近、Servlet3.0のアノテーションによるゼロコンフィグを試していましたところ、 ふと、これってGuiceと相性いいんんじゃね?と思い至りました。
サクっと書いてみたServlet3.0 + Guice + JDBCってのを紹介します。

Servlet3.0からは、アノテーションでServletを登録できる他に、ServletContext#addServlet()を呼び出しての登録も可能になっています。
つまり、Guiceを利用して生成したServletインスタンスをマッピングさせることが可能だということです。
これをやってみようかと思います。

まず、トランザクション担当のクラスの親となるクラスを作成します。

AbstractService.java
package com.brightgenerous.sample;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.sql.Connection;

public abstract class AbstractService {

    @Target({ ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface Transactional {
    }

    private final ThreadLocal<Holder<Connection>> thlocal = new ThreadLocal<Holder<Connection>>() {

        @Override
        protected Holder<Connection> initialValue() {
            return new Holder<Connection>();
        }
    };

    protected Connection getConnection() {
        return thlocal.get().m_object;
    }

    private void setConnection(Connection x_connection) {
        thlocal.get().m_object = x_connection;
    }

    static class Holder<T> {

        T m_object;
    }
}

次に、そのトランザクションの実装クラスを作成します。
インターフェースと実装クラスは1つずつなので、まとめて書いてしまいます。
便利なのでapache-commons-dbutilsを使用しています。

UserService.java
package com.brightgenerous.sample.service;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import com.brightgenerous.sample.AbstractService;

public interface UserService {

    List<Map<String, Object>> findAll();

    void add(Map<String, Object> x_data);

    class UserServiceImpl extends AbstractService implements UserService {

        @Transactional
        @Override
        public List<Map<String, Object>> findAll() {
            StringBuilder sql = new StringBuilder();
            sql.append("SELECT id, name, email FROM t_user ORDER BY id");
            try {
                return new QueryRunner().query(getConnection(), sql.toString(),
                        new MapListHandler());
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

        @Transactional
        @Override
        public void add(Map<String, Object> x_data) {
            x_data.put("id", nextId());
            StringBuilder sql = new StringBuilder();
            sql.append("INSERT INTO t_user(id, name, email) VALUES (?, ?, ?)");
            List<Object> params = new ArrayList<Object>();
            params.add(x_data.get("id"));
            params.add(x_data.get("name"));
            params.add(x_data.get("email"));
            try {
                new QueryRunner().update(getConnection(), sql.toString(),
                        params.toArray());
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

        private Number nextId() {
            StringBuilder sql = new StringBuilder();
            sql.append("SELECT COALESCE((SELECT MAX(id) FROM t_user), 0) + 1");
            try {
                return (Number) new QueryRunner().query(getConnection(),
                        sql.toString(), new ScalarHandler());
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

いったん、Servletの抽象クラスを作成しておきます。

AbstractHttpServlet.java
package com.brightgenerous.sample.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

abstract class AbstractHttpServlet extends HttpServlet {

    private static final long serialVersionUID = 2788992165026478005L;

    @Override
    protected void doGet(HttpServletRequest x_request,
            HttpServletResponse x_response) throws ServletException,
            IOException {

        doService(x_request, x_response);
    }

    @Override
    protected void doPost(HttpServletRequest x_request,
            HttpServletResponse x_response) throws ServletException,
            IOException {

        doService(x_request, x_response);
    }

    abstract protected void doService(HttpServletRequest x_request,
            HttpServletResponse x_response) throws ServletException,
            IOException;
}

そして、Servletの実装クラスを作成します。
ここでも、インターフェースと実装クラスは1つずつなので、まとめて書いてしまいます。

UserAddServlet.java
package com.brightgenerous.sample.servlet;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import com.brightgenerous.sample.service.UserService;
import com.google.inject.Inject;

public interface UserAddServlet extends Servlet {

    class UserAddServletImpl extends AbstractHttpServlet implements
            UserAddServlet {

        private static final long serialVersionUID = -4751580520972958252L;

        @Inject
        private UserService service;

        @Override
        protected void doService(HttpServletRequest x_request,
                HttpServletResponse x_response) throws ServletException,
                IOException {

            List<Map<String, Object>> datas = service.findAll();
            Map<String, Object> data = new HashMap<String, Object>();
            data.put("name", x_request.getParameter("name"));
            data.put("email", x_request.getParameter("email"));

            service.add(data);

            StringBuilder sb = new StringBuilder();
            sb.append("<html><head><title>servlet3 + guice</title></head><body>");
            sb.append("<a href=\"" + UserFormServlet.class.getSimpleName()
                    + "\">back</a>");
            sb.append("<table border=\"1\">");
            sb.append("<tr><th>name</th><th>e-mail</th></tr>");
            // ignore SQL injection...
            for (Map<String, Object> d : datas) {
                sb.append("<tr><td>" + d.get("name") + "</td>");
                sb.append("<td>" + d.get("email") + "</td></tr>");
            }
            sb.append("<tr><th>name</th><th>e-mail</th></tr>");
            sb.append("<tr><td>" + data.get("name") + "</td>");
            sb.append("<td>" + data.get("email") + "</td></tr>");

            sb.append("</table>");
            sb.append("</body></html>");

            ServletOutputStream out = x_response.getOutputStream();
            out.print(sb.toString());
            out.flush();
        }
    }
}

最後に、ServletContextListener実装クラスを作成します。
このクラスにweb.xmlに対応する処理を記述します。

InitialContextListener.java
package com.brightgenerous.sample;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;


import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import com.brightgenerous.sample.AbstractService.Transactional;
import com.brightgenerous.sample.service.UserService;
import com.brightgenerous.sample.service.UserService.UserServiceImpl;
import com.brightgenerous.sample.servlet.UserAddServlet;
import com.brightgenerous.sample.servlet.UserFormServlet;
import com.brightgenerous.sample.servlet.UserAddServlet.UserAddServletImpl;
import com.brightgenerous.sample.servlet.UserFormServlet.UserFormServletImpl;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.matcher.Matchers;

@WebListener
public class InitialContextListener implements ServletContextListener {

    @Override
    public void contextDestroyed(ServletContextEvent x_sce) {
    }

    @Override
    public void contextInitialized(ServletContextEvent x_sce) {

        Injector injector = Guice.createInjector(new AbstractModule() {

            @Override
            protected void configure() {
                bind(UserAddServlet.class).to(UserAddServletImpl.class);
                bind(UserService.class).to(UserServiceImpl.class);
                bindInterceptor(Matchers.subclassesOf(AbstractService.class),
                        Matchers.annotatedWith(Transactional.class),
                        new MethodInterceptorImpl());
            }
        });

        ServletContext context = x_sce.getServletContext();
        context.addServlet(UserAddServlet.class.getSimpleName(),
                injector.getInstance(UserAddServlet.class)).addMapping(
                "/" + UserAddServlet.class.getSimpleName());
    }

    static class MethodInterceptorImpl implements MethodInterceptor {

        private static final String URL = "jdbc:postgresql://127.0.0.1:5432/db";

        private static final String USERNAME = "username";

        private static final String PASSWORD = "password";

        private static final Method getter;

        private static final Method setter;

        static {
            try {
                Class.forName("org.postgresql.Driver");
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            try {
                getter = AbstractService.class
                        .getDeclaredMethod("getConnection");
                if (!getter.isAccessible()) {
                    getter.setAccessible(true);
                }
                setter = AbstractService.class.getDeclaredMethod(
                        "setConnection", Connection.class);
                if (!setter.isAccessible()) {
                    setter.setAccessible(true);
                }
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public Object invoke(MethodInvocation x_arg0) throws Throwable {
            Object object = x_arg0.getThis();
            if (getter.invoke(object) != null) {
                return x_arg0.proceed();
            }

            Connection connection = DriverManager.getConnection(URL, USERNAME,
                    PASSWORD);
            if (connection.getAutoCommit()) {
                connection.setAutoCommit(false);
            }

            setter.invoke(object, connection);

            Object result = null;
            try {

                result = x_arg0.proceed();
                connection.commit();

            } catch (Throwable e) {
                try {
                    connection.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
                throw e;
            } finally {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                try {

                    setter.invoke(object, (Connection) null);

                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
            return result;
        }
    }
}

起動すると、以下の順に実行されます
1.WebListenerアノテーションによってInitialContextListenerをServletContextListenerとして登録
2.contextInitializedが実行される
2-1.GuiceでAbstractServiceのサブクラスのTransactionalアノテーションが付いているメソッドに対してMethodInterceptorImplを設定しておく
2-2.GuiceがUserAddServletのインスタンスを生成する際にUserServiceを実装したクラスのインスタンスを注入
2-3.UserAddServletのインスタンスをServletとして登録

http://[SERVER_NAME]:[PORT_NUMBER]/[CONTEXT_ROOT]/UserAddServlet?name=brigen_name&email=brigen_mail
にRequestを送ると、サーブレットが実行されます。

かなり簡単にServletが書けました。
実際に活用することを考えたらトランザクションの実装はもっと細かくすべきでしょうが。

もちろん、GlassFishなどのJavaEE環境ではServlet3.0 + EJB Lite + JPAの選択が最適でしょう。

2011年7月23日土曜日

JavaSE7のtryは便利

7月28日に正式リリースされるJavaSE7では、いくつか便利な構文が増えます。
今回は、その中でも特に有用であろうtryの新しい構文について調べてみました。

ファイルのコピーを例にして、tryの構文の拡張を調べてみます
JavaSE6以前のソースコードをJavaSE7のコードに置き換えます。

これまで(JavaSE6以前)のコード
FileInputStream fis = null;
FileOutputStream fos = null;
try {
 fis = new FileInputStream("D:\\test.txt");
 fos = new FileOutputStream("D:\\test2.txt");
 byte[] bytes = new byte[1024];
 int size = -1;
 while ((size = fis.read(bytes)) != -1) {
  fos.write(bytes, 0, size);
 }
} catch (FileNotFoundException e) {
 e.printStackTrace();
} catch (IOException e) { 
 e.printStackTrace();
} finally {
 if (fos != null) {
  try {
   fos.close();
  } catch (IOException ex) {
  }
 }
 if (fis != null) {
  try {
   fis.close();
  } catch (IOException ex) {
  }
 }
}

これから、上記のソースコードをJavaSE7風に書き換えていきます。

例外のマルチキャッチ
catchの部分をひとつにまとめることができます
FileInputStream fis = null;
FileOutputStream fos = null;
try {
 fis = new FileInputStream("D:\\test.txt");
 fos = new FileOutputStream("D:\\test2.txt");
 byte[] bytes = new byte[1024];
 int size = -1;
 while ((size = fis.read(bytes)) != -1) {
  fos.write(bytes, 0, size);
 }
} catch (FileNotFoundException | IOException e) {
 e.printStackTrace();
} finally {
 if (fos != null) {
  try {
   fos.close();
  } catch (IOException ex) {
  }
 }
 if (fis != null) {
  try {
   fis.close();
  } catch (IOException ex) {
  }
 }
}
これで、例外処理時に同じコードを書かずに済みます。
また、「|」で複数の例外を指定した場合は、左の例外から評価されるようです。
これまでの構文のcatchで上に書いていた例外は、その順で左から書けばよいでしょう。つまり、Throwableを書く場合は、一番右です。
AutoCloseableインターフェース
JavaSE7から、AutoCloseableというインターフェースが追加されます。
このインターフェースには、closeというメソッドがあります(ご存知のとおり、JavaSE6以前にもcloseメソッドはありますが、それをオーバーライドしている感じです)。このメソッドが「try-finallyの処理で勝手に呼ばれる」ようです。 AutoCloseableを利用するためのコードは下記の通りです。
try (FileInputStream fis = new FileInputStream("D:\\test.txt"); FileOutputStream fos = new FileOutputStream("D:\\test2.txt")) {
 byte[] bytes = new byte[1024];
 int size = -1;
 while ((size = fis.read(bytes)) != -1) {
  fos.write(bytes, 0, size);
 }
} catch (FileNotFoundException | IOException e) {
 e.printStackTrace();
}
tryの()の中ではセミコロンで区切ると、複数のAutoCloseableを並べて記述できるようです。
Stream関係のインターフェースはAutoCloseableのサブインターフェースになっているようです。
インターフェースを実装する際に、AutoCloseableを意識する必要はあまりないと思われます。ъ(゚Д゚)グッジョブ!!(JavaSE6以前のソースを書き換える場合は別)
※public void close() throws IOException => public void close() throws Exception というふうに変わります。

なにはともあれ、コード量がかなり減りました。ワーイヽ(゚∀゚)メ(゚∀゚)メ(゚∀゚)ノワーイ
AutoCloseableを検証
実際に自分の眼で見て確認しました。
closeの順番と、実際に例外が起こった場合について。
public class Test {
 public static void main(String[] args) {
  new Test().start();
 }

 void start() {
  try (AutoCloseableImpl1 ac1 = new AutoCloseableImpl1(); AutoCloseableImpl2 ac2 = new AutoCloseableImpl2()) {
   
  }
 }
}

class AutoCloseableImpl1 implements AutoCloseable {

 AutoCloseableImpl1() {
  System.out.println(getClass().getSimpleName());
 }

 @Override
 public void close() {
  System.out.println(getClass().getSimpleName() + "#close()");
 }
}

class AutoCloseableImpl2 implements AutoCloseable {

 AutoCloseableImpl2() {
  System.out.println(getClass().getSimpleName());
 }

 @Override
 public void close() {
  System.out.println(getClass().getSimpleName() + "#close()");
 }
}
上記のプログラムを実行します。
実行結果は

AutoCloseableImpl1
AutoCloseableImpl2
AutoCloseableImpl2#close()
AutoCloseableImpl1#close()

closeが呼ばれており、closeする順番もオッケー。
ちなみに、AutoCloseableImpl2()コンストラクタで例外を投げた場合は、以下

AutoCloseableImpl1
AutoCloseableImpl1#close()
Exception in thread "main" java.lang.NullPointerException
・・・

期待通りです。

余談ですが、catchもfinallyもないtry構文が書けるようになりました。(内部的にはfinallyが存在しますが)

実は、JavaSE7にはjava.nio.file.Filesというクラスが追加されており、ファイルをコピーするだけなら、このクラスを使ったほうが良いです。
まぁ、今回はあくまで勉強のためですから・・・

今回の検証でもちいたIDEはNetBeansです。
Eclipse3.7でも検証は可能でしたが、不都合な点がありましたので、NetBeansを使用しました。
現在のEclipse3.7とJDK1.7の詳細については こちら

2011年7月17日日曜日

Eclipse3.7 で JavaSE7

Eclipse3.7 で JavaSE7

JavaSE7の正式リリースは2011年7月28日で、NetBeansは既にJavaSE7に対応しています。
EclipseはまだJavaSE7に対応していないのですが、pluginをインストールすれば使えるようになります。

各ツールのサイト
Java™ Platform, Standard Edition 7 Developer Preview Release
Eclipse Classic 3.7
JDT/Eclipse Java 7 Support(BETA)
JDK準備
JDK7 Preview Releaseをダウンロードして、インストールします。
開発ツールだけインストールすればOKです。
Eclipse準備
Eclipseは3.7の「Classic」を使います。
「IDE for Java Developers」や、「IDE for Java EE Developers」では、pluginのインストールができませんでした。
Install New SoftWare -> Work with:
「http://build.eclipse.org/eclipse/java7patch/」を入力して、pluginをインストールします。
Eclipse設定
Preferences -> Java -> Installed JREs
インストールしたJDK1.7を追加し、使用するようにします。
あとは、Java Compilerの設定で、Compiler compliance level を 1.7 にすれば準備完了です。

実は、上記で設定した開発環境には問題があり、メソッドや変数の候補を出せなくなります。
普段から、何も考えずにとりあえず Ctrl + Space 押して幸せになっている私にとっては致命的です。
Eclipseで正式にサポートされるまではJavaSE7は我慢するか、NetBeansを使いましょう。

2011年5月31日火曜日

アルゴリズムを考える

アルゴリズムというものについて考えてみる

Twitterでこんなツイートを見つけました。

自然数nに対して操作F「nが偶数ならばnを2で割る。nが奇数ならばnを3倍して1を加える。」を10回行う。10回目で初めて1となる自然数を全て求めよ。(07京都高校生数学コンテスト)

プログラマとしては、さっとプログラムを書いて結果を出してみたくなるのではないでしょうか。

この解を出すアルゴリズムは大きく分けて2通りあります。
その1.1から1024(2^10)までの全ての数についてチェックして探す
その2.結果の「1」から逆に遡って探す

上記その1のアルゴリズムは2重ループによる処理で、その2は再帰による処理になります。
おそらく、この問題で求められている解き方は2の方法でしょう。

書いてみた
import java.util.ArrayList;
import java.util.Collection;


public class Main {

 /*
  * こいつを呼ぶ
  */
 public static void main(String[] x_args) {
  new Main().start();
 }

 public void start() {
  exec(new F10To1ImplVer1());
  System.out.println();
  exec(new F10To1ImplVer2());
 }

 void exec(F10To1 x_f) {
  {
   System.out.println(x_f.getClass().getSimpleName() + ":start.");
   long time_s = System.currentTimeMillis();
   int count = 10;
   int end = (int) Math.pow(2, count);
   Collection<Integer> values = x_f.getF10To1(end, count);
   for (Integer value : values) {
    System.out.print(value + "\t");
   }
   long time_e = System.currentTimeMillis();
   System.out.println();
   System.out.println(":end.");
   System.out.println("time => " + (time_e - time_s) + "ms");
  }
 }
 
}

interface F10To1 {

 /*
  * 戻り値Collection内の要素の順番は保証しない
  */
 Collection<Integer> getF10To1(int x_end, int x_count);
}

/**
 * 全ての自然数についてチェックするパターン
 */
class F10To1ImplVer1 implements F10To1 {

 public Collection<Integer> getF10To1(int x_end, int x_count) {
  Collection<Integer> result = new ArrayList<Integer>();
  // 1...最後までのループ
  for (int i = 1 ; i <= x_end ; i++) {
   if (isF10To1(i, x_count)) {
    result.add(Integer.valueOf(i));
   }
  }
  return result;
 }

 private boolean isF10To1(int x_value, int x_count) {
  int tmp = x_value;
  // 9回処理する
  for (int i = 0 ; i < x_count - 1 ; i++) {
   tmp = f(tmp);
   if (tmp == 1) {
    // 1になったら対象外
    return false;
   }
  }
  // 10回目
  tmp = f(tmp);
  return tmp == 1;
 }

 /*
  * 1回分の処理
  */
 private int f(int x_value) {
  if (x_value % 2 == 0) {
   return x_value / 2;
  }
  return x_value * 3 + 1;
 }
}

/**
 * 逆から辿るパターン
 */
class F10To1ImplVer2 implements F10To1 {

 public Collection<Integer> getF10To1(int x_end, int x_count) {
  int tmp = 1;
  // 再帰処理でチェックします
  Collection<Integer> result = new ArrayList<Integer>();
  revF(result, tmp, x_count);
  return result;
 }

 private void revF(Collection<Integer> x_values, int x_value, int count) {
  boolean can3 = canRevF_3(x_value);
  int v2 = revF_2(x_value);
  int v3 = revF_3(x_value);
  count--;
  if (count < 1) {
   if (can3) {
    x_values.add(Integer.valueOf(v3));
   }
   x_values.add(Integer.valueOf(v2));
   return;
  }
  if (can3 && (1 < v3)) {
   revF(x_values, v3, count);
  }
  if (v2 != 1) {
   revF(x_values, v2, count);
  }
 }

 /*
  * 引数が2で割った結果だとした場合の元の数を返す
  */
 private int revF_2(int x_value) {
  return x_value * 2;
 }

 /*
  * 引数が3倍して1を足した結果だとした場合に元の数が存在するか判断する
  */
 private boolean canRevF_3(int x_value) {
  if (x_value < 4) {
   return false;
  }
  if ((x_value - 1) % 3 != 0) {
   return false;
  }
  return (x_value - 1) / 3 % 2 == 1;
 }

 /*
  * 引数が3倍して1を足した結果だとした場合の元の数を返す
  */
 private int revF_3(int x_value) {
  return (x_value - 1) / 3;
 }
}
もしかしたら上記の実装に間違いがあるかもしれません。その場合は、ご指摘いただけるとありがたいです。
F10To1ImplVer1が総当り(その1)で、F10To1ImplVer2が再帰処理を行う(その2)クラスです。
10回程度のループではそれほど差が出ません(マシンにもよります)が、20回程に回数を増やすと極端に処理時間に差が出ます。

上記の2通りのアルゴリズムで、私が先に書いたのは総当り(その1)の方です。
その後、少し考えてその2の方法を書きました。

普段から簡単にプログラムを書こうとしているせいか、「本来はどういうアルゴリズムであるべきか」をあまり考えなくなってしまったようです。

私は細かい実装はあまり気にしないのですが、気にせずともより良いアルゴリズムで書けるのが理想的ですね。

2011年4月24日日曜日

パッケージ内のクラス一覧を取得する

普段からSpringなどのDIフレームワークを使っているのですが、 Springでは、xmlファイルでパッケージ名を指定すると、そのパッケージ内のクラスをインスタンス化して注入してくれます。
便利な機能なのでよく使うのですが、どのようにしてパッケージ名からクラス一覧を取得しているかを知りませんでした。

実装を知るには、ソースを読めば手っ取り早いですが、今回はソースは読まずに、実装方法はこんな感じだろうかと考えてみます。

パッケージ名から、そのパッケージ内のクラス一覧を取得する
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;

/**
 * 指定パッケージ内のクラス情報のコレクションを返す。
 * 
 * @param x_packageName パッケージ名
 * @return クラス情報のコレクション
 */
public static Collection<Class<? extends Object>> getClasses(String x_packageName) {
 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 JavaFileManager fm = compiler.getStandardFileManager(new DiagnosticCollector<JavaFileObject>(), null, null);

 // 一覧に含めるオブジェクト種別。(クラスのみを含める)
 Set<JavaFileObject.Kind> kind = new HashSet<JavaFileObject.Kind>();
 kind.add(JavaFileObject.Kind.CLASS);

 Collection<Class<? extends Object>> results = new ArrayList<Class<? extends Object>>();

 Iterable<JavaFileObject> jfos = null;
 try {
  jfos = fm.list(StandardLocation.CLASS_PATH, x_packageName, kind, false);
 } catch (IOException e) {
  e.printStackTrace();
 }
 if (jfos != null) {
  for (JavaFileObject fos : jfos) {
   try {
    Class<? extends Object> clazz = Class.forName(x_packageName + "." + fos.getName().replaceAll("\\.class$", ""));
    if (clazz.isEnum() || clazz.isInterface() || clazz.isAnonymousClass() || clazz.isLocalClass()) {
     continue;
    }
    results.add(clazz);
   } catch (ClassNotFoundException e) {
    e.printStackTrace();
   }
  }
 }
 return results;
}
使用するのはjava.lang.reflectパッケージのクラスかと思っていましたが、実際はjavax.toolsパッケージのクラスです。
これらのクラスがあるのは、javaのバージョン1.6以降のようです。
バージョン1.5以前ではもうちょっと面倒なやり方になると思います。

フレームワークの実装を知ることで、フレームワークの使い方を知ります。
今回は、実装を想像しただけですが...

2011年3月31日木曜日

enumを使いたおしたい

こういうパターンはいかがでしょうか?という内容です。

javaのenumについてです。

先日、ふと、Effective Javaを読み直してenumについての認識を改めました。

enumの変数というのは、「唯一のインスタンス」
その結果、定数のようにイコールの演算子で一致するのです。

以前からそれは理解していました。
が、実は、思ってた以上に普通のクラスと同じで、インターフェースを実装できます。
さらには、mainメソッドを書いて実行することもできるのです。

これを定数としてしか使わないのは勿体無い。
ということで、有効活用をちょっと考えてみた。

こういうのはいかが?
package sample;

import java.math.BigDecimal;

enum NumberUtility {

 SIMPLE {

  @Override
  Integer createInteger(String x_str) {
   return Integer.valueOf(x_str);
  }

  @Override
  BigDecimal createBigDecimal(String x_str) {
   return new BigDecimal(x_str);
  }
 },

 QUIET {

  @Override
  Integer createInteger(String x_str) {
   Integer result = null;
   if (x_str != null) {
    try {
     result = SIMPLE.createInteger(x_str);
    } catch (NumberFormatException e) {
    }
   }
   return result;
  }

  @Override
  BigDecimal createBigDecimal(String x_args) {
   BigDecimal result = null;
   if (x_args != null) {
    try {
     result = SIMPLE.createBigDecimal(x_args);
    } catch (NumberFormatException e) {
    }
   }
   return result;
  }
 };

 abstract Integer createInteger(String x_str);

 abstract BigDecimal createBigDecimal(String x_str);
}

abstract class Test {

 void exec() {
  System.out.println("100 => " + getNumberUtility().createInteger("100"));
  System.out.println("10.0 => " + getNumberUtility().createBigDecimal("10.0"));
  System.out.println("null => " + getNumberUtility().createInteger("null"));
 }

 abstract NumberUtility getNumberUtility();
}

public class TestMain {

 public static void main(String[] args) {

  try {
   new Test() {

    @Override
    NumberUtility getNumberUtility() {
     return NumberUtility.SIMPLE;
    }
   }.exec();
  } catch (Exception e) {
   System.out.println("catch exception. => " + e.getMessage());
  }
 
  System.out.println();

  try {
   new Test() {

    @Override
    NumberUtility getNumberUtility() {
     return NumberUtility.QUIET;
    }
   }.exec();
  } catch (Exception e) {
   System.out.println("catch exception. => " + e.getMessage());
  }
 }
}
enumの中にabstractメソッドがあって、その場で実装しています。こんなことができるんですねぇ。知りませんでした。
書いた内容について簡単に言うと、Utilityクラスの切り替えです。
Utilityの処理の役割としてはstaticなメソッドで実装したいのだけど、ロジックの違いをインスタンスで変えたいって感じです。

Javaの言語仕様での、enumの実現方法がシンプルだからこそ、これだけの自由度があるのでしょう。
単純さが自由になります。

1行目でエラーが出ましたと言わせたい

今回は、ちょっとしたネタです。
知っている人も多いかもしれません。

内容はタイトルの通り「ソースコードの1行目でエラーを出す」方法です。
もしかすると、JVMに依存するかもしれませんのであしからず。

エラーを吐くコードを1行目に書く方法
public class ExceptionAtLine1 {public static void main(String[] x_args) { System.out.println(x_args[100]); } }
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
 at ExceptionAtLine1.main(ExceptionAtLine1.java:1)
すぐに思いつく方法です。

いや、そんなの当たり前だろって思われてる人も多いでしょう。

では、以下の方法はどうでしょうか?

Genericsを ”利用して” エラーを吐く方法
package sample;

public class ExceptionAtLine1Zwei {

 public static void main(String[] x_args) {
  IMethod method = new IMethodImpl();
  method.method(Integer.valueOf(100));
 }
}

interface IMethod {
 void method(T x_obj);
}

class IMethodImpl implements IMethod {

 @Override
 public void method(String x_obj) {
  System.out.println(x_obj);
 }
}
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
 at sample.IMethodImpl.method(ExceptionAtLine1Zwei.java:1)
 at sample.ExceptionAtLine1Zwei.main(ExceptionAtLine1Zwei.java:7)
どう見ても呼ばれるべきメソッドがありませんね。
Genericsを中途半端に使用すると恐ろしいことになる良い例です。

Eclipseで警告が出るようにしておきましょう。
厳しさは易しさです。