Czy można użyć operatora instanceof w instrukcji switch?

267

Mam pytanie o użycie skrzynki rozdzielczej dla instanceofobiektu:

Na przykład: mój problem można odtworzyć w Javie:

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

Jak można to zaimplementować za pomocą switch...case?

olidev
źródło
6
Jeśli naprawdę uważasz, że potrzebujesz przełącznika, możesz zahaczyć nazwę klasy na int i użyć tego, uważaj jednak na możliwe starcia. Dodanie raczej komentarza niż odpowiedzi, ponieważ nie podoba mi się, że pomysł został wykorzystany. Być może to, czego naprawdę potrzebujesz, to wzór odwiedzin.
vickirk
1
Począwszy od java 7, można nawet włączyć w pełni kwalifikowaną nazwę klasy, aby uniknąć takich konfliktów mieszania, jak wskazał @vickirk, ale nadal jest brzydka.
Mitja
Jest to możliwe z nazwą klasy jako wartością Enum
Murat Karagöz

Odpowiedzi:

225

Jest to typowy scenariusz, w którym pomaga polimorfizm podtypu. Wykonaj następujące czynności

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

Następnie można po prostu zadzwonić do()na this.

Jeśli nie masz możliwości zmiany A, Bi Cmożesz zastosować wzór odwiedzin, aby osiągnąć to samo.

jmg
źródło
33
Wzorzec gościa oznacza, że ​​A, B i C muszą implementować interfejs za pomocą metody abstrakcyjnej, która przyjmuje Odwiedzającego jako parametr wejściowy, co jeśli nie możesz zmienić A, B, C i żaden z nich nie implementuje tego interfejsu?
thermz
21
Ostatni komentarz na temat wzoru odwiedzającego jest nieprawidłowy. Nadal będziesz musiał wprowadzić interfejs A, B i C.
Ben Thurley
10
Niestety nie działa to, jeśli kod do () wymaga środowiska hosta (tj. Dostępu do zmiennych nieobecnych w samym do ()).
mafu
2
Pytanie @mafu OP dotyczyło wysyłki opartej na typach. Jeśli twoja metoda do () wymaga więcej danych do wysłania, niż twój problem jest IMHO poza zakresem omawianego tutaj pytania.
jmg
3
ta odpowiedź zakłada, że ​​możesz modyfikować klasy A, B, C, podczas gdy myślę, że chodzi o to, jak to zrobić bez modyfikacji A, B, C, ponieważ mogą one znajdować się w bibliotece trzeciej części
cloudy_weather
96

jeśli absolutnie nie możesz kodować do interfejsu, możesz użyć wyliczenia jako pośrednika:

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;

}
Nico
źródło
dzięki, musiałem również wprowadzić pewne zmiany: 1) zainicjować każdy identyfikator wyliczenia za pomocą odwołania do klasy; 2) podaj prostą nazwę klasy za pomocą enum id .toString (); 3) znajdź wyliczenie przez zachowane odwołanie do klasy na identyfikator wyliczenia. Myślę, że wtedy jest to również bezpieczne zaciemnianie.
Wodnik Moc
jeśli this.getClass (). getSimpleName () nie pasuje do wartości CLAZZ, zgłasza wyjątek ... lepiej jest otoczyć blokiem try catch, a wyjątek byłby traktowany jako opcja „default” lub „else” przełącznik
tetri
40

Wystarczy utworzyć mapę, w której klasa jest kluczem, a funkcjonalność, tj. Lambda lub podobna, jest wartością.

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

// oczywiście zrefaktoryzuj to, aby zainicjować tylko raz

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

Jeśli potrzebujesz zaznaczonych wyjątków, zaimplementuj interfejs FunctionalInterface, który zgłasza wyjątek i użyj go zamiast Runnable.

Novaterata
źródło
3
Najlepsze rozwiązanie imho, szczególnie dlatego, że umożliwia łatwe refaktoryzowanie.
Feiteira,
2
Jedynym minusem tego rozwiązania jest to, że nie może ono wykorzystywać zmiennych lokalnych (metody) w lambdach (zakładając, że może to być oczywiście potrzebne).
zapatero
1
@zapatero Możesz po prostu zmienić na Funkcję zamiast Runnable, przekazać instancję jako parametr, a następnie
przesłać
Pozytywne; jest to jedna z niewielu odpowiedzi, które faktycznie pomagają OP robić to, o co prosi (i tak, często można zrefaktoryzować kod, aby nie musiał robić instanceof, i nie, mój scenariusz niestety nie jest jednym z tych, które łatwo pasują do to pudełko ...)
Per Lundberg,
@ SergioGutiérrez Thanks. Ten wzorzec powinien być potrzebny tylko w bibliotece zewnętrznej. I nawet wtedy możesz po prostu utworzyć interfejs z implementacją adaptera, ale jest to przydatne tam, gdzie chcesz, aby, mówiąc inaczej, behawioralny DIFF był bardziej oczywisty. Przypuszczam, że podobny do routingu API biegły vs adnotacyjny.
Novaterata
36

