Ist es möglich, den Operator instanceof in einer switch-Anweisung zu verwenden?


267

Ich habe eine Frage zur Verwendung von Switch Case für instanceofObjekt:

Zum Beispiel: Mein Problem kann in Java reproduziert werden:

if(this instanceof A)
    doA();
else if(this instanceof B)
    doB();
else if(this instanceof C)
    doC():

Wie würde es mit implementiert werden switch...case?


6
Wenn Sie wirklich das Gefühl haben, einen Schalter zu benötigen, können Sie den Klassennamen auf einen int setzen und diesen verwenden. Achten Sie jedoch auf mögliche Konflikte. Hinzufügen als Kommentar statt als Antwort, da mir die Idee nicht gefällt, dass dies tatsächlich verwendet wurde. Vielleicht brauchen Sie wirklich das Besuchermuster.
Vickirk

1
Ab Java 7 können Sie sogar den vollständig qualifizierten Klassennamen aktivieren, um solche Hash-Konflikte zu vermeiden, wie @vickirk hervorhob, aber es ist immer noch hässlich.
Mitja

Antworten:


225

Dies ist ein typisches Szenario, in dem Subtyp-Polymorphismus hilfreich ist. Mach Folgendes

interface I {
  void do();
}

class A implements I { void do() { doA() } ... }
class B implements I { void do() { doB() } ... }
class C implements I { void do() { doC() } ... }

Dann können Sie rufen Sie einfach do()an this.

Wenn Sie nicht frei Asind B, und zu ändern , können CSie das Besuchermuster anwenden, um dasselbe zu erreichen.


33
Besuchermuster bedeutet, dass A, B und C eine Schnittstelle mit einer abstrakten Methode implementieren müssen, die einen Besucher als Eingabeparameter verwendet. Was ist, wenn Sie A, B, C nicht ändern können und keiner von ihnen diese Schnittstelle implementiert?
Thermz

21
Der letzte Kommentar zum Besuchermuster ist falsch. Sie müssten A, B und C weiterhin eine Schnittstelle implementieren lassen.
Ben Thurley

10
Leider funktioniert dies nicht, wenn der do () - Code die Umgebung des Hosts erfordert (dh Zugriff auf Variablen, die nicht in do () selbst vorhanden sind).
Mafu

2
Die Frage von @mafu OP betraf das typbasierte Versenden. Wenn Ihre do () -Methode zum Versenden mehr Eingaben benötigt als Ihr Problem, liegt IMHO außerhalb des Bereichs der hier diskutierten Frage.
Jmg

3
Diese Antwort setzt voraus, dass Sie die Klassen A, B, C ändern können, während ich denke, der Punkt ist, wie Sie dies tun können, ohne A, B, C zu ändern, da sie sich möglicherweise in einer Bibliothek des dritten Teils befinden
cloudy_weather

96

Wenn Sie absolut nicht für eine Schnittstelle codieren können, können Sie eine Aufzählung als Vermittler verwenden:

public A() {

    CLAZZ z = CLAZZ.valueOf(this.getClass().getSimpleName());
    switch (z) {
    case A:
        doA();
        break;
    case B:
        doB();
        break;
    case C:
        doC();
        break;
    }
}


enum CLAZZ {
    A,B,C;

}

thx, ich musste auch einige Änderungen vornehmen: 1) initialisiere jede Enum-ID mit der Klassenreferenz; 2) Bestätigen Sie den einfachen Klassennamen mit der Enum-ID .toString (). 3) Finden Sie die Aufzählung durch die gespeicherte Klassenreferenz pro Aufzählungs-ID. Ich denke, das ist dann auch sicher.
Wassermann Power

Wenn this.getClass (). getSimpleName () nicht mit einem Wert von CLAZZ übereinstimmt, wird eine Ausnahme ausgelöst. Es ist besser, mit einem try catch-Block zu umgeben, und die Ausnahme wird als "Standard" - oder "else" -Option von behandelt der Schalter
Tetri

40

Erstellen Sie einfach eine Map, in der die Klasse der Schlüssel und die Funktionalität, dh Lambda oder ähnliches, der Wert ist.

Map<Class,Runnable> doByClass = new HashMap<>();
doByClass.put(Foo.class, () -> doAClosure(this));
doByClass.put(Bar.class, this::doBMethod);
doByClass.put(Baz.class, new MyCRunnable());

