昨日、Java 7th moon 富山が開催されました。参加者21名と小規模ながら、会場は非常に盛り上がりました。ああ、凄く楽しい。こんなに楽しいのは久しぶりだ。スピーカーをしてくださった皆さん、参加してくださった皆さん、本当にありがとうございます!
LT(ライトニングトーク)で昔のネタを引っ張り出してSemicolonless Javaをやったのだけど、LTなので時間もなく一番アツイ部分が伝えられなかったのでちょっと書いておこう。
歴史
Semicolonless Java(セミコロンレスJava)はJavaのサブセットで、Java言語からただひとつセミコロンを取り除いただけのプログラム言語だ。
この言語自体は1996年にJavaがリリースされたと同時に実装が存在したが、誰にも知られていなかった。この言語が発見されたのは2010/3/21で場所は熱海。java-ja温泉というイベントの2日目のことだった。
Pythonにワンライナーと呼ばれるものがあって(参考)西尾さんが第一人者なのだけど、端的に説明すれば、通常は改行が必須であるPythonにおいて、改行を行わず1行だけでどのようにプログラムを書くかという話題である。
Semicolonless JavaはこのPythonのワンライナーの流れを汲む。とはいえ、Javaはセミコロンを用いて1行にマルチステートメントでいくらでも記述できる。Javaにおけるワンライナー的なモノとは何か。それがセミコロンを用いないという縛りだった。
文と式
Pythonのワンライナーを書く上では「文:Statement」と「式:Expression」の区別が重要である。普段のプログラミングの中ではこうした言語仕様はあまり意識しないものだが、すごくざっくり説明すれば「文:Statement」は値を持たない。「式:Expression」は値を持つ、といったところ。この考え方はSemicolonless Javaにも通じる。
例えばJavaで
Object o = null;
とした場合のnullの部分。この部分に書けるものは何か?といったときに「値」を持つモノは書けるが、「値」を持たないものは書けないわけだ。この値を持つもの、持たないものはJavaの構文上は明確に区別される。それが、「文:Statement」と「式:Expression」なわけだ。
- ローカル変数宣言文
- 基本for文
- break文
- continue文
- return文
- throw文
- 式文 (式にセミコロンをつけて文としたもの)
はセミコロンが必要となる。つまりSemicolonless Javaでは利用できない。
- ブロック
- ローカルクラス宣言
- if文
- assert文
- switch文
- while文
- do文
- 拡張for文
- synchronized文
- try文
はセミコロンなしに用いることができる。
次に「式」は評価、つまり実行されたときに結果を表す。この結果というのは
- 変数
- 値
- なし(void)
の3種類があってJava言語仕様3版15章「式」から引用すると
式文(§14.8)以外のすべてのコンテキストでは,その式が何かを表していなければならないため,何も表さない式は式文でしか使用することができない
例えばメソッド呼び出しは「式」なのだが、戻り値がvoidのメソッドの場合、代入の右辺などで用いることができない。
List list = new ArrayList(); Object o = list.clear(); // コンパイルエラー
つまり、Javaという言語の構文的にセミコロンなしで使用可能なのはセミコロンなしで用いれるいくつかの文と、void以外の式ということになる。いかに「式」でプログラミングするかというのがテーマになるわけだ。
メソッドを作る
Javaでメソッド呼び出しは「式」なのだけども、戻り値がvoidのメソッドは式文でしか使えない。そして式文はセミコロンが必要。つまり、戻り値がvoidのメソッドはSemicolonless Javaでは呼び出すことができないことを意味する。*2
そして戻り値があるメソッドの場合は、return文をセミコロンなしでは記述できないため、そもそもメソッドを定義することができない。この制約のため、ネイティブなJavaのinterfaceを実装することがほぼ不可能であり、それによって利用できるJavaのAPIは大きく制限される。
Semicolonless JavaではJava言語でのメソッドは戻り値がvoidのものだけ宣言することができ、そしてそれを呼び出すことはできないという状況にある。この制約下でどのように構造化プログラミングをするのか?
Java言語では構造化プログラミングのためには当然ながらメソッドを用いる。そのための言語機構なのだから。だが、それが使えない以上、他の方法でメソッドの代用としなければならない。
そこで編み出されたのが、コレクションを継承したクラスとコンストラクタでメソッドの代用とする手法だ。
public class Hoge extends java.util.ArrayList<String> { public Hoge(String in) { if (this.add(in)){} } }
ArrayList<String> を継承した Hogeクラスがある。このコンストラクタがSemicolonless Javaでの「メソッド」なのだ。this.add()で自身のインスタンスに「戻り値」をadd()する。すると、このHogeクラスのnewしてからget()すると値を取り出すことができるという寸法なのだ。
for(Hoge hoge : new Hoge[]{new Hoge("in")}) { if (null == System.out.printf(hoge.get(0))){} }
このSemicolonless Javaでの「メソッド」を作る際にextendsする型は何がよいだろうか。ここではArrayListを用いたが、Mapを用いてキーにenumを用いると型システム的に綺麗ではある。値を複数返す必要がないのであればSoftReferenceあたりが使い勝手が良いかもしれないなぁと思ったりもしたが、メソッド呼び出しから値を取得するまでにGCにソフト参照が回収される可能性が否定しきれない。JavaのAPIを探せばより手頃なクラスを見つけられるかもしれない。今後の研究が待たれるところである。
オブジェクト指向への道のり
さて、僕らはSemicolonless Javaで構造化プログラミングする技法を手にした。この「メソッド」―Javaでいうクラス宣言だが―を内部クラスとしてまとめてみよう。
/** 自身をMapとすることでインスタンスフィールドを表現する */ public class ClassSample extends java.util.HashMap<ClassSample.Key, Object> { /** インスタンスフィールドにアクセスするキー */ enum Key { FIELD_1, FIELD_2, } /** * メソッドA. * @return String型 */ public class MethodA extends java.util.ArrayList<String> { /** 引数なし */ public MethodA() {/*省略*/} /** オーバーロードメソッド */ public MethodA(int i) {/*省略*/} } /** メソッドB */ public class MethodB extends java.util.ArrayList<String> { public MethodB(String str) { if (null == ClassSample.this.put(Key.FIELD_1, str)){} } } }
この ClassSample ではJavaのクラスのようなものを表現してみた。自身をMapとすることでインスタンスフィールドを表現し、内部クラスでメソッドを表現する。メソッドBではメソッド内からインスタンスフィールドに値を格納してみせている。
しかし、これはオブジェクト指向のクラス足り得ない。このクラスを継承したクラスを作ってもポリモーフィズムが働かないのだ。Semicolonless Javaのメソッド呼び出しはJavaでいうnewなわけだが、これは静的に定まりポリモーフィズムしない。
そこでポリモーフィズムする機構を考えてやる必要がある。つまりnewを動的にしたいわけだから、答えはリフレクションだ。
まず、継承したクラスを作っておこう。
public class ExClassSample extends ClassSample { /** * メソッドAのオーバーライド */ public class MethodA extends java.util.ArrayList<String> { /** 引数なし */ public MethodA() {/*省略*/} /** オーバーロードメソッド */ public MethodA(int i) {/*省略*/} } }
そして、このクラスのメソッドAを呼び出すわけだが、リフレクションを使って呼び出すようにする。
for (ClassSample c : new ClassSample[]{new ClassSample(), new ExClassSample()}) { if (null==Class.forName(c.getClass().getName()+"$MethodA") .getConstructor(c.getClass()).newInstance(c)){} }
このようにすると、最初のループではClassSample$MethodAが動き、2回目ではExClassSample$MethodAが動くようになるわけである。