始める前に、代替のアプローチを提案するこの質問に対する多くの答えがあることを知っています。私は、この特定のアプローチが可能かどうか、また可能でなければ同様のアプローチが機能するかどうかについて支援を求めています。
スーパークラスを受け取り、渡されたオブジェクトの型に基づいてメソッドを呼び出すメソッドがあります。例えば:
_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
メソッドだけが私にあります。
私は_if/else
_の代わりにswitch
ステートメントを使用したいのですが、それは単にそれがずっときれいだからです。プリミティブと文字列のみを切り替えることができることを知っているので、クラス名を切り替えています:
_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個のサブパッケージなど)ことであり、Javaでは許可されないため、caseステートメントでClassName.class.getCanonicalName()
を呼び出すことはできません。 。だから私の次の解決策は列挙型でした。
私のコードは次のようになりたいです:
_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
を使用することもできます。
[〜#〜] note [〜#〜]同じ名前(内部クラス)を持つ型がいくつかあるため、getSimpleName()
出ています。
クラス名をまったく処理せず、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'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
}
Java 8ラムダを使用すると、次のようなものに到達できます。
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);
}
現在、Javaにはこれをサポートするドラフトがあります。 here を参照してください。構文は次のようになります
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);
}
instanceof
演算子は、クラスを所有していない場合の単純なアプローチです。オブジェクトが指定されたクラスまたはサブクラスである場合、instanceof
式はtrueです。
あなたはクラスを所有していないと言います。所有者は、後続のリリースでサブクラスを導入できます。オーナーがAPlusをAのサブクラスとして導入するとします。APlusのインスタンスはAです。Aで機能するコードはAPlusでも機能するはずです。 instanceof
を使用する場合、コードは引き続き機能しますが、ユーザーの努力は必要ありません。クラス名を使用すると、コンパイラからの通知なしに失敗します。
同じオブジェクトを繰り返し切り替えている場合は、インターフェイスを実装するラッパークラスでオブジェクトを1回ラップすると便利です。その後、インターフェイスでメソッドを呼び出すことができます-if
、switch
、または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);
}
サブクラスが保証されていない場合でも、switch
の場合、クラス名をリテラル文字列として指定しないでください。これにより、コンパイラーが検出できないエラーが発生し、デバッグに時間がかかる可能性があります。
列挙型でソリューションに非常に近かった。列挙型が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で反復するのではなく、mapの使用を検討してください。例:
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.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(){
}
}
以下に、それぞれの場合に単純なオブジェクトを使用する例を示します。
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
を処理するケースを作成する方法はありませんが、そのために少し変更することができます。あなたは例えばtest
メソッドがclass NullCase
を返すobj == null
を作成します。
また、可変引数を使用する代わりに、実際にオーバーロードを生成することもできます。これにより、ジェネリックメソッド宣言のみを使用して、クラスをコンシューマに関連付けることができます。以下は、これのかなり単純な例です。
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>
with Consumer<? super A>
、Class<B>
with Consumer<? super B>
などにマッピングできることに注意してください。これは実際には可変引数では不可能です(現在のバージョンの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
についての注意は、最初の例と同じです。)