// Refactor dies natürlich so, dass es nur einmal initialisiert wird

doByClass.get(getClass()).run();

Wenn Sie aktivierte Ausnahmen benötigen, implementieren Sie ein FunctionalInterface, das die Ausnahme auslöst, und verwenden Sie diese anstelle von Runnable.


3
Beste Lösung imho, vor allem, weil es ein einfaches Refactoring ermöglicht.
Feiteira

2
Der einzige Nachteil dieser Lösung ist, dass sie keine lokalen (Methoden-) Variablen in den Lambdas verwenden kann (vorausgesetzt, dies könnte natürlich erforderlich sein).
Zapatero

1
@zapatero Sie können einfach zu Funktion anstelle von Runnable wechseln, die Instanz als Parameter übergeben und sie dann bei Bedarf
umwandeln

Upvoted; Dies ist eine der wenigen Antworten, die dem OP tatsächlich dabei helfen, das zu tun, wonach er fragt (und ja, es ist oft möglich, den Code umzugestalten, um ihn nicht tun zu müssen instanceof, und nein, mein Szenario ist leider nicht eines von denen, in die es leicht passt diese Box ...)
Per Lundberg

@ SergioGutiérrez Danke. Nun, dieses Muster sollte nur mit einer externen Bibliothek benötigt werden. Und selbst dann können Sie stattdessen einfach eine Schnittstelle mit einer Adapterimplementierung erstellen, aber es ist nützlich, wenn Sie möchten, dass das Verhaltens-DIFF sozusagen offensichtlicher wird. Ähnlich wie beim fließenden vs Annotation API-Routing, nehme ich an.
Novaterata

36

Nur für den Fall, dass jemand es liest:

Die beste Lösung in Java ist:

public enum Action { 
    a{
        void doAction(...){
            // some code
        }

    }, 
    b{
        void doAction(...){
            // some code
        }

    }, 
    c{
        void doAction(...){
            // some code
        }

    };

    abstract void doAction (...);
}

Die großen Vorteile eines solchen Musters sind:

  1. Du machst es einfach so (KEINE Schalter überhaupt):

    void someFunction ( Action action ) {
        action.doAction(...);   
    }
  2. Wenn Sie eine neue Aktion mit dem Namen "d" hinzufügen, MÜSSEN Sie die Methode doAction (...) implementieren

HINWEIS: Dieses Muster ist in Joshuas Bloch "Effective Java (2nd Edition)" beschrieben.


1
nett! Ist das @Overrideüber jeder Implementierung von erforderlich doAction()?
mateuscb

9
Wie ist das die "BESTE" Lösung? Wie würden Sie entscheiden, welche Sie verwenden möchten action? Durch eine äußere Instanz der Kaskade, die someFunction()mit der richtigen aufruft action? Dies fügt nur eine weitere Indirektionsebene hinzu.
PureSpider

1
Nein, es wird automatisch zur Laufzeit durchgeführt. Wenn Sie someFunction (Action.a) aufrufen, wird a.doAction aufgerufen.
se.solovyev

11
Ich verstehe das nicht Woher wissen Sie, welche Aufzählung Sie verwenden sollen? Wie @PureSpider sagte, scheint dies nur eine weitere Arbeitsebene zu sein.
James Manes

2
Es ist sehr traurig, dass Sie kein vollständiges Beispiel angeboten haben , z. B. wie eine Klasseninstanz von a, b oder C dieser Aufzählung zugeordnet wird. Ich werde versuchen, die Instanz in diese Aufzählung zu übertragen.
Tom

21

Das kannst du nicht. Die switchAnweisung kann nur caseAnweisungen enthalten , die Kompilierzeitkonstanten sind und eine Ganzzahl ergeben (bis zu Java 6 und eine Zeichenfolge in Java 7).

Was Sie suchen, wird in der funktionalen Programmierung als "Pattern Matching" bezeichnet.

Siehe auch Vermeiden von Instanzen in Java


1
Nein, in den meisten funktionalen Sprachen können Sie keine Musterübereinstimmung für Typen, sondern nur für Konstruktoren durchführen. Das gilt zumindest für ML und Haskell. In Scala und OCaml ist es möglich, aber nicht die typische Anwendung des Pattern Matching.
jmg

