Java のローカル内部クラスや無名内部クラスから外部のローカル変数にアクセスする

Java で Swing を使ってると無名内部クラス (anonymous inner class) をしばしば用いることになると思います。 例えば SwingUtilities.invokeLater メソッド に渡す Runnable オブジェクト は無名内部クラスとして生成することが多いのではないでしょうか?

そのような無名内部クラスやローカル内部クラス (local inner class) からは、そのクラスが定義されているスコープ内のローカル変数 (局所変数) やメソッドパラメータにアクセスできないものだと思っていまして、「アクセスできれば便利なのになー」 とずっと思っていました。 が、実はローカル変数へのアクセスはできるということを知ったのでメモ書きしておきます。

public class Test {
    
    public static void main( String[] args ) {
        // 無名内部クラスからアクセスされるローカル変数
        final int extVar = 100;
        // 無名内部クラス
        new Runnable() {
            @Override
                public void run() {
                    // 無名内部クラスの外部で、かつ無名内部クラスが定義されて
                    // いるスコープ内のローカル変数にアクセスする
                    System.out.println( extVar );
                }
        }.run();
    }

}

ただし、アクセスできるのは final 修飾子が付けられた変数だけ です。 final 修飾子が付けられていない変数へのアクセスは以下のようなコンパイルエラーを引き起こします。

Cannot refer to a non-final variable variableName inside an inner class defined in a different method

参考文献

プログラミング言語Java (The Java Series)

プログラミング言語Java (The Java Series)

  • 作者: ケン・アーノルド,ジェームズゴスリン,デビッドホームズ,柴田芳樹
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2007/04
  • メディア: 単行本
  • 購入: 38人 クリック: 1,044回
  • この商品を含むブログ (71件) を見る

Amazon.co.jp では 「翻訳がひどい」 ということであまり評価は高くありません。 実際、翻訳がひどくて全く意味を理解できない箇所もないことはないのですが、まあ読み辛いものの意味は理解できるのが大部分ですし、内容的には良いと思います。

Java で Base64 エンコード, OAuth, JSON を扱うためのライブラリ WSCUtils を公開しました

JavaTwitter クライアントを作ろうと思ったときに、とりあえず OAuth や JSON を扱えるようにする必要があったのでライブラリを作ってみました。 既に twitter4j とかあるんで需要はない気はしますが興味のある方はどうぞ。

フィードバックなど頂けると嬉しいです。

続きを読む

Java における文字列とバイナリ列の相互変換についてと OAuth のパーセントエンコードの方法

Java で文字列を扱うのはあまり慣れておらず、文字列をパーセントエンコードするのにちょっとてこずったので軽くメモを。

文字列 (String オブジェクト) とバイナリ列 (byte 型配列) の相互変換

Java において、文字列を任意のエンコーディングエンコードしてバイナリ列を得るには、String#getBytes( Charset ) メソッド を使用します。

// "あいうえお" という文字列が UTF-8 エンコードされたバイナリ列
byte[] encodedStr = "あいうえお".getBytes( Charset.forName( "UTF-8" ) );

また、バイナリ列 (byte 型配列) を文字列に戻す場合には、String クラスのコンストラクタ String( byte[], Charset ) を使用します。

String str = new String( encodedStr, Charset.forName( "UTF-8" ) );

パーセントエンコード (The OAuth 1.0 Protocol 仕様)

The OAuth 1.0 Protocol (RFC5849) の仕様に合うように、文字列をパーセントエンコードします。 パーセントエンコードというのは、URL エンコードに用いられるように、エスケープすべき文字がある場合に、そのバイナリ列を 1 バイト毎に '%' + XX (XX は、バイト値を 16 進数表示したもの) の形式で表現するものです。

文字列をパーセントエンコードするには、まず文字列をエンコードしたバイナリ列を取得し、バイナリ列に対して処理を行います。 バイナリ列の取得は、上で述べた方法でできます。 また、バイナリ列に対して処理を行う際、byte 型の可変長配列が欲しいところですが、java.io.ByteArrayOutputStream クラス をその用途に用いることができます。

パーセントエンコードを行う機能を提供するクラスのサンプルコードを以下に示します。

package info.vividcode.oauth;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;

/**
 * The OAuth 1.0 Protocol の仕様に合う形で文字列をパーセントエンコードする
 * 機能を提供するクラス
 * @author nobuoka
 */
