匿名クラス内からローカル変数を参照
今さらながらJavaの匿名クラスから、
ローカル変数が参照できる事を最近知った。
まずはコードを
public abstract class DBAccessor { public DBAccessor() throws SQLException{ Connection conn = null; try { conn = DriverManager.getConnection(""); yield(conn); } finally { conn.close(); } } public abstract void yield(Connection conn) throws SQLException; } public class MyClass { private List fieldResult = new LinkedList(); public List exec() throws SQLException { List result = new LinkedList(); // 匿名クラス new DBAccessor(){ public void yield(Connection conn) throws SQLException { // これは問題ない fieldResult.add(conn.createStatement().execute("")); // これはアクセスできない result.add(conn.createStatement().execute("")); } }; return result; } }
final宣言をする
今までは上記の通り匿名クラスの中から
インスタンス変数(フィールド)にはアクセスできるが、
ローカル変数にアクセス出来ないと思っていたが。。。
どうやらローカル変数宣言時にfinalをつけてやると
アクセスできる事を知った。
public List exec() throws SQLException { final List result = new LinkedList(); new DBAccessor(){ // 匿名クラス public void yield(Connection conn) throws SQLException { // アクセスできるし、要素の追加もできる result.add(conn.createStatement().execute("")); } }; return result; }
但しfinal宣言している為、その変数に直接代入する事はできない。
がListやJavaBean等のメソッド経由で値を渡す事は可能。
Listをfinalで宣言できるってのも微妙な感じがするが。。。
これを使って
Javaでもそれなりに実用的な関数系によくあるような処理が書ける。
each
public class ExArray<E>{ private E[] ary; ExArray(E[] ary){ this.ary = ary; } each(Iterator<E> iter) { for(E e: this.ary) iter.yield(e); } public static interface Iterator<T>{ public void yield(T e, int index); } // ↑この書き方知らない人も案外多いかも?? }
こう使う
void main(String[] args){ ExArray<Integer>a = new ExArray<Integer>(new Integer[]{1, 3, 5, 7, 9}); final List result = new LinkedList(); a.each(new ExArray.Iterator<Integer>(){ public void yield(Integer e, int index){ result.add(e + index); } }); System.out.println(result); } // 実行結果 // [1, 4, 7, 10, 13]
ちょっと実用的なの
public class PatternReplace { private static final String PAT = "\\$\\{([^}]+?)\\}"; public String replace(String value, Iterator iter) { Matcher mat = Pattern.compile(PAT).matcher(value); while(mat.find()) { value = mat.replaceFirst(iter.yield(mat.group(1))); mat = mat.reset(value); } return value; } public static interface Iterator{ public String yield(String match); } }
文字列内の${〜}で囲まれたプレースホルダを見つける度に
yieldを呼び出し、戻り値を置換する。
こう使う。
void main(String[] args) { String test = "My name is ${name} !! Hello ${content} !!"; test = new PatternReplace().replace(test, new PatternReplace.Iterator(){ public String yield(String match) { if (match.equals("name")) return "James"; else if(match.equals("contents")) return "Java"; return ""; } }); System.out.println(test); } //-> My name is James !! Hello Java !!
rubyとかと比べるとやっぱ冗長なのは仕方ないか。
処理の安全性が得られるだけでも。
まぁJDK7でクロージャが実装されるまでの代用に。