Подтвердить что ты не робот

Тип переключения в java

Прежде чем начать, я знаю, что есть несколько ответов на этот вопрос, которые предлагают альтернативные подходы. Я ищу помощь в этом конкретном подходе в отношении того, возможно ли, а если нет, аналогичные подходы, которые могут работать.

У меня есть метод, который принимает суперкласс и вызывает метод, основанный на типе переданного объекта. например:

public void handle(Object o){
  if (o instanceof A)
    handleA((A)o);
  else if (o instanceof B)
    handleB((B)o);
  else if (o instanceof C)
    handleC((C)o);
  else 
    handleUnknown(o);

Я не могу изменить подтипы, чтобы переопределить метод handle(), поскольку этот ответ предложил бы, потому что я не владею классами. Таким образом, метод instanceof - это все, что у меня есть.

Я хотел бы использовать оператор switch вместо if/else, просто потому, что он намного опрятен. Я знаю, что вы можете включать только примитивы и строки, поэтому я перехожу к имени класса:

switch(o.getClass().getCanonicalName()){
case "my.package.A":
  handleA((A)o);
  break;
case "my.package.B":
  handleB((B)o);
  break;
case "my.package.C":
  handleC((C)o);
  break;
default:
  handleUnknown(o);
  break;
}

Ловушка здесь в том, что канонические имена ОЧЕНЬ долго (например, 12 подпакетов), и я не могу вызывать ClassName.class.getCanonicalName() в аргументе case, потому что Java этого не допускает. Итак, моим следующим решением был Enum. Здесь я ударил по моей проблеме.

Я хочу, чтобы мой код выглядел примерно так:

public enum Classes {
  A (A.getCanonicalName()),
  B (B.getCanonicalName()),
  C (C.getCanonicalName());
}

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

Но это не компилируется. Я не знаю, почему. Мне нужен какой-то подход, который позволяет мне переключать тип без ввода всего канонического имени. Если я это сделаю, я бы просто использовал if/else и instanceof.

ПРИМЕЧАНИЕ Существует несколько типов, имеющих одно и то же имя (внутренние классы), поэтому getSimpleName() отсутствует.

4b9b3361

Ответ 1

Вот подход, который вообще не относится к именам классов, и отправляет так быстро, как это делает оператор switch: сделать хэш-карту для отображения объектов Class<T> для обработчиков класса и использовать вместо этого карту от 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 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
}

Ответ 2

Используя java 8 lambdas, вы можете получить что-то вроде этого:

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

Чтобы сделать это, вы должны определить следующие статические методы:

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);
}    

Ответ 3

Оператор instanceof - это простой подход, когда вы не владеете классами. Выражение instanceof истинно, когда объектом является данный класс или подкласс.

Вы отмечаете, что вы не владеете классами. Владелец мог вводить подклассы в последующем выпуске. Скажем, владелец вводит APlus в качестве подкласса A. Экземпляр APlus - это код A., который работает на A, должен также работать над APlus. Если вы используете instanceof, ваш код будет продолжать работать - без усилий от вас. Если вы используете имена классов, это провалится - без уведомления вашего компилятора.

Если вы многократно включаете один и тот же объект, вам может оказаться полезным обернуть объект один раз в класс-оболочку, который реализует интерфейс. После этого вы можете просто вызвать методы на интерфейсе - без if, switch или карты.

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);
}

Даже при гарантированном отсутствии подклассов избегайте указывать имена классов как литералы в случаях switch. Это позволяет ошибки, которые компилятор не найдет, что может стоить вам время отладки.

Ответ 4

Вы были очень близки к решению с перечислениями. Он не скомпилирован, потому что ваш enum пропустил конструктор и метод coversion для отображения перечисления из String. На самом деле вы можете решить его даже без String, то есть без вызова getCanonicalName:

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;
}

если вы получаете значительное количество известных классов, рассмотрите возможность использования карты вместо итерации в Classes.fromClass, например:

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;
  }
}

Ответ 5

Мне удалось работать с 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(){

    }


}

Ответ 6

Чтобы переключить известные типы классов, вы можете использовать подход ниже

Создайте Enum с именами классов.

public enum ClassNameEnum {
    ClassA, ClassB, ClassC
}

Найдите имя класса объекта. Напишите регистр коммутатора над перечислением.

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;
        }
    }
}

Ответ 7

Вот пример этого, который использует простой объект для каждого случая.

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;
            }
        }
    }
}

Пример использования будет примерно таким, если импортировать импорт 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"))
);

Вы также можете создать объект многократного использования:

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

Заметки:

  • Если вы хотите использовать случай по default, вы можете использовать Object.class в качестве последнего случая.

  • Там нет способа сделать случай, который обрабатывает null, но для этого он может быть немного изменен. Вы могли бы, например, сделать class NullCase чей test метод возвращает obj == null.


То, что вы могли бы также сделать, это генерировать перегрузки вместо использования varargs. Это позволяет связать классы с потребителями, используя только общие объявления методов. Ниже приведен довольно простой пример:

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));
        }
    }
}

Если вы хотите генерировать перегрузки, например, чтобы иметь более 8 случаев, вы можете сказать примерно следующее:

GeneratedClassSwitch.generateFixedOverloads(16, 1);

Это будет генерировать методы System.out которые следуют общей форме:

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));
    }
}

Обратите внимание, что мы можем отобразить каждый тип класса к связанному с потребительским типа, т.е. Class< A > с Consumer<? super A > Consumer<? super A >, Class< B > с Consumer<? super B > Consumer<? super B > и т.д. Это на самом деле невозможно с varargs (как в текущей версии Java, так или иначе, что равно 10).

Пример нашего использования теперь выглядит следующим образом, опять же при импорте импорта 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")
);

(Заметки о случаях по default и null совпадают с первым примером.)