public class OAuthEncoder {
    
    /** "0123456789ABCDEF" の ASCII バイト列 */
    static final private byte[] BS = { 48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70 };
    /** 指定のバイトをパーセントエンコードする必要があるかどうかの真理値を格納した配列 
     * (インデックスがバイト値に対応. ただし最上位ビットが 1 のものは含まない) */
    static final private boolean[] NEED_ENCODE = new boolean[ 0x7F ];
    
    // NEED_ENCODING の初期化
    static {
        for( int i = 0; i < NEED_ENCODE.length; i++ ) {
            // a(97)-z(122), A(65)-Z(90), 0(48)-9(57), -(45), .(46), _(95), ~(126)
            if( ( 65 <= i && i <= 90 ) || ( 97 <= i && i <= 122) || 
                    ( 48 <= i && i <= 57 ) || i == 45 || i == 46 || i == 95 || i == 126 ) {
                NEED_ENCODE[i] = false;
            } else {
                NEED_ENCODE[i] = true;
            }
        }
    }
    
    /**
     * can't instantiate from this class
     */
    private OAuthEncoder() {}
    
    /** 
     * The OAuth 1.0 Protocol の仕様に合う形で文字列をパーセントエンコードする.
     * パーセントエンコードの対象になるのは 'A'-'Z', 'a'-'z', '0'-'9', '-', '.', '_', '~' を除く全ての文字である. 
     * @param str パーセントエンコードの対象文字列
     * @return str をパーセントエンコードした文字列
     */
    static public String encode( String str ) {
        String encodedStr = null;
        ByteArrayOutputStream os = null;
        try {
            os = new ByteArrayOutputStream();
            for( byte b : str.getBytes( Charset.forName( "UTF-8" ) ) ) {
                if( b < 0 || NEED_ENCODE[b] ) {
                    // "%"
                    os.write( 37 );
                    // 上の 4 ビット
                    os.write( BS[ ( b >> 4 ) & 0x0F ] );
                    // 下の 4 ビット
                    os.write( BS[ b & 0x0F ] );
                } else {
                    os.write( b );
                }
            }
            encodedStr = os.toString();
        } finally {
            try {
                // close する意味はないが, 一応
                if( os != null ) os.close();
            } catch( IOException err ) {
                err.printStackTrace();
            }
        }
        return encodedStr;
    }
    
}

次のように使うことができます。

String parcentEncodedStr = OAuthEncoder.encode( "あい" ); //=> "%E3%81%82%E3%81%84"

『増補改訂版 Java 言語で学ぶデザインパターン入門 マルチスレッド編』 結城 浩 著

マルチスレッドの基本を勉強するための本を探していたところ、知り合いに本書を薦められたので読んでみました。 もともと本書の存在は知っていて、デザインパターンの本ということでマルチスレッドの勉強には向かないんじゃないかと敬遠してたんですが、実際にはマルチスレッドの入門書としてすごく良い本でした。

マルチスレッドプログラミングで気をつけるべきところを体系的に学べる実践的入門書

本書はマルチスレッドに関するデザインパターンを説明した書籍です。 デザインパターンというと入門書よりもちょっと敷居が高い感じがしますが、そんなことはありません。 本書ではマルチスレッドプログラミングを行う際に 必ず使うであろう基本的な処理をパターン化したもの (具体的には、クリティカルセクションを同時に読み書きしないように排他処理をする、ということなど) から順に説明を行っていますので、マルチスレッドプログラミングの入門書として最適です。

マルチスレッドプログラミングをする際に気をつけるべきことと、実際にどう実装すればいいのか、ということをパターン化しており、実践的な内容となっています。 また、「なぜそれをする必要があるのか」 ということや 「もしそれをしなかったらどういう問題が起こるのか」 といったことを丁寧に説明していますので、マルチスレッドプログラミングの経験がない人でも理解しやすいと思います。 サンプルプログラムのソースコードのうち重要な部分を網掛けなどで強調しているのもわかりやすくて良かったです。

Java で使えるのはもちろんのこと Java 以外の言語でも有用

本書では Java を題材にしていますので、Java の基本的な部分は知っておかないと読み進めるのは困難です。 しかし、本書の内容は Java でしか使えないものではなく、どんな言語でもマルチスレッドプログラミングをする際には参考になるものです。 本書では 「Java ではどう実装するか」 を説明していますので、他の言語でどのように実装するかは自分で調べる必要がありますが、「マルチスレッドプログラミングをする際にどういうことを気をつければいいのか」 ということは本書を読んで学ぶことができます。

