« Back to Home

Re: Javaでのパターンマッチを考える

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