Sicher, aber die Überprüfung gegen Konstruktoren wäre "äquivalent" zu dem oben beschriebenen Szenario.
Carlo V. Dango

1
In einigen Fällen, aber nicht im Allgemeinen.
jmg

Switches können auch Aufzählungen unterstützen.
Solomon Ucko

"Sie können nicht" eine andere Sprache zu betrachten ist selten eine hilfreiche Antwort.
L. Blanc

17

Wie in den Top-Antworten erläutert, besteht der traditionelle OOP-Ansatz darin, Polymorphismus anstelle von Switch zu verwenden. Es gibt sogar ein gut dokumentiertes Refactoring-Muster für diesen Trick: Ersetzen Sie Bedingt durch Polymorphismus . Wann immer ich nach diesem Ansatz greife, möchte ich auch ein Null-Objekt implementieren , um das Standardverhalten bereitzustellen.

Ab Java 8 können wir Lambdas und Generika verwenden, um uns etwas zu geben, mit dem funktionale Programmierer sehr vertraut sind: Pattern Matching. Es ist keine Kernsprache, aber die Javaslang-Bibliothek bietet eine Implementierung. Beispiel aus dem Javadoc :

Match.ofType(Number.class)
    .caze((Integer i) -> i)
    .caze((String s) -> new BigDecimal(s))
    .orElse(() -> -1)
    .apply(1.0d); // result: -1

Es ist nicht das natürlichste Paradigma in der Java-Welt. Verwenden Sie es daher mit Vorsicht. Mit den generischen Methoden müssen Sie zwar den übereinstimmenden Wert nicht typisieren, es fehlt jedoch eine Standardmethode zum Zerlegen des übereinstimmenden Objekts, wie beispielsweise bei Scalas Fallklassen .


9

Ich weiß, dass dies sehr spät ist, aber für zukünftige Leser ...

Beachten Sie die oben genannten Ansätze, die nur auf dem Namen der Klasse von A , B , C ... basieren :

Sofern Sie nicht garantieren können, dass A , B , C ... (alle Unterklassen oder Implementierer von Base ) endgültig sind, werden Unterklassen von A , B , C ... nicht behandelt.

Obwohl der Ansatz if, elseif, elseif .. für eine große Anzahl von Unterklassen / Implementierern langsamer ist, ist er genauer.


In der Tat sollten Sie niemals den Polymorphimsmus (auch bekannt als OOP) verwenden
Val

8

Leider ist dies nicht sofort möglich, da die switch-case-Anweisung einen konstanten Ausdruck erwartet. Um dies zu überwinden, besteht eine Möglichkeit darin, Enum-Werte mit den Klassennamen zu verwenden, z

public enum MyEnum {
   A(A.class.getName()), 
   B(B.class.getName()),
   C(C.class.getName());

private String refClassname;
private static final Map<String, MyEnum> ENUM_MAP;

MyEnum (String refClassname) {
    this.refClassname = refClassname;
}

static {
    Map<String, MyEnum> map = new ConcurrentHashMap<String, MyEnum>();
    for (MyEnum instance : MyEnum.values()) {
        map.put(instance.refClassname, instance);
    }
    ENUM_MAP = Collections.unmodifiableMap(map);
}

public static MyEnum get(String name) {
    return ENUM_MAP.get(name);
 }
}

Damit ist es möglich, die switch-Anweisung so zu verwenden

MyEnum type = MyEnum.get(clazz.getName());
switch (type) {
case A:
    ... // it's A class
case B:
    ... // it's B class
case C:
    ... // it's C class
}

Ich glaube, bis das JEP-Problem 8213076 vollständig implementiert ist, ist dies der sauberste Weg, eine switch-Anweisung basierend auf dem Typ zu erhalten, ohne die Zielklasse zu ändern.
Rik Schaaf


5

Die Verwendung solcher switch-Anweisungen ist nicht objektorientiert. Sie sollten stattdessen die Kraft des Polymorphismus nutzen . Einfach schreiben

this.do()

Nachdem Sie zuvor eine Basisklasse eingerichtet haben:

abstract class Base {
   abstract void do();
   ...
}

Das ist die Basisklasse für A, Bund C:

class A extends Base {
    void do() { this.doA() }
}