Java ではどう実装すればよいかという実践的な内容を説明しながら、言語に縛られないマルチスレッドに関する原則的な説明もしているのが本書の特徴だと思います。

java.util.concurrent パッケージの説明も

J2SE 5.0 ではマルチスレッドで動作させる際に便利なクラスライブラリ java.util.concurrent パッケージが追加されました。 本書ではこのクラスライブラリに含まれるクラスの使い方をサンプルプログラムにからめて説明しています。 (サンプルプログラムを java.util.concurrent パッケージのクラスを使って書き直すとどうなるか、など。)

本書を読んだだけで java.util.concurrent パッケージを使いこなせるようになるわけではありませんが、パッケージの一部のクラスの使い方を知れるだけでも意味のあることだと思います。

マルチスレッドプログラミングの入門者から初心者にオススメです

本書は 「デザインパターン入門」 と銘打たれているので、マルチスレッドプログラミングの経験者がデザインパターンを学ぶための本かと思ってしまいますが、実際にはマルチスレッドプログラミングの入門書にふさわしい内容になっています。 マルチスレッドプログラミングなんて経験したことがないという入門者や、マルチスレッドプログラミングはちょっとやったことがあるけどいまいちよくわかっていない、という初心者の人にオススメします。

W3C DOM 3 XPath の勧告に従った方法で XPath を評価する

Java を使用し、DOM 3 Load and Save Specification に従った方法で XML 文書を読み込み DOM を構築する方法は 昨日の記事 で書きました。

同じように DOM の勧告として DOM 3 XPath Specification があります。 この勧告は DOM ツリーを XPath を使って探索する際の API について記述しています。 今日はこの勧告に従った方法で XPath を評価するサンプルを書いておきます。

サンプル

Java SE 6 で実行し、動作することを確認しました。

package info.vividcode;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSParser;
import org.w3c.dom.xpath.XPathEvaluator;
import org.w3c.dom.xpath.XPathExpression;
import org.w3c.dom.xpath.XPathResult;

public class DOMXPathTest {
public static void main( String[] args ) {
try {

// ===== Load and Save に関する実装取得 =====
// DOMImplementationRegistry オブジェクトを取得 (ここから DOM 実装を取得できる)
DOMImplementationRegistry dir = DOMImplementationRegistry.newInstance();
// Load and Save 機能および XPath 機能を実装しているか,
// それらの機能と協調できる DOM 実装を取得
DOMImplementation imp = dir.getDOMImplementation("+LS 3.0 +XPath 3.0");
// 本来はここで imp が null でないかどうかチェックすべき
// (該当する DOM 実装が無ければ null になる)

// ===== XML 文書のパース =====
// Load and Save 機能を持つオブジェクトを取得
DOMImplementationLS impLS = (DOMImplementationLS) imp.getFeature("+LS", "3.0");
// LSInput クラスのインスタンスを取得
LSInput input = impLS.createLSInput();
// 読み込み元の XML として文字列を設定
input.setStringData(
"<test>1 つ目のテキストノード<t2>2 つ目のテキストノード</t2></test>" );
// LSParser クラスのインスタンス取得
LSParser parser = impLS.createLSParser(
DOMImplementationLS.MODE_SYNCHRONOUS, null );
// XML をパースして DOM Document オブジェクトを取得
Document doc = parser.parse( input );

// ===== XPath 式を実行 =====
// XPath 機能を持つオブジェクトを取得
XPathEvaluator evaluator = (XPathEvaluator) imp.getFeature("+XPath", "3.0");
// XPath 式を表すオブジェクトを生成
XPathExpression exp =
evaluator.createExpression( "string(//text())", null );
// XPath 式を実行し, 結果を取得
// 結果は第 2 引数で指定した型に自動的に変換される
// 変換しない場合は XPathResult.ANY_TYPE を指定すればよい
XPathResult res =
(XPathResult) exp.evaluate( doc, XPathResult.STRING_TYPE, null );
// 結果の文字列値を取得し, 表示
System.out.println( res.getStringValue() );
// => "1 つ目のテキストノード" と表示されるはず

} catch( Exception e ) {
e.printStackTrace();
}
}
}

解説

DOM 実装の取得