Na wszelki wypadek, jeśli ktoś to przeczyta:

Najlepszym rozwiązaniem w java jest:

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

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

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

    };

    abstract void doAction (...);
}

WIELKIE zalety takiego wzoru to:

  1. Po prostu robisz to tak (żadnych przełączników):

    void someFunction ( Action action ) {
        action.doAction(...);   
    }
  2. W przypadku dodania nowej akcji o nazwie „d” MUSISZ zastosować metodę doAction (...)

UWAGA: Ten wzór jest opisany w Blos Joshua „Effective Java (2nd Edition)”

se.solovyev
źródło
1
miły! Czy @Overridewymagane jest każde wdrożenie doAction()?
mateuscb
9
Jak to jest „NAJLEPSZE” rozwiązanie? Jak zdecydowałbyś actionsię użyć? Przez zewnętrzną instancję kaskady, która dzwoni someFunction()z poprawnym action? To tylko dodaje kolejny poziom pośredni.
PureSpider
1
Nie, zostanie to zrobione automatycznie w czasie wykonywania. Jeśli wywołasz someFunction (Action.a), to zostanie wywołana a.doAction.
se.solovyev
11
Nie rozumiem tego Skąd miałbyś wiedzieć, którego wyliczenia użyć? Jak powiedział @PureSpider, wydaje się, że to kolejny poziom pracy do wykonania.
James Manes
2
To bardzo smutne, że nie podałeś pełnego przykładu , np. Jak zmapować dowolne wystąpienie klasy a, b lub C do tego wyliczenia. Spróbuję rzucić Instancję na ten Enum.
Tom
21

Nie możesz switchZestawienie może zawierać tylko casedeklaracje, które są kompilacji stałe czasowe i które ocenia się całkowitą (do Java 6 i ciąg w Javie 7).

To, czego szukasz, nazywa się „dopasowaniem wzorców” w programowaniu funkcjonalnym.

Zobacz także Unikanie instancji w Javie

Carlo V. Dango
źródło
1
Nie, w większości języków funkcjonalnych nie można dopasowywać wzorców do typów, tylko do konstruktorów. Dotyczy to przynajmniej ML i Haskell. W Scali i OCaml jest możliwe, ale nie typowe zastosowanie dopasowania wzorca.
jmg
Jasne, ale sprawdzanie konstruktorów byłoby „równoważne” ze scenariuszem opisanym powyżej.
Carlo V. Dango,
1
W niektórych przypadkach, ale nie ogólnie.
jmg
Przełączniki mogą również obsługiwać wyliczenia.
Solomon Ucko
„Nie możesz” spojrzeć na inny język rzadko jest pomocną odpowiedzią.
L. Blanc
17

Jak omówiono w najważniejszych odpowiedziach, tradycyjne podejście OOP polega na użyciu polimorfizmu zamiast zamiany. Istnieje nawet dobrze udokumentowany wzorzec refaktoryzacji dla tej sztuczki: Zamień warunkowy na polimorfizm . Ilekroć sięgam po to podejście, lubię również implementować obiekt Null, aby zapewnić domyślne zachowanie.

Począwszy od Java 8, możemy używać lambd i generics, aby dać nam coś, co programiści są bardzo obeznani z: dopasowaniem wzorców. To nie jest podstawowa funkcja języka, ale biblioteka JavaScript zapewnia jedną implementację. Przykład z javadoc :

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

To nie jest najbardziej naturalny paradygmat w świecie Java, więc używaj go ostrożnie. Chociaż ogólne metody pozwolą ci uniknąć konieczności typowania wartości dopasowanej, brakuje nam standardowego sposobu dekompozycji dopasowanego obiektu, jak na przykład w przypadku klas przypadków Scali .

Pavel
źródło
9

Wiem, że jest bardzo późno, ale dla przyszłych czytelników ...