class B extends Base {
    void do() { this.doB() }
}

class C extends Base {
    void do() { this.doC() }
}

@jmg suggeests ( stackoverflow.com/questions/5579309/switch-instanceof/… ) unter Verwendung einer Schnittstelle anstelle einer abstrakten Basisklasse. Das kann unter bestimmten Umständen überlegen sein.
Raedwald

5

Dies funktioniert schneller und macht Sinn, falls
- Sie haben relativ viele "Fälle"
- der Prozess in einem leistungsabhängigen Kontext ausgeführt wird

public <T> T process(Object model) {
    switch (model.getClass().getSimpleName()) {
        case "Trade":
            return processTrade();
        case "InsuranceTransaction":
            return processInsuranceTransaction();
        case "CashTransaction":
            return processCashTransaction();
        case "CardTransaction":
            return processCardTransaction();
        case "TransferTransaction":
            return processTransferTransaction();
        case "ClientAccount":
            return processAccount();
        ...
        default:
            throw new IllegalArgumentException(model.getClass().getSimpleName());
    }
}

1
Dies ist nicht dasselbe wie das Ausführen einer Instanz von, da dies nur funktioniert, wenn die Implementierungsklasse zum
Wechseln

Ja, das ist nicht
Mike

A für Mühe, aber abgesehen von @ lifesoordinarys Kommentar vermissen Sie auch die Typensicherheit, die Sie normalerweise haben, da diese Antwort fest codierte Zeichenfolgen anstelle von Klassenreferenzen verwendet. Es ist ziemlich einfach, einen Tippfehler zu machen, besonders wenn Sie diese Funktionalität mit vollständigen kanonischen Namen erweitern müssen, wenn sich Klassennamen mit unterschiedlichen Paketnamen überschneiden. Bearbeiten: Tippfehler behoben (was meinen Standpunkt irgendwie beweist)
Rik Schaaf

4

Sie können nicht, dass ein Switch nur mit den Typen Byte, Short, Char, Int, String und Enumerated funktioniert (und den Objektversionen der Grundelemente, es hängt auch von Ihrer Java-Version ab, Strings können switchin Java 7 bearbeitet werden).


Sie können Strings in Java 6 nicht aktivieren. Und Sie können "Objektversionen der Grundelemente" nicht aktivieren.
Lukas Eder

@Bozho Ich habe gesagt, es hängt von Ihrer Java-Version ab, in Java 7 können Sie Strings einschalten.
Tnem

@ Lukas Eder überprüfen Sie Ihre Java-Spezifikation, die Sie können
Tnem

4

Ich persönlich mag den folgenden Java 1.8-Code:

    mySwitch("YY")
            .myCase("AA", (o) -> {
                System.out.println(o+"aa");
            })
            .myCase("BB", (o) -> {
                System.out.println(o+"bb");
            })
            .myCase("YY", (o) -> {
                System.out.println(o+"yy");
            })
            .myCase("ZZ", (o) -> {
                System.out.println(o+"zz");
            });

Wird ausgegeben:

YYyy

