Тип переключения в 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()
отсутствует.
Ответы
Ответ 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
совпадают с первым примером.)