webentwicklung-frage-antwort-db.com.de

Instanz wechseln?

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

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 switch...case implementiert werden?

215
olidev

Dies ist ein typisches Szenario, in dem der Subtyp-Polymorphismus hilft. 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 einfach do() mit this aufrufen. 

Wenn Sie A, B und C nicht ändern können, können Sie das Besuchermuster anwenden, um dies zu erreichen.

196
jmg

wenn Sie absolut keine Codes für eine Schnittstelle erstellen können, können Sie eine Enumeration 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;

}
79
Nico

Nur für den Fall, dass jemand es lesen wird:

Die BEST-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 wie (KEINE Schalter):

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

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

34
se.solovyev

Erstellen Sie einfach eine Map, bei der die Klasse der Schlüssel ist und die Funktionalität, d. H. 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 natürlich um nur einmal zu initialisieren

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

Wenn Sie markierte Exceptions benötigen, implementieren Sie ein FunctionalInterface, das die Exception auslöst, und verwenden Sie diese anstelle von Runable.

20
Novaterata

Du kannst nicht Die switch-Anweisung kann nur case-Anweisungen enthalten, bei denen es sich um Kompilierungszeitkonstanten handelt, die zu einer Ganzzahl ausgewertet werden (bis Java 6 und eine Zeichenfolge in Java 7). 

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

Siehe auch Instanz von Java vermeiden

17
Carlo V. Dango

Wie in den ersten Antworten erörtert, besteht der traditionelle OOP - Ansatz darin, Polymorphismus anstelle von Switches zu verwenden. Es gibt sogar ein gut dokumentiertes Refactoring-Muster für diesen Trick: Bedingte durch Polymorphismus ersetzen . Immer wenn ich nach diesem Ansatz greife, möchte ich auch ein Null-Objekt implementieren, um das Standardverhalten bereitzustellen.

Beginnend mit Java 8 können wir Lambdas und Generics verwenden, um uns etwas zu geben, mit dem funktionale Programmierer sehr vertraut sind: Pattern Matching. Es ist keine zentrale Sprachfunktion, 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 also mit Vorsicht. Während die generischen Methoden es Ihnen ersparen, den übereinstimmenden Wert zu konvertieren, fehlt eine Standardmethode zum Zerlegen des übereinstimmenden Objekts wie beispielsweise mit Scalas Fallklassen .

15
Pavel

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

Hüten Sie sich vor den obigen Ansätzen, die nur auf dem name der Klasse vonA,B,C... basieren: 

Wenn Sie nicht garantieren können, dassA,B,C... (alle Unterklassen oder Implementierer von Base ) dann final sind Unterklassen vonA,B,C... werden nicht behandelt.

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

7
JohnK

Nein, das gibt es nicht. Möglicherweise möchten Sie jedoch Polymorphism als eine Möglichkeit betrachten, um mit dieser Art von Problemen fertig zu werden.

5

Die Verwendung von Schalteranweisungen wie diesem ist nicht objektorientiert. Sie sollten stattdessen die Kraft von Polymorphismus verwenden. Einfach schreiben

this.do()

Nachdem Sie zuvor eine Basisklasse eingerichtet haben:

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

welches ist die Basisklasse für A, B und 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() }
}
5
Raedwald

Mir persönlich gefällt der folgende 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 ausgeben:

YYyy

Der Beispielcode verwendet Strings, Sie können jedoch jeden Objekttyp, einschließlich Class, verwenden. 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
Feiteira

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

Für mich musste die Logik in der in der switch-Anweisung geschriebenen sein, nicht im 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 Java 7 verwenden, können Sie Zeichenfolgenwerte für die Aufzählung angeben, und der Fall für den Fall wird weiterhin funktionieren.

3
Gaʀʀʏ

Sie können einen Switch nicht nur mit den Typen byte, short, char, int, String und Aufzählung ausführen (und mit den Objektversionen der Primitiven. Dies hängt auch von Ihrer Java-Version ab. In Java kann Strings switched sein 7)

2
Tnem

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

Leider ist dies nicht sofort möglich, da die switch-case-Anweisung einen konstanten Ausdruck erwartet. Um dies zu überwinden, wäre eine Möglichkeit die Verwendung von Aufzählungswerten mit den Klassennamen, 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 wie folgt zu verwenden

MyEnumType 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
}
1
Murat Karagöz

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 "umschalten" 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, worauf Sie sich interessieren sollten: Halten Sie die "Types" für alle Klassen, die ClassTypeInterface implementieren, eindeutig. Dies ist kein großes Problem, da bei einer Kreuzung für die Anweisung "switch-case" ein Fehler bei der Kompilierung angezeigt wird.

1

es gibt eine noch einfachere Methode, eine Switch-Struktur zu emulieren, die instanceof verwendet. Dazu erstellen Sie einen Codeblock in Ihrer Methode und benennen ihn mit einem Label. Dann verwenden Sie if-Strukturen, um die Case-Anweisungen zu emulieren. Wenn ein Fall zutrifft, verwenden Sie den Abbruch LABEL_NAME, um die provisorische Switch-Struktur zu verlassen.

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

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

instance.getClass().getName();

gibt eine Zeichenfolge des Klassenimplementierungsnamens zurück. d.h. org.Eclipse.emf.ecore.util.EcoreUtil

instance.getClass().getSimpleName();

gibt die einfache Darstellung zurück, d. h .: EcoreUtil

1
Thomas Haarhoff

Dies wird schneller funktionieren und für den Fall Sinn machen
- Modellklassen können nicht geändert werden (externe Bibliothek)
Der Prozess wird im leistungsabhängigen Kontext ausgeführt
- Sie haben relativ viele "Fälle"

public static <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());
    }
}
0

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

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
Viswanath

Während es nicht möglich ist, eine switch-Anweisung zu schreiben, kann für jeden gegebenen Typ zu einer bestimmten Verarbeitung verzweigt werden. Ein Weg, dies zu tun, besteht darin, einen Standard-Doppelversand-Mechanismus zu verwenden. Ein Beispiel, bei dem wir basierend auf dem Typ "wechseln" möchten, ist 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 (d. H. Die Verwendung eines polymorphen Verfahrens, das jede Ausnahme in eine Fehlerantwort übersetzt), ist die Verwendung eines Doppelversand-Mechanismus 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);
    }
}

Dann, 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";
    }
}
0
Ravi Sanwal

Das Eclipse Modeling Framework hat eine interessante Idee, die auch die Vererbung berücksichtigt. Das Grundkonzept wird im Switch interface definiert: Das Umschalten erfolgt durch Aufrufen der Methode doSwitch .

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

public T caseXXXX(XXXX object);

methode muss implementiert werden (mit einer Standardimplementierung, die 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 Integer-ID. Die Logik ist also ein reiner Schalter:

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 bekommen.

0
Arcanefoam

Erstellen Sie ein Enum mit Klassennamen.

public enum ClassNameEnum {
    A, B, C
}

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 A:
                doA();
                break;
            case B:
                doB();
                break;
            case C:
                doC();
                break;
        }
    }
}

Hoffe das hilft.

0
Siva Kumar

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 angesichts der Tatsache, wie viel Arbeit sie in letzter Zeit in Switches gesteckt haben, denke ich, dass es noch gehen wird. Das Beispiel in der JEP 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 unter Verwendung ihrer Lambda-Syntax und Rückgabe eines Wertes

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

auf jeden Fall haben sie coole Sachen mit Schaltern gemacht.

0
A_Arnold