// Load and Save 機能および XPath 機能を実装しているか, 
// それらの機能と協調できる DOM 実装を取得
DOMImplementation imp = dir.getDOMImplementation("+LS 3.0 +XPath 3.0");

まずここで、"LS" (Load and Save) および "XPath" の機能を実装しているか、またはそれらの機能と協調できる DOM 実装を取得しています。 "LS" および "XPath" の前に "+" が付いていますが、これは 「DOM 実装自身が "LS" や "XPath" の機能を実装していなくても、協調できれば良い」 ということを表しています。 "+" を付けずに

DOMImplementation imp = dir.getDOMImplementation("LS 3.0 XPath 3.0");
とした場合は、DOM 実装自身が "LS" と "XPath" の機能を実装していなければなりませんので、敷居が高くなります。 私の環境の場合、"+" を付けなければ該当する DOM 実装はありませんでした。 基本的には機能名の前には "+" を付けると良いでしょう。

なお、機能名の後ろに "3.0" とありますが、これはその機能の版を表しています。 現在のところ "LS" と "XPath" の版は "3.0" のみです。

XML 文書の読み込み

XML 文書の読み込みに関しては 昨日の記事 を参照してください。 また、W3C DOM 3 Load and Save Specification も参照してください。

XPath 機能を持つオブジェクトを取得

// XPath 機能を持つオブジェクトを取得
XPathEvaluator evaluator = (XPathEvaluator) imp.getFeature("+XPath", "3.0");

ここで、DOM 実装から XPath 機能を持つオブジェクト (XPathEvaluator インターフェイスを実装しているオブジェクト) を取得しています。 getFeature メソッドでは、第 1 引数に機能名、第 2 引数にその版を指定します。 本来は第 1 引数の機能名の前に "+" を付ける必要はないはずなのですが、何故か私の環境では "+" を付けなければ null が返されました。 おそらくバグだと思われます。

XPath 式を表すオブジェクトを取得

// XPath 式を表すオブジェクトを生成
XPathExpression exp = evaluator.createExpression( "string(//text())", null );

次に createExpression メソッドを使って XPath 式を表すオブジェクト (XPathExpression インターフェイスを実装しているオブジェクト) を取得します。 第 1 引数が XPath 式 (文字列) で、第 2 引数は名前空間を解決するためのオブジェクトです。 名前空間を考えなくて良い場合は null で構いません。

XPath 式の実行

// XPath 式を実行し, 結果を取得
XPathResult res = (XPathResult) exp.evaluate( doc, XPathResult.STRING_TYPE, null );

ここで実際に XPath 式を実行します。 第 1 引数が XPath 式を評価するときの context node です。 XPath 式の中で相対パスを使っている場合は context node は重要になりますが、絶対パスを使っている場合は Document ノードで良いでしょう。 第 2 引数は、XPath 式の実行結果をどの型で評価するかを指定します。 ここで型を指定しておけば、自動的にその型に変換されます。 (今回の場合は、XPath 式の実行結果は String 型で、第 2 引数の指定も String 型なので変換されない。) 自動的な型変換をして欲しくない場合は XPathResult.ANY_TYPE を指定します。 第 3 引数は null で良いでしょう。

第 2 引数として指定できる定数にどのようなものがあるかは Interface XPathResult のドキュメントページ をご覧ください。

実行結果の取得

// 結果の文字列値を取得し, 表示
System.out.println( res.getStringValue() );

実行結果は (XPathResult インターフェイスを実装している) res に格納されています。 ここから値を取り出すためには各種 getter を使用します。 今回の場合は結果として文字列が得られていますので、getStringValue メソッドを使用してその文字列を取得できます。

その他にどのようなメソッドがあるかは Interface XPathResult のドキュメントページ をご覧ください。

Java で XML 文書を読み込んで DOM ツリーを構築するサンプル

XML 文書をツリー形式で扱うためのモデルとして DOM があり、W3C がその仕様を公開しています。 例えば DOM 3 Core Specification などです。

しかし、XML 文書を読み込んで DOM ツリーを構築したり、逆に DOM ツリーから XML 文書を出力したりするという部分については実装依存だと (今まで) 思っていました。 が、実はその部分 (内部処理ではなく API に関して) もちゃんと仕様がありました。