Uważaj na powyższe podejścia, które opierają się tylko na nazwie klasy A , B , C ...:

O ile nie możesz zagwarantować, że A , B , C ... (wszystkie podklasy lub realizatorzy Base ) są ostateczne, podklasy A , B , C ... nie będą rozpatrywane.

Chociaż podejście if, elseif, elseif .. jest wolniejsze dla dużej liczby podklas / implementatorów, jest bardziej dokładne.

JohnK
źródło
Rzeczywiście, nigdy nie powinieneś używać polimorfizmu (aka OOP)
Val
8

Niestety nie jest to możliwe od razu po wyjęciu z pudełka, ponieważ instrukcja case-switch oczekuje stałego wyrażenia. Aby temu zaradzić, jednym ze sposobów byłoby użycie wartości wyliczeniowych z nazwami klas, np

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

Dzięki temu możliwe jest użycie instrukcji switch w ten sposób

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
}
Murat Karagöz
źródło
Wierzę, że dopóki problem JEP 8213076 nie zostanie w pełni wdrożony, jest to najczystszy sposób uzyskania instrukcji switch na podstawie typu, bez modyfikowania klasy docelowej.
Rik Schaaf
5

Nie, nie ma na to sposobu. Możesz jednak rozważyć polimorfizm jako sposób na poradzenie sobie z tego rodzaju problemami.

Andreas Johansson
źródło
5

Używanie instrukcji switch w ten sposób nie jest zorientowane obiektowo. Zamiast tego powinieneś użyć mocy polimorfizmu . Po prostu napisz

this.do()

Po wcześniejszym skonfigurowaniu klasy podstawowej:

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

który jest klasą bazową dla A, Bi 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() }
}
Raedwald
źródło
@jmg suggeests ( stackoverflow.com/questions/5579309/switch-instanceof/… ) przy użyciu interfejsu zamiast abstrakcyjnej klasy bazowej. To może być lepsze w niektórych przypadkach cyrkowych.
Raedwald,
5

Będzie to działało szybciej i sprawi, że w przypadku
- gdy masz stosunkowo wiele „przypadków”
- proces jest wykonywany w kontekście wrażliwym na wydajność

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());
    }
}
Mikrofon
źródło
1
To nie jest to samo, co robienie instanceof, ponieważ działa to tylko wtedy, gdy do implementacji używana jest klasa implementacyjna, ale nie będzie działać dla interfejsów / klasy abstrakcyjnej / superklasy
lifesoordinary 25.04.19
tak, to nie jest
Mike
Dla wysiłku, ale oprócz komentarza @ lifesoordinary, brakuje również bezpieczeństwa typowego, które normalnie masz, ponieważ ta odpowiedź używa zakodowanych ciągów zamiast odwołań do klas. Jest to dość łatwo popełnić literówkę, szczególnie jeśli trzeba by rozszerzyć tę funkcjonalność z pełnych nazw kanonicznych jeśli tu jakaś pokrywają się z nazwami klas pakietu innym names.Edit: Fixed literówki (co trochę udowodni mi chodzi)
Rik Schaaf
4

Nie możesz przełącznika działać tylko z typami bajtów, krótkich, char, int, String i wyliczonymi (i obiektowymi wersjami prymitywów, zależy to również od wersji java, ciągi można switchedytować w java 7)

Tnem
źródło
Nie można włączyć ciągów w Javie 6. Nie można też włączyć „obiektowych wersji prymitywów”.
Lukas Eder,
@Bozho Powiedziałem, że to zależy od twojej wersji Java, w Javie 7 możesz włączyć Strings.
Tnem,
@Lukas Eder sprawdź swoją specyfikację Java, którą możesz
Tnem,
4

Osobiście podoba mi się następujący kod Java 1.8:

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

Wyjdzie:

YYyy

W przykładowym kodzie zastosowano ciągi, ale można użyć dowolnego typu obiektu, w tym klasy. na przykład.myCase(this.getClass(), (o) -> ...

Potrzebuje następującego fragmentu kodu:

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);
}
Feiteira
źródło
4

Java pozwala teraz przełączać się w sposób OP. Nazywają to dopasowywanie wzorców dla przełącznika. Obecnie jest w fazie Draft, ale biorąc pod uwagę, ile pracy włożono ostatnio w przełączniki, myślę, że to przejdzie. Przykład podany w JEP to

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

lub używając ich składni lambda i zwracając wartość

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

