Est-il possible d'utiliser l'opérateur instanceof dans une instruction switch?

267

J'ai une question d'utiliser le boîtier de commutateur pour l' instanceofobjet:

Par exemple: mon problème peut être reproduit en Java:

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

Comment serait-il mis en œuvre en utilisant switch...case?

olidev
la source
6
Si vous sentez vraiment que vous avez besoin d'un commutateur, vous pouvez hacher le nom de la classe en entier et l'utiliser, faites attention aux éventuels conflits. Ajouter un commentaire plutôt qu'une réponse car je n'aime pas l'idée que cela soit réellement utilisé. Peut-être que vous avez vraiment besoin du modèle de visiteur.
vickirk
1
Depuis java 7, vous pouvez même activer le nom de classe complet pour éviter les conflits de hachage comme l'a souligné @vickirk, mais c'est toujours moche.
Mitja
C'est possible avec le nom de classe comme valeur Enum
Murat Karagöz

Réponses:

225

Il s'agit d'un scénario typique où le polymorphisme de sous-type aide. Procédez comme suit

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

Ensuite , vous pouvez simplement appeler do()sur this.

Si vous n'êtes pas libre de changer A, Bet C, vous pouvez appliquer le modèle de visiteur pour obtenir le même.

jmg
la source
33
Le modèle de visiteur signifie que A, B et C doivent implémenter une interface avec une méthode abstraite qui prend un visiteur comme paramètre d'entrée, que se passe-t-il si vous ne pouvez pas modifier A, B, C et qu'aucun d'eux n'implémente cette interface?
thermz
21
Le dernier commentaire sur le modèle de visiteur est faux. Il vous faudrait quand même faire en sorte que A, B et C implémentent une interface.
Ben Thurley
10
Malheureusement, cela ne fonctionne pas si le code do () - nécessite l'environnement de l'hôte (c'est-à-dire l'accès aux variables non présentes dans le do () lui-même).
mafu
2
La question de @mafu OP concernait la répartition basée sur le type. Si votre méthode do () a besoin de plus d'entrée pour distribuer que votre problème est à mon humble avis en dehors de la portée de la question discutée ici.
jmg
3
cette réponse suppose que vous pouvez modifier les classes A, B, C, alors que je pense que le point est de savoir comment le faire sans modifier A, B, C car elles pourraient être dans une bibliothèque de troisième partie
cloudy_weather
96

si vous ne pouvez absolument pas coder sur une interface, vous pouvez utiliser une énumération comme intermédiaire:

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
la source
thx, j'ai également dû faire quelques changements: 1) initialiser chaque id enum avec la référence de classe; 2) affirmez le nom simple de la classe avec l'énumération id .toString (); 3) trouver l'énumération à travers la référence de classe stockée par identifiant d'énumération. Je pense que cela est également sans danger pour l'obscurcissement.
Aquarius Power
si this.getClass (). getSimpleName () ne correspond pas à une valeur de CLAZZ, il lève une exception ... il vaut mieux entourer d'un bloc try catch et l'exception serait traitée comme l'option "par défaut" ou "else" de le changement
tetri
40

Il suffit de créer une carte où la classe est la clé et la fonctionnalité, c'est-à-dire lambda ou similaire, est la valeur.

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

// bien sûr, refactorisez ceci pour ne l'initialiser qu'une seule fois

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

Si vous avez besoin d'exceptions vérifiées, implémentez une FunctionalInterface qui lève l'exception et utilisez-la à la place de Runnable.