Der Beispielcode verwendet Strings, Sie können jedoch jeden Objekttyp verwenden, einschließlich Class. z.B.myCase(this.getClass(), (o) -> ...

Benötigt das folgende Snippet:

public Case mySwitch(Object reference) {
    return new Case(reference);
}

public class Case {

    private Object reference;

    public Case(Object reference) {
        this.reference = reference;
    }

    public Case myCase(Object b, OnMatchDo task) {
        if (reference.equals(b)) {
            task.task(reference);
        }
        return this;
    }
}

public interface OnMatchDo {

    public void task(Object o);
}

4

Mit Java können Sie jetzt in der Art des OP wechseln. Sie nennen es Pattern Matching für Switch. Es befindet sich derzeit im Entwurf, aber da ich sehe, wie viel Arbeit sie in letzter Zeit in Schalter gesteckt haben, denke ich, dass es durchgehen wird. Das im JEP gegebene Beispiel ist

String formatted;
switch (obj) {
    case Integer i: formatted = String.format("int %d", i); break;
    case Byte b:    formatted = String.format("byte %d", b); break;
    case Long l:    formatted = String.format("long %d", l); break;
    case Double d:  formatted = String.format("double %f", d); break;
    case String s:  formatted = String.format("String %s", s); break
    default:        formatted = obj.toString();
}  

oder verwenden Sie ihre Lambda-Syntax und geben Sie einen Wert zurück

String formatted = 
    switch (obj) {
        case Integer i -> String.format("int %d", i)
        case Byte b    -> String.format("byte %d", b);
        case Long l    -> String.format("long %d", l); 
        case Double d  -> String.format("double %f", d); 
        case String s  -> String.format("String %s", s); 
        default        -> obj.toString();
    };

So oder so haben sie coole Sachen mit Schaltern gemacht.


3

Wenn Sie die allgemeine Schnittstelle bearbeiten können, können Sie eine Aufzählung hinzufügen und jede Klasse einen eindeutigen Wert zurückgeben lassen. Sie benötigen keine Instanz oder ein Besuchermuster.

Für mich musste die Logik in der switch-Anweisung geschrieben sein, nicht das Objekt selbst. Das war meine Lösung:

ClassA, ClassB, and ClassC implement CommonClass

Schnittstelle:

public interface CommonClass {
   MyEnum getEnumType();
}

Aufzählung:

public enum MyEnum {
  ClassA(0), ClassB(1), ClassC(2);

  private int value;

  private MyEnum(final int value) {
    this.value = value;
  }

  public int getValue() {
    return value;
  }

Impl:

...
  switch(obj.getEnumType())
  {
    case MyEnum.ClassA:
      ClassA classA = (ClassA) obj;
    break;

    case MyEnum.ClassB:
      ClassB classB = (ClassB) obj;
    break;

    case MyEnum.ClassC:
      ClassC classC = (ClassC) obj;
    break;
  }
...

Wenn Sie mit Java 7 arbeiten, können Sie Zeichenfolgenwerte für die Aufzählung eingeben, und der Switch-Case-Block funktioniert weiterhin.


Das valueFeld ist redundant, wenn Sie nur die Enum-Konstanten unterscheiden möchten - Sie können die Konstanten direkt verwenden (wie Sie es tun).
user905686

2

Wie wäre es damit ?

switch (this.name) 
{
  case "A":
    doA();
    break;
  case "B":
    doB();
    break;
  case "C":
    doC();
    break;
  default:
    console.log('Undefined instance');
}

3
Sollte darauf hinweisen, dass dies nur unter Java 7 funktioniert. Und dass Sie anrufen müssen. this.getSimpleName()Nicht sicher, ob das Poster mit JS verwechselt wird (ja, er verwendet die Konsole, hehe).
Pablisco

5
Dies hat das Problem, dass die referenzielle Transparenz des Quellcodes nicht mehr gewährleistet ist. Das heißt, Ihre IDE kann die Referenzintegrität nicht aufrechterhalten. Angenommen, Sie möchten Ihren Namen umbenennen. Das Spiegelbild ist böse.
Val

1
Keine gute Idee. Klassennamen sind nicht eindeutig, wenn Sie mehrere Klassenlader haben.
Doradus

Pausen bei der Codekomprimierung (→ ProGuard)
Matthias Ronge

1

Ich denke, es gibt Gründe, eine switch-Anweisung zu verwenden. Wenn Sie xText generierten Code verwenden, möglicherweise. Oder eine andere Art von EMF-generierten Klassen.

instance.getClass().getName();

Gibt eine Zeichenfolge des Klassenimplementierungsnamens zurück. dh: org.eclipse.emf.ecore.util.EcoreUtil

instance.getClass().getSimpleName();

gibt die einfache Darstellung zurück, dh: EcoreUtil


Sie können es nicht switchals caseBedingung verwenden, da es kein konstanter Wert ist
B-GangsteR

1

Wenn Sie durch den Klassentyp "dieses" Objekts "wechseln" müssen, ist diese Antwort die beste https://stackoverflow.com/a/5579385/2078368

Aber wenn Sie "switch" auf eine andere Variable anwenden müssen. Ich würde eine andere Lösung vorschlagen. Definieren Sie folgende Schnittstelle:

public interface ClassTypeInterface {
    public String getType();
}

Implementieren Sie diese Schnittstelle in jeder Klasse, die Sie "wechseln" möchten. Beispiel:

public class A extends Something implements ClassTypeInterface {

    public final static String TYPE = "A";

    @Override
    public String getType() {
        return TYPE;
    }
}

Danach können Sie es folgendermaßen verwenden:

switch (var.getType()) {
    case A.TYPE: {
        break;
    }
    case B.TYPE: {
        break;
    }
    ...
}

Das einzige, was Sie beachten sollten - halten Sie die "Typen" für alle Klassen, die das ClassTypeInterface implementieren, eindeutig. Dies ist kein großes Problem, da bei einer Kreuzung ein Kompilierungsfehler für die Anweisung "switch-case" angezeigt wird.


Anstatt String für zu verwenden TYPE, können Sie eine Aufzählung verwenden, und die Eindeutigkeit ist garantiert (wie in dieser Antwort beschrieben ). Bei jedem der Ansätze müssen Sie jedoch bei einer Umbenennung an zwei Stellen eine Umgestaltung vornehmen.
user905686

@ user905686 umbenennen von was? Im aktuellen Beispiel wird der Typ "A" in der Something-Klasse definiert, um die Codemenge zu minimieren. Aber im wirklichen Leben sollten Sie es natürlich draußen definieren (an einem alltäglichen Ort), und es gibt kein Problem mit weiteren Umgestaltungen.
Sergey Krivenkov

Ich meine das Umbenennen der Klasse A. Beim automatischen Refactoring wird die Variable TYPE = "A"beim Umbenennen möglicherweise nicht berücksichtigt. Insbesondere wenn es sich außerhalb der entsprechenden Klasse befindet, kann es auch vergessen werden, wenn es manuell ausgeführt wird. IntelliJ findet das Vorkommen des Klassennamens auch in Zeichenfolgen oder Kommentaren, aber das ist nur eine Textsuche (anstatt den Syntaxbaum zu betrachten) und enthält daher falsch positive Ergebnisse.
user905686

@ user905686 Es ist nur ein Beispiel, um die Idee zu visualisieren. Verwenden Sie String nicht für Typdefinitionen in realen Projekten, deklarieren Sie einige MyTypes-Klasseninhaber mit Ganzzahlkonstanten (oder Enum) und verwenden Sie sie in den Klassen, die ClassTypeInterface implementieren.
Sergey Krivenkov

1

Hier ist eine funktionale Möglichkeit, dies in Java 8 mithilfe von http://www.vavr.io/ zu erreichen.

import static io.vavr.API.*;
import static io.vavr.Predicates.instanceOf;
public Throwable liftRootCause(final Throwable throwable) {
        return Match(throwable).of(
                Case($(instanceOf(CompletionException.class)), Throwable::getCause),
                Case($(instanceOf(ExecutionException.class)), Throwable::getCause),
                Case($(), th -> th)
        );
    }

0

Erstelle ein Aufzählung mit Klassennamen .

public enum ClassNameEnum {
    A, B, C
}

Suchen Sie den Klassennamen des Objekts. Schreib ein Schalterfall über die Aufzählung.

private void switchByClassType(Object obj) {

        ClassNameEnum className = ClassNameEnum.valueOf(obj.getClass().getSimpleName());

        switch (className) {
            case A:
                doA();
                break;
            case B:
                doB();
                break;
            case C:
                doC();
                break;
        }
    }
}

Hoffe das hilft.


2
Im Gegensatz zu diesem Ansatz, bei dem die Kopplung zwischen Enum-Konstanten und Klassen explizit erfolgt, erfolgt die Kopplung implizit nach Klassennamen. Dadurch wird Ihr Code beschädigt, wenn Sie nur eine der Enum-Konstanten oder die Klasse umbenennen, während der andere Ansatz weiterhin funktioniert.
user905686

0

Das Eclipse Modeling Framework hat eine interessante Idee, die auch die Vererbung berücksichtigt. Das Grundkonzept ist in der Switch- Schnittstelle definiert : Das Umschalten erfolgt durch Aufrufen der doSwitch- Methode.

Was wirklich interessant ist, ist die Implementierung. Für jede Art von Interesse a

public T caseXXXX(XXXX object);

Methode muss implementiert werden (wobei eine Standardimplementierung null zurückgibt). Die doSwitch- Implementierung versucht, alle caseXXX- Methoden für das Objekt für alle Typhierarchien aufzurufen . Etwas in den Zeilen von:

BaseType baseType = (BaseType)object;
T result = caseBaseType(eAttribute);
if (result == null) result = caseSuperType1(baseType);
if (result == null) result = caseSuperType2(baseType);
if (result == null) result = caseSuperType3(baseType);
if (result == null) result = caseSuperType4(baseType);
if (result == null) result = defaultCase(object);
return result;

Das eigentliche Framework verwendet für jede Klasse eine Ganzzahl-ID, sodass die Logik tatsächlich ein reiner Schalter ist:

public T doSwitch(Object object) {
    return doSwitch(object.class(), eObject);
}

protected T doSwitch(Class clazz, Object object) {
    return doSwitch(getClassifierID(clazz), object);
}

protected T doSwitch(int classifierID, Object theObject) {
    switch (classifierID) {
    case MyClasses.BASETYPE:
    {
      BaseType baseType = (BaseType)object;
      ...
      return result;
    }
    case MyClasses.TYPE1:
    {
      ...
    }
  ...

Sie können sich eine vollständige Implementierung des ECoreSwitch ansehen , um eine bessere Vorstellung zu erhalten.


0

Während es nicht möglich ist, eine switch-Anweisung zu schreiben, ist es möglich, für jeden gegebenen Typ zu einer bestimmten Verarbeitung zu verzweigen. Eine Möglichkeit hierfür ist die Verwendung eines Standard-Doppelversandmechanismus. Ein Beispiel, bei dem wir basierend auf dem Typ "wechseln" möchten, ist der Jersey Exception Mapper, bei dem wir eine Vielzahl von Ausnahmen auf Fehlerantworten abbilden müssen. Während es für diesen speziellen Fall wahrscheinlich einen besseren Weg gibt (dh die Verwendung einer polymorphen Methode, die jede Ausnahme in eine Fehlerantwort übersetzt), ist die Verwendung des Doppelversandmechanismus immer noch nützlich und praktisch.

interface Processable {
    <R> R process(final Processor<R> processor);
}

interface Processor<R> {
    R process(final A a);
    R process(final B b);
    R process(final C c);
    // for each type of Processable
    ...
}

class A implements Processable {
    // other class logic here

    <R> R process(final Processor<R> processor){
        return processor.process(this);
    }
}

class B implements Processable {
    // other class logic here

    <R> R process(final Processor<R> processor){
        return processor.process(this);
    }
}

class C implements Processable {
    // other class logic here

    <R> R process(final Processor<R> processor){
        return processor.process(this);
    }
}

Wo immer der "Schalter" benötigt wird, können Sie dies wie folgt tun:

public class LogProcessor implements Processor<String> {
    private static final Logger log = Logger.for(LogProcessor.class);

    public void logIt(final Processable base) {
        log.info("Logging for type {}", process(base));
    }

    // Processor methods, these are basically the effective "case" statements
    String process(final A a) {
        return "Stringifying A";
    }

    String process(final B b) {
        return "Stringifying B";
    }

    String process(final C c) {
        return "Stringifying C";
    }
}

Dies sieht dem Besuchermuster sehr ähnlich, das bereits in dieser Antwort besprochen wurde: stackoverflow.com/a/5579385
typeracer

-1

Es gibt eine noch einfachere Möglichkeit, eine Switch-Struktur zu emulieren, die instanceof verwendet. Dazu erstellen Sie einen Codeblock in Ihrer Methode und benennen ihn mit einer Bezeichnung. Dann verwenden Sie if-Strukturen, um die case-Anweisungen zu emulieren. Wenn ein Fall zutrifft, verwenden Sie die Unterbrechung LABEL_NAME, um aus Ihrer provisorischen Schalterstruktur herauszukommen.

        DEFINE_TYPE:
        {
            if (a instanceof x){
                //do something
                break DEFINE_TYPE;
            }
            if (a instanceof y){
               //do something
                break DEFINE_TYPE;
            }
            if (a instanceof z){
                // do something
                break DEFINE_TYPE;
            }
        }

Wie ist das besser als der if... else ifCode des OP?
Typeracer

Nur um auf meinen vorherigen Kommentar einzugehen: Was Sie vorschlagen, ist im Wesentlichen, if... else ifdurch "goto" -Anweisungen zu ersetzen , was der falsche Weg ist, den Kontrollfluss in Sprachen wie Java zu implementieren.
Typeracer
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.