匿名クラス内からローカル変数を参照

今さらながら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でクロージャが実装されるまでの代用に。