Novaterata
la source
3
Meilleure solution à mon humble avis, spécialement parce qu'elle permet une refactorisation facile.
Feiteira
2
Le seul inconvénient de cette solution est qu'elle ne peut pas utiliser les variables locales (méthode) dans les lambdas (en supposant que cela pourrait être nécessaire bien sûr).
zapatero
1
@zapatero Vous pouvez simplement passer à Function au lieu de Runnable, passer l'instance en tant que paramètre, puis la caster si nécessaire
Novaterata
A voté; c'est l'une des rares réponses qui aide réellement l'OP à faire ce qu'il demande (et oui, il est souvent possible de refactoriser le code pour ne pas avoir à le faire instanceof, et non, mon scénario n'est malheureusement pas de ceux qui s'intègrent facilement dans cette boîte ...)
Per Lundberg
@ SergioGutiérrez Merci. Eh bien, ce modèle ne devrait être nécessaire qu'avec une bibliothèque externe. Et même alors, vous pouvez simplement créer une interface avec une implémentation d'adaptateur à la place, mais c'est utile là où vous voulez que le DIFF comportemental, pour ainsi dire, soit plus évident. Similaire au routage de l'API d'annotation vs fluent, je suppose.
Novaterata
36

Juste au cas où quelqu'un le lirait:

La MEILLEURE solution en java est:

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

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

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

    };

    abstract void doAction (...);
}

Les GRANDS avantages d'un tel modèle sont:

  1. Vous le faites comme (AUCUN interrupteurs du tout):

    void someFunction ( Action action ) {
        action.doAction(...);   
    }
  2. Dans le cas où si vous ajoutez une nouvelle action appelée "d", vous DEVEZ imlement la méthode doAction (...)

REMARQUE: ce modèle est décrit dans le Bloch de Joshua "Effective Java (2nd Edition)"

se.solovyev
la source
1
agréable! Est-ce que ce qui est @Overriderequis au-dessus de chaque implémentation de doAction()?
mateuscb
9
En quoi est-ce la "MEILLEURE" solution? Comment décideriez-vous lequel actionutiliser? Par une instance externe de cascade qui appelle someFunction()avec le bon action? Cela ajoute juste un autre niveau d'indirection.
PureSpider du
1
Non, cela se fera automatiquement lors de l'exécution. Si vous appelez someFunction (Action.a), alors a.doAction sera appelée.
se.solovyev
11
Je ne comprends pas ça. Comment sauriez-vous quelle énumération utiliser? Comme l'a dit @PureSpider, cela semble être juste un autre niveau de travail à faire.
James Manes
2
Il est très triste que vous n'ayez pas proposé d'exemple complet , par exemple , comment mapper une instance de classe a, b ou C à cette énumération. J'essaierai de lancer l'instance dans cette énumération.
Tom
21