そしてちゃんと Java でも実装されているというからびっくりしました。 「Java DOM XML」 などのキーワードで検索して XML 文書をパースして DOM 木を取得する方法を調べると、検索の上位は javax.xml.parsers.DocumentBuilder クラス を使用する方法で占められているので今まで全く気づきませんでした。

せっかくなので javax.xml.parsers.DocumentBuilder クラス を使用して XML 文書をパースし javax.xml.transform.Transformer クラス を使用してシリアライズするサンプルと、W3C Load and Save の API を使ってパースおよびシリアライズするサンプルを置いておきます。

DocumentBuilder クラスおよび Transformer クラスを使用

XML 文書のパースに javax.xml.parsers.DocumentBuilder クラス を使用し、DOM ツリーを文字列に変換して出力するために javax.xml.transform.Transformer クラス を使用する方法です。

package info.vividcode;

import java.io.File;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;

public class DOMTest1 {
public static void main( String[] args ) {
try {

// ===== XML 文書のパース =====
// DocumentBuilder クラスのインスタンスを取得
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbf.newDocumentBuilder();
// パース
Document doc = builder.parse( new File("file.xml") );

// ===== DOM ツリーのシリアライズ =====
// Transformer クラスのインスタンスを取得
TransformerFactory tff = TransformerFactory.newInstance();
Transformer transformer = tff.newTransformer();
// シリアライズする DOM オブジェクト
DOMSource src = new DOMSource();
src.setNode( doc );
// シリアライズした結果を出力する先
StreamResult output = new StreamResult();
output.setOutputStream( System.out );
// シリアライズ
transformer.transform( src, output );

} catch ( Exception e ) {
e.printStackTrace();
}
}
}

DOM Load and Save の API を使用する方法

DOM Load and Save で規定されているインターフェイスを使用して XML 文書のパース、および DOM ツリーのシリアライズを行うサンプルです。

package info.vividcode;

import java.io.FileInputStream;

import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSParser;
import org.w3c.dom.ls.LSSerializer;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;

public class DOMTest2 {
public static void main( String[] args ) {
try {

// ===== Load and Save に関する実装取得 =====
// DOMImplementationLS のインスタンスを取得
DOMImplementationRegistry dir = DOMImplementationRegistry.newInstance();
DOMImplementation imp = dir.getDOMImplementation("+LS 3.0");
// 本来はここで imp が null でないかチェックすべき (該当する DOM 実装が無ければ imp は null)
DOMImplementationLS impLS = (DOMImplementationLS) imp.getFeature("+LS", "3.0");

// ===== XML 文書のパース =====
// LSInput クラスのインスタンスを取得し, 読み込み元設定
LSInput input = impLS.createLSInput();
input.setByteStream( new FileInputStream("file.xml") );
// LSParser クラスのインスタンス取得
LSParser parser = impLS.createLSParser(
DOMImplementationLS.MODE_SYNCHRONOUS, null );
// パース
Document doc = parser.parse(input);

// ===== DOM ツリーのシリアライズ =====
// LSOutput クラスのインスタンスを取得し, 出力先設定
LSOutput output = impLS.createLSOutput();
output.setByteStream( System.out );
output.setEncoding( "UTF-8" );
// LSSerializer クラスのインスタンス取得
LSSerializer serializer = impLS.createLSSerializer();
// シリアライズ
serializer.write( doc, output );

} catch( Exception e ) {
e.printStackTrace();
}
}
}

変更

      DOMImplementationLS impLS = 
(DOMImplementationLS) dir.getDOMImplementation("LS 3.0");
となっていたところを
      DOMImplementation   imp   = dir.getDOMImplementation("Core 3.0");
DOMImplementationLS impLS = (DOMImplementationLS) imp.getFeature("LS", "3.0");
と変更しました。 こちらの方が W3C DOM に従った形になっているはずです。 [2009.10.12 朝]

さらに

      DOMImplementation   imp   = dir.getDOMImplementation("+LS 3.0");
// 本来はここで imp が null でないかチェックすべき (該当する DOM 実装が無ければ imp は null)
DOMImplementationLS impLS = (DOMImplementationLS) imp.getFeature("+LS", "3.0");
と変更しました。 dir.getDOMImplementation("+LS 3.0") で、Load and Save 機能を実装している、または協調可能な DOM 実装を取得し、imp.getFeature("+LS", "3.0") で Load and Save 機能を実装しているオブジェクトを取得しています。 [2009.10.12 夜]