tak czy inaczej robili fajne rzeczy z przełącznikami.

A_Arnold
źródło
3

Jeśli możesz manipulować wspólnym interfejsem, możesz dodać wartość dodaną w wyliczeniu, a każda klasa zwróci unikalną wartość. Nie potrzebujesz instancji ani wzorca odwiedzającego.

Dla mnie logika musiała być zapisana w instrukcji switch, a nie sam obiekt. To było moje rozwiązanie:

ClassA, ClassB, and ClassC implement CommonClass

Berło:

public interface CommonClass {
   MyEnum getEnumType();
}

Enum:

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

Jeśli korzystasz z java 7, możesz wstawić wartości ciągu dla wyliczenia, a blok skrzynki przełączników będzie nadal działał.

Gaʀʀʏ
źródło
valuePole jest zbędny, jeśli tylko chcą odróżnić stałe enum - można bezpośrednio używać stałych (jak to zrobić).
user905686,
2

Co powiesz na to ?

switch (this.name) 
{
  case "A":
    doA();
    break;
  case "B":
    doB();
    break;
  case "C":
    doC();
    break;
  default:
    console.log('Undefined instance');
}
Joeri
źródło
3
Należy zauważyć, że działa to tylko w Javie 7. I że musisz zadzwonić this.getSimpleName()Nie jestem pewien, czy plakat jest pomylony z JS (tak, używa konsoli, hehe).
pablisco
5
Ma to problem polegający na wypadnięciu przejrzystości referencyjnej kodu źródłowego. Oznacza to, że twoje IDE nie będzie w stanie utrzymać integralności odniesienia. Załóżmy, że chcesz zmienić nazwę swojego imienia. Odbicie jest złe.
Val
1
Niezły pomysł. Nazwy klas nie są unikalne, jeśli masz wiele programów ładujących klasy.
Doradus,
Łamie się, jeśli chodzi o kompresję kodu (→ ProGuard)
Matthias Ronge
1

Myślę, że istnieją powody, aby używać instrukcji switch. Jeśli używasz kodu wygenerowanego xText, być może. Lub inny rodzaj klas generowanych przez EMF.

instance.getClass().getName();

zwraca ciąg nazwy implementacji klasy. tj .: org.eclipse.emf.ecore.util.EcoreUtil

instance.getClass().getSimpleName();

zwraca prostą reprezentację, tj .: EcoreUtil

Thomas Haarhoff
źródło
Nie można go użyć switchjako casewarunku, ponieważ nie jest to stała wartość
B-GangsteR
1

Jeśli potrzebujesz „przełączyć” typ klasy „tego” obiektu, ta odpowiedź jest najlepsza https://stackoverflow.com/a/5579385/2078368

Ale jeśli musisz zastosować „zmień” na dowolną inną zmienną. Sugerowałbym inne rozwiązanie. Zdefiniuj następujący interfejs:

public interface ClassTypeInterface {
    public String getType();
}

Zaimplementuj ten interfejs w każdej klasie, którą chcesz „przełączyć”. Przykład:

public class A extends Something implements ClassTypeInterface {

    public final static String TYPE = "A";

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

Następnie możesz go użyć w następujący sposób:

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

Jedyną rzeczą, na którą powinieneś zwrócić uwagę - zachować unikalność „typów” we wszystkich klasach implementujących interfejs ClassTypeInterface. Nie jest to duży problem, ponieważ w przypadku jakiegokolwiek skrzyżowania pojawia się błąd czasu kompilacji dla instrukcji „case-case”.

Siergiej Krivenkov
źródło
Zamiast używać ciągu znaków do TYPE, możesz użyć wyliczenia, a unikalność jest gwarantowana (tak jak w tej odpowiedzi ). Jednak przy każdym z tych podejść będziesz musiał dokonać refaktoryzacji w dwóch miejscach po zmianie nazwy.
user905686,
@ user905686 zmienić nazwę czego? W obecnym przykładzie typ „A” jest zdefiniowany wewnątrz klasy Something, aby zminimalizować ilość kodu. Ale w rzeczywistości powinieneś oczywiście zdefiniować to na zewnątrz (w jakimś wspólnym miejscu) i nie ma żadnych problemów z dalszą refaktoryzacją.
Sergey Krivenkov
Mam na myśli zmianę nazwy klasy A. Automatyczne refaktoryzacja może nie uwzględniać zmiennej TYPE = "A"podczas zmiany nazwy. Zwłaszcza jeśli znajduje się poza odpowiednią klasą, można również zapomnieć o tym, wykonując to ręcznie. IntelliJ faktycznie znajduje również wystąpienia nazwy klasy w ciągach lub komentarzach, ale jest to po prostu wyszukiwanie tekstowe (zamiast patrzenia na drzewo składniowe), a zatem zawiera fałszywe alarmy.
user905686
@ user905686 to tylko przykład wizualizacji pomysłu. Nie używaj ciągu znaków do definicji typu w prawdziwym projekcie, deklaruj uchwyt klasy MyTypes ze stałymi liczbami całkowitymi (lub wyliczeniami) i używaj ich w klasach implementujących ClassTypeInterface.
Sergey Krivenkov
1

Oto funkcjonalny sposób na osiągnięcie tego w Javie 8 przy użyciu http://www.vavr.io/

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)
        );
    }
