在java中切换类型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29570767/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Switch over type in java
提问by ewok
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 whether it is possible, and if not, similar approaches that might work.
在我开始之前,我知道这个问题有很多建议替代方法的答案。我正在寻求对这种特定方法的帮助,以了解它是否可行,如果不可行,类似的方法是否可行。
I have a method that takes a superclass and calls a method based on the type of the passed object. for instance:
我有一个方法,它接受一个超类并根据传递的对象的类型调用一个方法。例如:
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);
I can't modify the subtypes to override a handle()
method, as this answerwould suggest, because I don't own the classes. So the instanceof
method is all I have.
我不能修改子类型来覆盖handle()
方法,正如这个答案所暗示的那样,因为我不拥有这些类。所以instanceof
方法就是我所拥有的。
I'd like to use a switch
statement instead of if/else
, simply because it's much neater. I know you can only switch on primitives and Strings, so I'm switching over the class name:
我想使用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;
}
The catch here is that the canonical names are VERY long (like 12 subpackages), and I can't call ClassName.class.getCanonicalName()
in the case statement because Java doesn't allow that. So my next solution was an Enum. This is where I hit my problem.
这里的问题是规范名称非常长(如 12 个子包),我不能ClassName.class.getCanonicalName()
在 case 语句中调用,因为 Java 不允许这样做。所以我的下一个解决方案是枚举。这就是我遇到问题的地方。
I'd like my code to look something like this:
我希望我的代码看起来像这样:
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;
}
But this doesn't compile. I'm not sure why. I'd like some approach that allows me to switch over the type without having to type out the entire canonical name. If I do that, I might as well just use if/else
and instanceof
.
但这不能编译。我不知道为什么。我想要一些允许我切换类型而不必输入整个规范名称的方法。如果我这样做,我还不如使用if/else
and instanceof
。
NOTEThere are a couple of types that have the same name (inner classes), so getSimpleName()
is out.
注意有几种类型具有相同的名称(内部类),所以getSimpleName()
不在了。
回答by dasblinkenlight
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
:
这是一种根本不处理类名称的方法,并且与switch
语句一样快地分派:制作散列映射以将Class<T>
对象映射到特定于类的处理程序,并使用映射而不是 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
}
回答by Andy Thomas
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.
instanceof
当您不拥有这些类时,运算符是一种简单的方法。一个instanceof
当对象是给定类表达式为真或子类。
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.
您提到您不拥有这些课程。所有者可以在后续版本中引入子类。假设所有者将 APlus 引入为 A 的子类。APlus 的一个实例是 A。适用于 A 的代码也应该适用于 APlus。如果您使用instanceof
,您的代码将继续工作——无需您付出任何努力。如果您使用类名,它将失败——编译器不会通知您。
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.
如果您反复切换同一个对象,您可能会发现将对象包装在实现接口的包装类中一次很有用。此后,你可以简单地调用接口上的方法-无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);
}
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.
即使在保证没有子类的情况下,也要避免在switch
case中将类名指定为文字字符串。这会导致编译器找不到的错误,这可能会花费您调试时间。
回答by eitan
Using java 8 lambdas you can get to something like this:
使用 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)))
);
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);
}
回答by Ramiz
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:
您非常接近枚举的解决方案。它尚未编译,因为您的枚举错过了从字符串映射枚举的构造函数和覆盖方法。实际上,即使没有 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;
}
if you get significant count of known classes, consider using map instead of iterating in Classes.fromClass, e.g.:
如果您获得大量已知类,请考虑使用 map 而不是在 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;
}
}
回答by Edgar
I was able to work around with java.lang.reflect
我能够解决 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(){
}
}
回答by Siva Kumar
To switch over known class types you can use below approach
要切换已知的类类型,您可以使用以下方法
Create an Enumwith Class names.
创建一个带有类名的枚举。
public enum ClassNameEnum {
ClassA, ClassB, ClassC
}
Find the Class nameof the object. Write a switchcase over the enum.
找到对象的类名。在枚举上写一个switchcase。
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;
}
}
}
回答by Radiodef
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.*;
:
一个使用示例将是这样的,假设导入例如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 useObject.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 aclass NullCase
whosetest
method returnsobj == null
.
如果你想要一个
default
案例,你可以使用Object.class
最后一个案例。没有办法制作处理 的案例
null
,但可以为此稍作修改。例如,您可以创建一个class NullCase
whotest
方法返回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:
如果你想生成重载,例如超过 8 个 case,你可以这样说:
GeneratedClassSwitch.generateFixedOverloads(16, 1);
That will generate methods to System.out
that follow the general form of:
这将生成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));
}
}
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).
请注意,我们能够将每个类类型映射到其关联的使用者类型,即
with 、with等等。这对于可变参数实际上是不可能的(从 Java 的当前版本开始,无论如何,它是 10)。Class<A>
Consumer<? super A>
Class<B>
Consumer<? super B>
Our usage example is now like this, again assuming imports of e.g. import static mcve.util.GeneratedClassSwitch.*;
:
我们的使用示例现在是这样的,再次假设导入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.)
(关于default
案例的说明和null
第一个例子相同。)
回答by A_Arnold
Java currently has a draft to support this. See here. The syntax looks like this
Java 目前有一个草案来支持这一点。见这里。语法看起来像这样
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);
}