@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) を使用してコンパイルしてみたところ、まだコンパイラにバグがあるようで、可変長引数メソッドを参照するとコンパイルが通りませんでした。