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?
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.
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;
}
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:
Du machst es einfach wie (KEINE Schalter):
void someFunction ( Action action ) {
action.doAction(...);
}
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.
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.
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
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 .
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.
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.
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() }
}
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);
}
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.
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 switch
ed sein 7)
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');
}
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
}
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.
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;
}
}
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
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());
}
}
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)
);
}
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";
}
}
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.
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.
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.