Tu ne peux pas. L' switchinstruction ne peut contenir que des caseinstructions qui sont des constantes de temps de compilation et qui s'évaluent en un entier (jusqu'à Java 6 et une chaîne en Java 7).

Ce que vous recherchez est appelé "pattern matching" dans la programmation fonctionnelle.

Voir aussi Éviter instanceof en Java

Carlo V. Dango
la source
1
Non, dans la plupart des langages fonctionnels, vous ne pouvez pas faire correspondre les modèles aux types, uniquement aux constructeurs. C'est du moins vrai dans ML et Haskell. Dans Scala et OCaml, il est possible, mais pas l'application typique de la correspondance de modèles.
jmg
Bien sûr, mais la comparaison avec les constructeurs serait "équivalente" au scénario décrit ci-dessus.
Carlo V. Dango
1
Dans certains cas, mais pas en général.
jmg
Les commutateurs peuvent également prendre en charge les énumérations.
Solomon Ucko
«Vous ne pouvez pas» regarder une langue différente est rarement une réponse utile.
L. Blanc
17

Comme indiqué dans les principales réponses, l'approche OOP traditionnelle consiste à utiliser le polymorphisme au lieu du commutateur. Il existe même un modèle de refactorisation bien documenté pour cette astuce: remplacer le conditionnel par le polymorphisme . Chaque fois que j'atteins cette approche, j'aime également implémenter un objet Null pour fournir le comportement par défaut.

À partir de Java 8, nous pouvons utiliser des lambdas et des génériques pour nous donner quelque chose que les programmeurs fonctionnels connaissent très bien: la mise en correspondance de modèles. Ce n'est pas une fonctionnalité de langage de base, mais la bibliothèque Javaslang fournit une implémentation. Exemple du javadoc :

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

Ce n'est pas le paradigme le plus naturel du monde Java, alors utilisez-le avec prudence. Alors que les méthodes génériques vous éviteront d'avoir à transtyper la valeur correspondante, il nous manque une façon standard de décomposer l'objet correspondant comme avec les classes de cas de Scala par exemple.

Pavel
la source
9

Je sais que c'est très tard mais pour les futurs lecteurs ...

Méfiez-vous des approches ci-dessus qui ne sont basées que sur le nom de la classe A , B , C ...:

A moins que vous ne puissiez garantir que A , B , C ... (toutes les sous-classes ou implémenteurs de Base ) sont finales, puis les sous-classes de A , B , C ... ne seront pas traitées.

Même si l' approche if, elseif, elseif .. est plus lente pour un grand nombre de sous-classes / implémenteurs, elle est plus précise.

JohnK
la source
En effet, vous ne devez jamais utiliser le polymorphisme (aka OOP)
Val
8

Malheureusement, il n'est pas possible de sortir de la boîte car l'instruction switch-case attend une expression constante. Pour surmonter cela, une façon serait d'utiliser des valeurs d'énumération avec les noms de classe, par exemple

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

Avec cela, il est possible d'utiliser l'instruction switch comme ceci

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
la source
Je crois que jusqu'à ce que le problème JEP 8213076 soit entièrement implémenté, c'est la manière la plus propre d'obtenir une instruction switch basée sur le type, sans modifier la classe cible.
Rik Schaaf
5

Non, il n'y a aucun moyen de le faire. Ce que vous pourriez vouloir faire est cependant de considérer le polymorphisme comme un moyen de gérer ce genre de problèmes.

Andreas Johansson
la source
5

L'utilisation d'instructions de commutation comme celle-ci n'est pas orientée objet. Vous devriez plutôt utiliser le pouvoir du polymorphisme . Ecrivez simplement

this.do()

Après avoir configuré une classe de base:

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

qui est la classe de base pour A, Bet 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
la source
@jmg suggeests ( stackoverflow.com/questions/5579309/switch-instanceof/… ) en utilisant une interface au lieu d'une classe de base abstraite. Cela peut être supérieur dans certains cas.
Raedwald
5

Cela fonctionnera plus rapidement et fera sence au cas où
- vous avez relativement de nombreux cas
- le processus est exécuté dans un contexte sensible aux performances

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());
    }
}
Mike
la source
1
Ce n'est pas la même chose que de faire une instanceof, car cela ne fonctionne que si la classe d'implémentation est utilisée pour commuter, mais cela ne fonctionnera pas pour les interfaces / abstractclass / superclasses
lifesoordinary
oui, ce n'est pas
Mike
Un effort, mais à part le commentaire de @ lifesoordinary, vous manquez également la sécurité de type que vous avez normalement, car cette réponse utilise des chaînes codées en dur, au lieu des références de classe. Il est assez facile de faire une faute de frappe, surtout si vous avez besoin d'étendre cette fonctionnalité avec des noms canoniques complets s'il y avait un chevauchement dans les noms de classe avec des noms de package différents.Modifier: faute de frappe fixe (ce qui prouve mon point de vue)
Rik Schaaf
4

Vous ne pouvez pas un commutateur ne fonctionne qu'avec les types byte, short, char, int, String et énumérés (et les versions d'objet des primitives, cela dépend aussi de votre version java, les chaînes peuvent être switchéditées dans java 7)

Tnem
la source
Vous ne pouvez pas activer les chaînes dans Java 6. Et vous ne pouvez pas activer les "versions d'objet des primitives".
Lukas Eder
@Bozho J'ai dit que cela dépend de votre version java, dans Java 7, vous pouvez activer Strings.
Tnem
@Lukas Eder vérifiez vos spécifications java que vous pouvez
Tnem
4

Personnellement, j'aime le code Java 1.8 suivant:

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

Sortira:

YYyy

L'exemple de code utilise des chaînes mais vous pouvez utiliser n'importe quel type d'objet, y compris la classe. par exemple.myCase(this.getClass(), (o) -> ...

Nécessite l'extrait de code suivant:

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
la source
4

