web-dev-qa-db-ger.com

Umschalten Typ in Java

Bevor ich anfange, weiß ich, dass es eine Reihe von Antworten auf diese Frage gibt, die alternative Ansätze vorschlagen. Ich suche Unterstützung für diesen speziellen Ansatz, ob es möglich ist und wenn nicht, ähnliche Ansätze, die funktionieren könnten.

Ich habe eine Methode, die eine Superklasse annimmt und eine Methode basierend auf dem Typ des übergebenen Objekts aufruft. zum Beispiel:

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

Ich kann die Subtypen nicht ändern, um eine handle()-Methode zu überschreiben, da diese Antwort vorschlagen würde, da ich die Klassen nicht besitze. Also ist die instanceof Methode alles was ich habe.

Ich würde gerne eine switch-Anweisung anstelle von if/else verwenden, einfach weil es viel netter ist. Ich weiß, dass Sie nur Primitive und Strings einschalten können, also wechsle ich den Klassennamen:

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

Der Haken dabei ist, dass die kanonischen Namen SEHR lang sind (wie 12 Unterpakete), und ich kann ClassName.class.getCanonicalName() in der case-Anweisung nicht aufrufen, da Java dies nicht zulässt. Meine nächste Lösung war also ein Enum. Hier traf ich mein Problem.

Ich möchte, dass mein Code ungefähr so ​​aussieht:

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

Das wird aber nicht kompiliert. Ich bin mir nicht sicher warum. Ich hätte gerne einen Ansatz, mit dem ich den Typ umschalten kann, ohne den gesamten kanonischen Namen eingeben zu müssen. Wenn ich das mache, könnte ich genauso gut if/else und instanceof verwenden.

NOTE Es gibt einige Typen, die denselben Namen haben (innere Klassen), so dass getSimpleName() ausfällt.

22
ewok

Hier ist ein Ansatz, der sich nicht mit Klassennamen befasst und so schnell abläuft wie eine switch-Anweisung: Machen Sie eine Hash-Map, um Class<T>-Objekte klassenspezifischen Handlern zuzuordnen, und verwenden Sie die Map anstelle einer 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
}
28
dasblinkenlight

Mit Java 8-Lambdas können Sie Folgendes erreichen:

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

Dazu müssen Sie die folgenden statischen Methoden definieren:

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

Java hat derzeit einen Entwurf, der dies unterstützt. Siehe hier . Die Syntax sieht so aus

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

Der instanceof-Operator ist ein einfacher Ansatz, wenn Sie keine Klassen besitzen. Ein instanceof-Ausdruck ist wahr, wenn das Objekt die angegebene Klasse oder eine Unterklasse ist. 

Sie erwähnen, dass Sie die Klassen nicht besitzen. Der Besitzer könnte in einer späteren Version Unterklassen einführen. Angenommen, der Besitzer führt APlus als Unterklasse von A ein. Eine Instanz von APlus ist ein A. Code, der auf einem A funktioniert, sollte auch auf einem APlus funktionieren. Wenn Sie instanceof verwenden, funktioniert Ihr Code weiterhin - ohne Aufwand von Ihnen. Wenn Sie Klassennamen verwenden, schlägt dies fehl, ohne dass Sie von Ihrem Compiler darauf hingewiesen werden.

Wenn Sie dasselbe Objekt wiederholt einschalten, kann es hilfreich sein, das Objekt einmal in einer Wrapper-Klasse zu verpacken, die eine Schnittstelle implementiert. Danach können Sie einfach Methoden auf der Oberfläche aufrufen - ohne if, switch oder 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);
}

Vermeiden Sie es auch in der garantierten Abwesenheit von Unterklassen, die Klassennamen in switch-Fällen als Literalzeichenfolgen anzugeben. Dies ermöglicht Fehler, die der Compiler nicht findet, was die Debugging-Zeit kosten kann.

2
Andy Thomas

Sie waren mit enum sehr nahe an der Lösung ..__ Sie wurde nicht kompiliert, weil Ihr Enum Konstruktor und Coversion-Methode zur Zuordnung von Enum von String fehlte. Eigentlich könnte man es auch ohne String lösen, d. H. Ohne getCanonicalName überhaupt aufzurufen: 

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

wenn Sie eine signifikante Anzahl bekannter Klassen erhalten, sollten Sie die Verwendung von map erwägen, anstatt in Classes.fromClass zu iterieren, z. 

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

Ich konnte mit Java.lang.reflect umgehen

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
Edgar

Hier ist ein Beispiel, bei dem für jeden Fall ein einfaches Objekt verwendet wird.

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

Ein Verwendungsbeispiel wäre so ähnlich, wenn Importe von z. 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"))
);

Sie können auch ein wiederverwendbares Objekt erstellen:

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

Anmerkungen:

  • Wenn Sie einen default-Fall wünschen, können Sie Object.class als letzten Fall verwenden.

  • Es gibt keine Möglichkeit, einen Fall zu erstellen, der null behandelt, aber es könnte dafür etwas geändert werden. Sie könnten z. Erstellen Sie einen class NullCase, dessen test-Methode obj == null zurückgibt.


Sie können auch Überladungen generieren, anstatt Varargs zu verwenden. Auf diese Weise können Sie Klassen mithilfe von generischen Methodendeklarationen mit Verbrauchern verknüpfen. Das folgende ist ein ziemlich einfaches Beispiel dafür:

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

Wenn Sie Überladungen generieren möchten, um beispielsweise mehr als 8 Fälle zu erhalten, können Sie etwa Folgendes sagen:

GeneratedClassSwitch.generateFixedOverloads(16, 1);

Daraufhin werden Methoden generiert, die der allgemeinen Form von System.out folgen:

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

Beachten Sie, dass wir jeden Klassentyp mit dem zugehörigen Konsumententyp verknüpfen können, d. H., __.Class<A> mit Consumer<? super A>, Class<B> mit Consumer<? super B> usw. Dies ist eigentlich mit varargs nicht möglich (ab der aktuellen Java-Version, die 10 ist).

Unser Anwendungsbeispiel sieht nun so aus, wobei wiederum Importe von z. 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")
);

(Hinweise zu default Fällen und null sind die gleichen wie im ersten Beispiel.)

0
Radiodef

Um bekannte Klassentypen umzuschalten, können Sie den folgenden Ansatz verwenden

Erstellen Sie ein Enum mit Klassennamen.

public enum ClassNameEnum {
    ClassA, ClassB, ClassC
}

Suchen Sie den Klassennamen des Objekts . Schreiben Sie einen switch case über die Aufzählung.

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
Siva Kumar