投稿日:

    カテゴリー 技術 / デザイン / 制作

    【Java】ラムダ式があればswitchいらないんじゃね?

    こんにちは、
    コーヒーは一日一杯まで。神尾です。

    switch文が嫌いなプログラマーは多いと思います。

    Javaのswitchは列挙型比較(※)なのでphpと比べたら全然ましですけど、
    しかし何ともいえないこの食い合わせの悪さ。

    breakの付け忘れでバグ。
    インデントが見にくい。

    あと、「switch-case文」とかいうけど、文はswitchだけで、case、貴様はただのラベルだ。

    なので今日は普段あんまり使わないラムダ式を使って、
    switch文みたいなクラスを作るよ。

    ※列挙型…enumともいう。定数の配列みたいな何か。
    switchでは、Stringのように見えても列挙型にコンパイルされている。
    nullを突っ込むとエラーになるのはそのせい。

    Switchクラス作ってみた

    import java.util.function.Consumer;
    
    public class Switch<T, R> {
        private T comparison;
        private R controlee;
        private boolean end;
    
        private Switch(T comparison, R controlee) {
            this.comparison = comparison;
            this.controlee = controlee;
        }
    
        private void consume(Consumer<R> consumer) {
            if(!end) {
                if(consumer != null) {
                    consumer.accept(controlee);
                }
                end = true;
            }
        }
    
        public static <T, R> Switch swit(T comparison, R controlee) {
            Switch swit = new Switch<T, R>(comparison, controlee);
            swit.init();
            return swit;
        }
    
        public void init() {
            end = false;
        }
    
        public Switch when(T target, Consumer<R> consumer) {
            if(target.equals(comparison)) {
                consume(consumer);
            }
            return this;
        }
    
        public Switch defa(Consumer<R> consumer) {
            consume(consumer);
            return this;
        }
    
    }

    何かあんまりかっこよくないけど、コンパイル通ったからいいか。

    ラムダ型の1つであるConsumerを使っています。

    ラムダ型は抽象メソッドが1つだけのインターフェースのこと。
    代表的なのはRunnableだけど、これは引数も戻り値もない、走るしかできん子。

    java.util.functionパッケージにはラムダ型がいろいろ入ってるから、
    引数や戻り値の数によって好きなのを選ぶといいよ、って感じ。

    詳しくはこちら。

    使い方

    class Test {
    
        public static void main(String args[]){
            String bread = "croissant";
            BreadEater breadEater = new BreadEater();
    
            Switch.swit(bread, breadEater)
                    .when("shokupan", (obj)->{
                        obj.message("mochi mochi");
                    })
                    .when("coupe", (obj)->{
                        obj.message("fuwa fuwa");
                    })
                    .when("croissant", (obj)->{
                        obj.message("saku saku");
                    })
                    .defa((obj)->{
                        obj.message("hara hetta");
                    });
    
            breadEater.talk();
        }
    
        static class BreadEater {
            private String message;
    
            void message(String message) {
                this.message = message;
            }
    
            void talk() {
                System.out.println(message);
            }
        }
    
    }

    (obj)->{
    //処理
    }
    の部分がラムダ式。
    使い慣れないけど、無名クラスの宣言を省略してるだけ。

    無名クラスも同じだけど、ラムダ式の中ではラムダ式の外で宣言されたfinal以外のローカル関数は使えない。
    だから処理用のクラスを用意して、Consumerに渡している。

    なんだかもう十分まどろっこしいけど、コンパイラは通らない。

    Test.java:9: エラー: タイプObjectのmessage(String)を見つけられません
                        obj.message("mochi mochi");
                           ^
    

    えー?なんでObjectでConsumerに渡ってるの?
    型宣言しなかったから・・・?

            Switch.swit(bread, breadEater)
    ⇓
            Switch.<String, BreadEater>swit(bread, breadEater)
    

    ジェネリックメソッドであるswitを、明示的に型宣言して実行。
    でも、これもだめ。

    しょうがないので、最終的にこうなった。

            Switch.swit(bread, breadEater)
                    .when("shokupan", (obj)->{
                        ((BreadEater)obj).message("mochi mochi");
                    })
                    .when("coupe", (obj)->{
                        ((BreadEater)obj).message("fuwa fuwa");
                    })
                    .when("croissant", (obj)->{
                        ((BreadEater)obj).message("saku saku");
                    })
                    .defa((obj)->{
                        ((BreadEater)obj).message("hara hetta");
                    });
    

    ちゃんと動いたけど、カッコ悪いなあ。

    やっぱりswitch使おうかなあ、トホホ。

    追記

    メソッドの戻り値にジェネリックの型を指定するのを忘れてました!!

    -    public static <T, R> Switch swit(T comparison, R controlee) {
    +    public static <T, R> Switch<T, R> swit(T comparison, R controlee) {
    
    ---
    
    -    public Switch when(T target, Consumer consumer) {
    +    public Switch<T, R> when(T target, Consumer consumer) {
    
    ---
    
    -    public Switch defa(Consumer consumer) {
    +    public Switch<T, R> defa(Consumer consumer) {
    

    これでバッチリ。