Java vous permet désormais de basculer à la manière de l'OP. Ils l'appellent Pattern Matching pour switch. Il est actuellement en cours de rédaction, mais vu combien de travail ils ont mis dans les commutateurs récemment, je pense que cela va passer. L'exemple donné dans le JEP est

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

ou en utilisant leur syntaxe lambda et en retournant une valeur

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

de toute façon, ils ont fait des trucs sympas avec des interrupteurs.

A_Arnold
la source
3

Si vous pouvez manipuler l'interface commune, vous pouvez ajouter une énumération et demander à chaque classe de renvoyer une valeur unique. Vous n'aurez pas besoin de instanceof ou d'un modèle de visiteur.

Pour moi, la logique devait être dans l'écrit dans l'instruction switch, pas l'objet lui-même. C'était ma solution:

ClassA, ClassB, and ClassC implement CommonClass

Interface:

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

Si vous utilisez Java 7, vous pouvez mettre des valeurs de chaîne pour l'énumération et le bloc de casse de commutateur fonctionnera toujours.

Gaʀʀʏ
la source
Le valuechamp est redondant si vous souhaitez uniquement distinguer les constantes d'énumération - vous pouvez utiliser les constantes directement (comme vous le faites).
user905686
2

Que dis-tu de ça ?

switch (this.name) 
{
  case "A":
    doA();
    break;
  case "B":
    doB();
    break;
  case "C":
    doC();
    break;
  default:
    console.log('Undefined instance');
}
Joeri
la source
3
Doit souligner que cela ne fonctionne que sur Java 7. Et que vous devez appeler this.getSimpleName()Je ne sais pas si l'affiche est confondue avec JS (ouais, il utilise la console, hehe).
pablisco
5
Cela a un problème de chute de la transparence référentielle du code source. Autrement dit, votre IDE ne sera pas en mesure de maintenir l'intégrité de référence. Supposons que vous souhaitiez renommer votre nom. La réflexion est mauvaise.
Val
1
Pas une bonne idée. Les noms de classe ne sont pas uniques si vous avez plusieurs chargeurs de classe.
Doradus
Casse quand il s'agit de compression de code (→ ProGuard)
Matthias Ronge
1

Je pense qu'il y a des raisons d'utiliser une instruction switch. Si vous utilisez peut-être du code généré par xText. Ou un autre type de classes générées par EMF.

instance.getClass().getName();

renvoie une chaîne du nom d'implémentation de classe. c'est-à-dire: org.eclipse.emf.ecore.util.EcoreUtil

instance.getClass().getSimpleName();

renvoie la représentation simple ie: EcoreUtil

Thomas Haarhoff
la source
Vous ne pouvez pas l'utiliser switchcomme casecondition car ce n'est pas une valeur constante
B-GangsteR
1

Si vous devez "basculer" via le type de classe de "cet" objet, cette réponse est la meilleure https://stackoverflow.com/a/5579385/2078368

Mais si vous devez appliquer "switch" à toute autre variable. Je proposerais une autre solution. Définissez l'interface suivante:

public interface ClassTypeInterface {
    public String getType();
}

Implémentez cette interface dans chaque classe que vous souhaitez "basculer". Exemple:

public class A extends Something implements ClassTypeInterface {