Viswanath
źródło
1

Chociaż nie jest możliwe napisanie instrukcji switch, możliwe jest rozgałęzienie do określonego przetwarzania dla każdego danego typu. Jednym ze sposobów jest użycie standardowego mechanizmu podwójnej wysyłki. Przykładem, w którym chcemy „przełączyć” w zależności od typu, jest program mapujący wyjątek Jersey, w którym musimy zmapować wiele wyjątków od odpowiedzi na błędy. Chociaż w tym konkretnym przypadku istnieje prawdopodobnie lepszy sposób (tj. Zastosowanie metody polimorficznej, która tłumaczy każdy wyjątek na odpowiedź na błąd), użycie mechanizmu podwójnej wysyłki jest nadal przydatne i praktyczne.

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

Tam, gdzie kiedykolwiek potrzebny jest „przełącznik”, możesz to zrobić w następujący sposób:

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";
    }
}
Ravi Sanwal
źródło
Wygląda to bardzo podobnie do wzorca gościa, który został już omówiony w tej odpowiedzi: stackoverflow.com/a/5579385
typeracer
0

Utwórz Enum z nazwami klas.

public enum ClassNameEnum {
    A, B, C
}

Znajdź nazwę klasy obiektu. Napisz skrzynkę przełączającą nad wyliczeniem.

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

Mam nadzieję że to pomoże.

Siva Kumar
źródło
2
W przeciwieństwie do tego podejścia, w którym sprzężenie między stałymi wyliczającymi a klasami odbywa się jawnie, sprzężenie wykonuje się niejawnie według nazwy klasy. Spowoduje to uszkodzenie kodu, gdy zmienisz nazwę tylko jednej ze stałych enum lub klasy, podczas gdy drugie podejście nadal będzie działać.
user905686,
0

Środowisko modelowania Eclipse ma interesujący pomysł, który uwzględnia również dziedziczenie. Podstawowa koncepcja została zdefiniowana w interfejsie Switch : przełączanie odbywa się poprzez wywołanie metody doSwitch .

Naprawdę ciekawe jest wdrożenie. Dla każdego rodzaju zainteresowania:

public T caseXXXX(XXXX object);

Metoda musi zostać zaimplementowana (z domyślną implementacją zwracającą wartość null). DoSwitch realizacja będzie próbował zadzwonić AL caseXXX metod na obiekcie dla całej jego rodzaj hierarchii. Coś w linii:

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;

Rzeczywista struktura używa identyfikatora liczb całkowitych dla każdej klasy, więc logika jest w rzeczywistości czystym przełącznikiem:

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:
    {
      ...
    }
  ...

Możesz spojrzeć na pełną implementację ECoreSwitch, aby uzyskać lepszy pomysł.

Arcanefoam
źródło
-1

istnieje jeszcze prostszy sposób emulacji struktury przełącznika, który korzysta z instanceof. Robisz to, tworząc blok kodu w metodzie i nazywając go etykietą. Następnie używasz struktur if do emulacji instrukcji case. Jeśli przypadek jest prawdziwy, użyj przerwy LABEL_NAME, aby wyjść ze struktury prowizorycznego przełącznika.

        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;
            }
        }
Maurice
źródło
Jak to jest lepsze niż if... else ifkod podany przez OP?
typeracer
Aby rozwinąć mój poprzedni komentarz: to, co proponujesz, to zasadniczo zastąpienie if... else ifwyrażeniami „goto”, co jest złym sposobem na wdrożenie kontroli w takich językach jak Java.
typeracer