Switch over type in java

后端 未结 8 1131
执念已碎
执念已碎 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 14:59

    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.

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

    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(){
    
        }
    
    
    }
    
    0 讨论(0)
  • 2020-12-02 15:07

    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
    }
    
    0 讨论(0)
  • 2020-12-02 15:10

    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);
    }    
    
    0 讨论(0)
  • 2020-12-02 15:12

    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;
      }
    }
    
    0 讨论(0)
  • 2020-12-02 15:13

    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;
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题