Switch over type in java

£可爱£侵袭症+ 提交于 2019-11-28 04:42:51

Here is an approach that does not deal with class names at all, and dispatches as fast as a switch statement does: make a hash map to map Class<T> objects to class-specific handlers, and use the map instead of a switch:

// Declare an interface for your polymorphic handlers to implement.
// There will be only anonymous implementations of this interface.
private interface Handler {
    void handle(Object o);
}
// Make a map that translates a Class object to a Handler
private static final Map<Class,Handler> dispatch = new HashMap<Class,Handler>();
// Populate the map in a static initializer
static {
    dispatch.put(A.class, new Handler() {
        public void handle(Object o) {
            handleA((A)o);
        }
    });
    dispatch.put(B.class, new Handler() {
        public void handle(Object o) {
            handleB((B)o);
        }
    });
    dispatch.put(C.class, new Handler() {
        public void handle(Object o) {
            handleC((C)o);
        }
    });
}
// This object performs the dispatch by looking up a handler,
// and calling it if it's available
private static void handle(Object o) {
    Handler h = dispatch.get(o.getClass());
    if (h == null) {
        // Throw an exception: unknown type
    }
    h.handle(o); // <<== Here is the magic
}

Using java 8 lambdas you can get to something like this:

Collection col = Arrays.asList(1,2,3);
switchType(col, 
       caze(Collection.class, c->System.out.println(c.size())),
       caze(ArrayBlockingQueue.class, bq->System.out.println(bq.remainingCapacity())),
       caze(Queue.class, q->System.out.println(q.poll())),
       caze(String.class, s->System.out.println(s.substring(0))),
       caze(ArrayList.class, al->System.out.println(al.get(0)))
);

In order to do that you should define the following static methods:

public static <T> void switchType(Object o, Consumer... a) {
    for (Consumer consumer : a)
        consumer.accept(o);
}

public static <T> Consumer caze(Class<T> cls, Consumer<T> c) {
    return obj -> Optional.of(obj).filter(cls::isInstance).map(cls::cast).ifPresent(c);
}    

The instanceof operator is a simple approach, when you don't own the classes. An instanceof expression is true when the object is the given class or a subclass.

You mention that you don't own the classes. The owner could introduce subclasses in a subsequent release. Say the owner introduces APlus as a subclass of A. An instance of APlus is an A. Code that works on an A should also work on an APlus. If you use instanceof, your code would continue to work -- without effort from you. If you use class names, it would fail -- without notice from your compiler.

If you're repeatedly switching on the same object, you might find it useful to wrap the object once in a wrapper class that implements an interface. Thereafter, you can simply call methods on the interface -- with no if, switch, or map.

public interface IWrapper {
    public void handle();
    public String describe();
}

public AWrapper implements IWrapper { ... }
public BWrapper implements IWrapper { ... }
public CWrapper implements IWrapper { ... }
public UnknownWrapper implements IWrapper { ... }

IWrapper wrap( Object o ) {
    if ( o instanceof A ) return new AWrapper((A) o);
    else if ( o instanceof B ) return new BWrapper((B) o);
    else if ( o instanceof C ) return new CWrapper((C) o);
    else return new UnknownWrapper(o);
}

Even in the guaranteed absence of subclasses, avoid specifying class names as literal strings in switch cases. This allows errors that the compiler will not find, which may cost you debugging time.

You was very close to the solution with enums. It hasn't compiled because your enum missed constructor and coversion method to map enum from String. Actually you could solve it even without String, i.e. without calling getCanonicalName at all:

public enum Classes {
  // changed enum constants a bit to avoid confusing with target class names
  ClsA (A.class),
  ClsB (B.class),
  ClsC (C.class),
  UNKNOWN(null);
  private final Class<?> targetClass;
  Classes(Class<?> targetClass) {
    this.targetClass = targetClass;
  }
  public static Classes fromClass(Class<?> cls) {
    for(Classes c : values()) {
      if(c.targetClass == cls)
         return c;
    }
    return UNKNOWN;
  }
}

switch (Classes.fromClass(o.getClass())) {
case ClsA:
  handleA((A)o);
  break;
case ClsB:
  handleB((B)o);
  break;
case ClsC:
  handleC((C)o);
  break;
default:
  handleUnknown(o);
  break;
}

if you get significant count of known classes, consider using map instead of iterating in Classes.fromClass, e.g.:

public enum Classes {
  ClsA(A.class),
  ClsB(B.class),
  // etc...
  UNKNWON(null);

  // need a wrapper class to avoid compilation problem
  // with referring static enum field within an initializer 
  private static class Holder {
    public static final IdentityHashMap<Class<?>, Classes> map = new IdentityHashMap<>();
  }
  Classes(Class<?> targetClass) {
    Holder.map.put(targetClass, this);
  }
  public static Classes fromClass(Class<?> cls) {
    Classes c = Holder.map.get(cls);
    return c != null ? c : UNKNOWN;
  }
}

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

switch (obj) {
    case Integer i: handleI((I)obj); break;
    case Byte b:    handleB((B)obj); break;
    case Long l:    handleL((L)obj); break;
    case Double d:  handleD((D)obj); break;
    case String s:  handleS((S)obj); break
    default:        handle(obj);
}

I was able to work around with java.lang.reflect

import java.lang.reflect.Method;

public class MyClass {

    public void validate(Object o) {    
        String className = o.getClass().getSimpleName();     
        try {
            //this line searches a method named as className
            Method m = this.getClass().getDeclaredMethod(className);
            //this line execute the method 
             m.invoke(this);
        } catch (Exception e) {
            e.printStackTrace();
            handleUnknown();
        }

    }

    //this methot will execute if the object o is instance of A
    public void A() {

    }
    //this methot will execute if the object o is instance of B
     public void B() {

    }
    //this methot will execute if the object o is instance of C
     public void C() {

    }
    //this methot will execute if the method is unknown
    public void handleUnknown(){

    }


}

To switch over known class types you can use below approach

Create an Enum with Class names.

public enum ClassNameEnum {
    ClassA, ClassB, ClassC
}

Find the Class name of the object. Write a switch case over the enum.

private void switchByClassType(Object obj) {

        ClassNameEnum className = ClassNameEnum.valueOf(obj.getClass().getSimpleName());

        switch (className) {
            case ClassA:
                doA();
                break;
            case ClassB:
                doB();
                break;
            case ClassC:
                doC();
                break;
        }
    }
}

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.)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!