Switch over type in java

后端 未结 8 1132
执念已碎
执念已碎 2020-12-02 14:23

Before I start, I know there are a bunch of answers to this question that suggest alternate approaches. I\'m looking for assistance to this particular approach as to whethe

相关标签:
8条回答
  • 2020-12-02 15:14

    Here's an example of this which uses a simple object for each case.

    package mcve.util;
    
    import java.util.*;
    import java.util.function.*;
    
    /**
     * Allows switch-like statements with classes and consumers.
     */
    public final class ClassSwitch implements Consumer<Object> {
        /**
         * For each of the specified cases, in order of their
         * appearance in the array, if cases[i].test(obj) returns
         * true, then invoke cases[i].accept(obj) and return.
         *
         * @param  obj   the object to switch upon
         * @param  cases the cases for the switch
         * @throws NullPointerException
         *         if any of the cases are null
         */
        public static void cswitch(Object obj, Case<?>... cases) {
            if (cases != null) {
                for (Case<?> c : cases) {
                    if (c.test(obj)) {
                        c.accept(obj);
                        break;
                    }
                }
            }
        }
    
        /**
         * @param  type   the type of the case
         * @param  action the action to perform
         * @param  <T>    the type of the case
         * @throws NullPointerException
         *         if the type or action is null
         * @return a new Case
         */
        public static <T> Case<T> ccase(Class<T> type, Consumer<? super T> action) {
            return new Case<>(type, action);
        }
    
        /**
         * @param <T> the type of the case
         */
        public static final class Case<T> implements Predicate<Object>,
                                                     Consumer<Object> {
            private final Class<T> type;
            private final Consumer<? super T> action;
    
            /**
             * @param  type   the type of the case
             * @param  action the action to perform
             * @throws NullPointerException
             *         if the type or action is null
             */
            public Case(Class<T> type, Consumer<? super T> action) {
                this.type   = Objects.requireNonNull(type,   "type");
                this.action = Objects.requireNonNull(action, "action");
            }
    
            /**
             * @param  obj the object to test
             * @return true if the object is an instance of T, else false
             */
            @Override
            public boolean test(Object obj) {
                return type.isInstance(obj);
            }
    
            /**
             * @param  obj the object to perform the action on
             * @throws ClassCastException
             *         if the object is not an instance of T
             */
            @Override
            public void accept(Object obj) {
                action.accept(type.cast(obj));
            }
        }
    
        /**
         * An unmodifiable list of the cases in this switch.
         */
        private final List<Case<?>> cases;
    
        /**
         * @param  cases the cases for this switch
         * @throws NullPointerException
         *         if any of the cases are null
         */
        public ClassSwitch(Case<?>... cases) {
            if (cases == null) {
                this.cases = Collections.emptyList();
            } else {
                List<Case<?>> list = new ArrayList<>(cases.length);
                for (Case<?> c : cases) {
                    list.add(Objects.requireNonNull(c, "case"));
                }
                this.cases = Collections.unmodifiableList(list);
            }
        }
    
        /**
         * @return an unmodifiable view of the cases in this switch
         */
        public List<Case<?>> getCases() { return cases; }
    
        /**
         * For each of the cases in this switch, in order of their
         * appearance in the list, if cases.get(i).test(obj) returns
         * true, then invoke cases.get(i).accept(obj) and return.
         *
         * @param obj the object to switch upon
         */
        @Override
        public void accept(Object obj) {
            for (Case<?> c : cases) {
                if (c.test(obj)) {
                    c.accept(obj);
                    break;
                }
            }
        }
    }
    

    A usage example would be something like this, assuming imports of e.g. import static mcve.util.ClassSwitch.*;:

    cswitch(anObject,
        ccase(Byte.class,    b -> System.out.println("Byte")),
        ccase(Short.class,   s -> System.out.println("Short")),
        ccase(Integer.class, i -> System.out.println("Integer")),
        ccase(Long.class,    l -> System.out.println("Long")),
        ccase(Float.class,   f -> System.out.println("Float")),
        ccase(Double.class,  d -> System.out.println("Double"))
    );
    

    You could also create a reusable object:

    ClassSwitch ts =
        new ClassSwitch(ccase(String.class, System.out::println),
                        ccase(Double.class, System.out::println));
    ts.accept(anObject);
    

    Notes:

    • If you want a default case, you can use Object.class as the last case.

    • There's no way to make a case which handles null, but it could be modified a little bit for that. You could e.g. make a class NullCase whose test method returns obj == null.


    What you could also do is actually generate overloads instead of using varargs. This lets you associate classes with consumers using just generic method declarations. The following is a fairly simple example of this:

    package mcve.util;
    
    import java.util.*;
    import java.util.function.*;
    
    /**
     * Allows switch-like statements with classes and consumers.
     */
    public final class GeneratedClassSwitch {
        private GeneratedClassSwitch() {}
    
        /**
         * Generates overloads for a class switch to System.out.
         *
         * For example, if max=4, then 5 methods are generated:
         * with 0, 1, 2, 3, and 4 cases.
         *
         * @param  max
         *         the number of cases in the largest overload generated
         * @param  indents
         *         the number of indents to indent each generated method
         * @throws IllegalArgumentException
         *         if max is negative or greater than 26, or if indents
         *         is negative
         */
        public static void generateFixedOverloads(int max, int indents) {
            if (max < 0 || max > 26) {
                throw new IllegalArgumentException("max=" + max);
            }
            String indent = String.join("", Collections.nCopies(indents, "    "));
    
            for (int i = 0; i <= max; ++i) {
                System.out.print(indent);
                System.out.print("public static ");
    
                if (i > 0) {
                    System.out.print("<");
    
                    for (char ch = 'A'; ch < 'A' + i; ++ch) {
                        if (ch != 'A') {
                            System.out.print(", ");
                        }
                        System.out.print(ch);
                    }
    
                    System.out.print("> ");
                }
    
                System.out.print("void cswitch");
    
                if (i > 0) {
                    System.out.println();
                    System.out.print(indent + "       (Object o, ");
    
                    for (char ch = 'A'; ch < 'A' + i; ++ch) {
                        if (ch != 'A') {
                            System.out.println(",");
                            System.out.print(indent + "                  ");
                        }
                        System.out.print("Class<" + ch + "> class" + ch);
                        System.out.print(", Consumer<? super " + ch + "> action" + ch);
                    }
                } else {
                    System.out.print("(Object o");
                }
    
                System.out.println(") {");
    
                for (char ch = 'A'; ch < 'A' + i; ++ch) {
                    if (ch == 'A') {
                        System.out.print(indent + "    ");
                    } else {
                        System.out.print(" else ");
                    }
                    System.out.println("if (class" + ch + ".isInstance(o)) {");
                    System.out.print(indent + "        ");
                    System.out.println("action" + ch + ".accept(class" + ch + ".cast(o));");
                    System.out.print(indent + "    ");
                    System.out.print("}");
                    if (ch == ('A' + i - 1)) {
                        System.out.println();
                    }
                }
    
                System.out.print(indent);
                System.out.println("}");
            }
        }
    
        // Generated code pasted below.
    
        public static void cswitch(Object o) {
        }
        public static <A> void cswitch
               (Object o, Class<A> classA, Consumer<? super A> actionA) {
            if (classA.isInstance(o)) {
                actionA.accept(classA.cast(o));
            }
        }
        public static <A, B> void cswitch
               (Object o, Class<A> classA, Consumer<? super A> actionA,
                          Class<B> classB, Consumer<? super B> actionB) {
            if (classA.isInstance(o)) {
                actionA.accept(classA.cast(o));
            } else if (classB.isInstance(o)) {
                actionB.accept(classB.cast(o));
            }
        }
        public static <A, B, C> void cswitch
               (Object o, Class<A> classA, Consumer<? super A> actionA,
                          Class<B> classB, Consumer<? super B> actionB,
                          Class<C> classC, Consumer<? super C> actionC) {
            if (classA.isInstance(o)) {
                actionA.accept(classA.cast(o));
            } else if (classB.isInstance(o)) {
                actionB.accept(classB.cast(o));
            } else if (classC.isInstance(o)) {
                actionC.accept(classC.cast(o));
            }
        }
        public static <A, B, C, D> void cswitch
               (Object o, Class<A> classA, Consumer<? super A> actionA,
                          Class<B> classB, Consumer<? super B> actionB,
                          Class<C> classC, Consumer<? super C> actionC,
                          Class<D> classD, Consumer<? super D> actionD) {
            if (classA.isInstance(o)) {
                actionA.accept(classA.cast(o));
            } else if (classB.isInstance(o)) {
                actionB.accept(classB.cast(o));
            } else if (classC.isInstance(o)) {
                actionC.accept(classC.cast(o));
            } else if (classD.isInstance(o)) {
                actionD.accept(classD.cast(o));
            }
        }
        public static <A, B, C, D, E> void cswitch
               (Object o, Class<A> classA, Consumer<? super A> actionA,
                          Class<B> classB, Consumer<? super B> actionB,
                          Class<C> classC, Consumer<? super C> actionC,
                          Class<D> classD, Consumer<? super D> actionD,
                          Class<E> classE, Consumer<? super E> actionE) {
            if (classA.isInstance(o)) {
                actionA.accept(classA.cast(o));
            } else if (classB.isInstance(o)) {
                actionB.accept(classB.cast(o));
            } else if (classC.isInstance(o)) {
                actionC.accept(classC.cast(o));
            } else if (classD.isInstance(o)) {
                actionD.accept(classD.cast(o));
            } else if (classE.isInstance(o)) {
                actionE.accept(classE.cast(o));
            }
        }
        public static <A, B, C, D, E, F> void cswitch
               (Object o, Class<A> classA, Consumer<? super A> actionA,
                          Class<B> classB, Consumer<? super B> actionB,
                          Class<C> classC, Consumer<? super C> actionC,
                          Class<D> classD, Consumer<? super D> actionD,
                          Class<E> classE, Consumer<? super E> actionE,
                          Class<F> classF, Consumer<? super F> actionF) {
            if (classA.isInstance(o)) {
                actionA.accept(classA.cast(o));
            } else if (classB.isInstance(o)) {
                actionB.accept(classB.cast(o));
            } else if (classC.isInstance(o)) {
                actionC.accept(classC.cast(o));
            } else if (classD.isInstance(o)) {
                actionD.accept(classD.cast(o));
            } else if (classE.isInstance(o)) {
                actionE.accept(classE.cast(o));
            } else if (classF.isInstance(o)) {
                actionF.accept(classF.cast(o));
            }
        }
        public static <A, B, C, D, E, F, G> void cswitch
               (Object o, Class<A> classA, Consumer<? super A> actionA,
                          Class<B> classB, Consumer<? super B> actionB,
                          Class<C> classC, Consumer<? super C> actionC,
                          Class<D> classD, Consumer<? super D> actionD,
                          Class<E> classE, Consumer<? super E> actionE,
                          Class<F> classF, Consumer<? super F> actionF,
                          Class<G> classG, Consumer<? super G> actionG) {
            if (classA.isInstance(o)) {
                actionA.accept(classA.cast(o));
            } else if (classB.isInstance(o)) {
                actionB.accept(classB.cast(o));
            } else if (classC.isInstance(o)) {
                actionC.accept(classC.cast(o));
            } else if (classD.isInstance(o)) {
                actionD.accept(classD.cast(o));
            } else if (classE.isInstance(o)) {
                actionE.accept(classE.cast(o));
            } else if (classF.isInstance(o)) {
                actionF.accept(classF.cast(o));
            } else if (classG.isInstance(o)) {
                actionG.accept(classG.cast(o));
            }
        }
        public static <A, B, C, D, E, F, G, H> void cswitch
               (Object o, Class<A> classA, Consumer<? super A> actionA,
                          Class<B> classB, Consumer<? super B> actionB,
                          Class<C> classC, Consumer<? super C> actionC,
                          Class<D> classD, Consumer<? super D> actionD,
                          Class<E> classE, Consumer<? super E> actionE,
                          Class<F> classF, Consumer<? super F> actionF,
                          Class<G> classG, Consumer<? super G> actionG,
                          Class<H> classH, Consumer<? super H> actionH) {
            if (classA.isInstance(o)) {
                actionA.accept(classA.cast(o));
            } else if (classB.isInstance(o)) {
                actionB.accept(classB.cast(o));
            } else if (classC.isInstance(o)) {
                actionC.accept(classC.cast(o));
            } else if (classD.isInstance(o)) {
                actionD.accept(classD.cast(o));
            } else if (classE.isInstance(o)) {
                actionE.accept(classE.cast(o));
            } else if (classF.isInstance(o)) {
                actionF.accept(classF.cast(o));
            } else if (classG.isInstance(o)) {
                actionG.accept(classG.cast(o));
            } else if (classH.isInstance(o)) {
                actionH.accept(classH.cast(o));
            }
        }
    }
    

    If you want to generate overloads, for example to have more than 8 cases, you can say something like the following:

    GeneratedClassSwitch.generateFixedOverloads(16, 1);
    

    That will generate methods to System.out that follow the general form of:

    public static <A, B, C> void cswitch
           (Object o, Class<A> classA, Consumer<? super A> actionA,
                      Class<B> classB, Consumer<? super B> actionB,
                      Class<C> classC, Consumer<? super C> actionC) {
        if (classA.isInstance(o)) {
            actionA.accept(classA.cast(o));
        } else if (classB.isInstance(o)) {
            actionB.accept(classB.cast(o));
        } else if (classC.isInstance(o)) {
            actionC.accept(classC.cast(o));
        }
    }
    

    Notice that we're able to map each class type to its associated consumer type, i.e. Class<A> with Consumer<? super A>, Class<B> with Consumer<? super B>, and so on. This is actually impossible with varargs (as of the current version of Java, anyway, which is 10).

    Our usage example is now like this, again assuming imports of e.g. import static mcve.util.GeneratedClassSwitch.*;:

    cswitch(anObject,
        Byte.class,    b -> System.out.println("Byte"),
        Short.class,   s -> System.out.println("Short"),
        Integer.class, i -> System.out.println("Integer"),
        Long.class,    l -> System.out.println("Long"),
        Float.class,   f -> System.out.println("Float"),
        Double.class,  d -> System.out.println("Double")
    );
    

    (Notes about default cases and null are the same as the first example.)

    0 讨论(0)
  • 2020-12-02 15:16

    Java currently has a draft to support this. See here. The syntax looks like this

    switch (obj) {
        case Integer i: handleI(i); break;
        case Byte b:    handleB(b); break;
        case Long l:    handleL(l); break;
        case Double d:  handleD(d); break;
        case String s:  handleS(s); break
        default:        handle(obj);
    }
    
    0 讨论(0)
提交回复
热议问题