    public final static String TYPE = "A";

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

Après cela, vous pouvez l'utiliser de la manière suivante:

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

La seule chose à laquelle vous devriez vous soucier - garder les "types" uniques dans toutes les classes implémentant ClassTypeInterface. Ce n'est pas un gros problème, car en cas d'intersection, vous recevez une erreur de compilation pour l'instruction "switch-case".

Sergey Krivenkov
la source
Au lieu d'utiliser String pour le TYPE, vous pouvez utiliser une énumération et l'unicité est garantie (comme cela est fait dans cette réponse ). Cependant, avec l'une des approches, vous devrez refactoriser à deux endroits lorsque vous effectuez un changement de nom.
user905686
@ user905686 renommer quoi? Dans l'exemple actuel, le type "A" est défini dans la classe Something pour minimiser la quantité de code. Mais dans la vraie vie, vous devez évidemment le définir à l'extérieur (dans un lieu commun) et il n'y a aucun problème avec une refactorisation supplémentaire.
Sergey Krivenkov
Je veux dire renommer la classe A. Le refactoring automatique pourrait ne pas inclure la variable TYPE = "A"lors du renommage. Surtout si elle est en dehors de la classe correspondante, on peut également l'oublier en la faisant manuellement. IntelliJ trouve également les occurrences du nom de classe dans des chaînes ou des commentaires, mais ce n'est qu'une recherche de texte (au lieu de regarder l'arbre de syntaxe) et inclut donc les faux positifs.
user905686
@ user905686 c'est juste un exemple, pour visualiser l'idée. N'utilisez pas de chaîne pour les définitions de type dans un projet réel, déclarez un support de classe MyTypes avec des constantes entières (ou enum) et utilisez-les dans les classes implémentant ClassTypeInterface.
Sergey Krivenkov
1

Voici une manière fonctionnelle de l'accomplir dans Java 8 en utilisant 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
la source
1

Bien qu'il ne soit pas possible d'écrire une instruction switch, il est possible de passer à un traitement spécifique pour chaque type donné. Une façon de procéder consiste à utiliser un mécanisme de double répartition standard. Un exemple où nous voulons «basculer» en fonction du type est le mappeur d'exceptions Jersey où nous devons mapper une multitude d'exceptions aux réponses aux erreurs. Alors que dans ce cas spécifique, il existe probablement un meilleur moyen (c'est-à-dire en utilisant une méthode polymorphe qui traduit chaque exception en une réponse d'erreur), l'utilisation du mécanisme de double répartition est toujours utile et pratique.

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

Ensuite, partout où le "commutateur" est nécessaire, vous pouvez le faire comme suit:

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
la source
Cela ressemble beaucoup au modèle Visitor, qui a déjà été discuté dans cette réponse: stackoverflow.com/a/5579385
typeracer
0

Créez une énumération avec des noms de classe.

public enum ClassNameEnum {
    A, B, C
}

Recherchez le nom de classe de l'objet. Écrivez un commutateur sur l'énumération.

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

J'espère que cela t'aides.

Siva Kumar
la source
2
Contrairement à cette approche où le couplage entre les constantes enum et les classes se fait explicitement, vous effectuez le couplage implicitement par nom de classe. Cela cassera votre code lorsque vous renommez uniquement l'une des constantes enum ou la classe alors que l'autre approche fonctionnerait toujours.
user905686
0

Le cadre de modélisation Eclipse a une idée intéressante qui considère également l'héritage. Le concept de base est défini dans l' interface Switch : la commutation se fait en appelant le doSwitch méthode .

Ce qui est vraiment intéressant, c'est l'implémentation. Pour chaque type d'intérêt, un

public T caseXXXX(XXXX object);

doit être implémentée (avec une implémentation par défaut renvoyant null). L' implémentation doSwitch tentera d'appeler toutes les méthodes caseXXX sur l'objet pour toute sa hiérarchie de types. Quelque chose dans les lignes de:

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;

Le cadre réel utilise un identifiant entier pour chaque classe, donc la logique est en fait un simple commutateur:

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

Vous pouvez consulter une implémentation complète de l' ECoreSwitch pour vous faire une meilleure idée.

Arcanefoam
la source
-1

il existe un moyen encore plus simple d'émuler une structure de commutateur qui utilise instanceof, vous pouvez le faire en créant un bloc de code dans votre méthode et en le nommant avec une étiquette. Vous utilisez ensuite les structures if pour émuler les instructions case. Si un cas est vrai, vous utilisez la pause LABEL_NAME pour sortir de votre structure de commutateur de fortune.

        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
la source
Comment est-ce mieux que le if... else ifcode donné par l'OP?
typeracer
Juste pour développer mon commentaire précédent: ce que vous proposez est essentiellement de remplacer if... else ifpar des instructions "goto", ce qui est la mauvaise façon d'implémenter le flux de contrôle dans des langages comme Java.
typeracer