@kis さんのエントリ「Javaでのパターンマッチを考える」がとても興味深かったので、私も書いてみました。
ユーティリティクラスを使って解決することにしたらどうでしょう?
Scalaの記法をよく見て、なるべく似たような形になるようにクラス構造を考えました。
caseなどのキーワードに当たる部分はstaticメソッドで乗り切り、マッチした際に実行されるコードブロックはインナークラスを使って表現します。
package patternmatch;
import java.util.Arrays;
public class PatternMatcher {
public static PatternMatcher subject(Object... objects) {
return new PatternMatcher(objects);
}
public static Case case_(Object... objects) {
return new Case(objects);
}
public static final Case default_ = new Case() {
@Override
boolean matches(Object[] objects) {
return true;
}
};
public static final WildCard _ = new WildCard();
private final Object[] objects;
PatternMatcher(Object... objects) {
this.objects = objects;
}
public void match(CaseBlock... caseBlocks) {
for (CaseBlock caseBlock : caseBlocks) {
Case c = caseBlock.getCase();
if (c.matches(objects)) {
caseBlock.getBlock().apply();
return;
}
}
}
// コンパイル用(本来不要)
public void match(CaseBlock cb1, CaseBlock cb2,
CaseBlock cb3, CaseBlock cb4) {
match(new CaseBlock[]{cb1, cb2, cb3, cb4});
}
public static class Case {
private final Object[] objects;
Case(Object... objects) {
this.objects = objects;
}
public CaseBlock exec(Block b) {
return new CaseBlock(this, b);
}
boolean matches(Object[] objects) {
// ちょっと省略
return Arrays.equals(this.objects, objects);
}
}
public static interface Block {
public void apply();
}
static class CaseBlock {
private final Case c;
private final Block b;
public CaseBlock(Case c, Block b){
this.c = c;
this.b = b;
}
public Case getCase() {
return c;
}
public Block getBlock() {
return b;
}
}
static class WildCard {
@Override
public boolean equals(Object obj) {
return true;
}
}
}
このPatternMatcherクラスを使ってFizzBuzzを書くとこうなります。
package patternmatch.use;
import static java.lang.System.out;
import static patternmatch.PatternMatcher.*;
import patternmatch.PatternMatcher.Block;
public class Test {
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
final int j = i;
subject(j % 3, j % 5).match(
case_(0, 0).exec(new Block(){
@Override
public void apply() {
out.println("fizzbuzz");
}}),
case_(0, _).exec(new Block(){
@Override
public void apply() {
out.println("fizz");
}}),
case_(_, 0).exec(new Block(){
@Override
public void apply() {
out.println("buzz");
}}),
default_.exec(new Block(){
@Override
public void apply() {
out.println(j);
}})
);
}
}
}
これではあまり嬉しくないのですが、JavaSE8のLambdaを使えば以下のように書けます。
package patternmatch.use;
import static java.lang.System.out;
import static patternmatch.PatternMatcher.*;
public class Test2 {
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
final int j = i;
subject(j % 3, j % 5).match(
case_(0, 0).exec(()-> out.println("fizzbuzz") ),
case_(0, _).exec(()-> out.println("fizz") ),
case_(_, 0).exec(()-> out.println("buzz") ),
default_.exec(()-> out.println(j) )
);
}
}
}
おお、多少のムリヤリ感は拭えないまでも、Lambdaで書くとかなり記述量が減って良い感じですね!
ちなみに、執筆時点で最新のOpenJDK with Lambda support (lambda-8-b69-macosx-x86_64-17_dec_2012) を使用してコンパイルしてみたところ、まだコンパイラにバグがあるようで、可変長引数メソッドを参照するとコンパイルが通りませんでした。