web-dev-qa-db-fra.com

Changer d'instance?

J'ai une question d'utilisation de la casse de commutateur pour l'objet instanceof:

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 cela serait-il implémenté en utilisant switch...case?

215
olidev

Ceci est un scénario typique où le polymorphisme de sous-type aide. Faire ce qui 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, B et C, vous pouvez appliquer le modèle de visiteur pour obtenir le même résultat.

196
jmg

si vous ne pouvez absolument pas coder vers 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;

}
79
Nico

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 les suivants:

  1. Vous le faites juste comme (AUCUN commutateur):

    void someFunction ( Action action ) {
        action.doAction(...);   
    }
    
  2. Si vous ajoutez une nouvelle action appelée "d", vous DEVEZ utiliser la méthode doAction (...)

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

34
se.solovyev

Créez simplement une mappe où la classe est la clé et la fonctionnalité, par exemple, 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, refactoriser ceci pour ne l'initialiser qu'une seule fois

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

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

20
Novaterata

Tu ne peux pas. L'instruction switch ne peut contenir que des instructions case qui sont des constantes de compilation et qui donnent un entier (jusqu'à Java 6 et une chaîne dans Java 7). 

Ce que vous recherchez est appelé "correspondance de modèle" dans la programmation fonctionnelle.

Voir aussi Eviter instanceof en Java

17
Carlo V. Dango

Comme indiqué dans les réponses les plus fréquentes, l’approche traditionnelle OOP consiste à utiliser le polymorphisme au lieu du commutateur. Il existe même un schéma de refactoring bien documenté pour cette astuce: Remplacer le conditionnel par le polymorphisme . Lorsque 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 les lambdas et les génériques pour nous familiariser avec les programmeurs fonctionnels: la correspondance des modèles. Ce n'est pas une fonctionnalité de base mais la bibliothèque Javaslang fournit une implémentation. Exemple tiré 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, utilisez-le avec prudence. Alors que les méthodes génériques vous éviteront de devoir transtyper la valeur correspondante, il nous manque un moyen standard de décomposer l'objet correspondant comme avec les classes de cas de Scala par exemple.

15
Pavel

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

Méfiez-vous des approches ci-dessus qui ne reposent que sur le name de la classe deA,B,C...: 

Sauf si vous pouvez garantir queA,B,C... (toutes les sous-classes ou tous ceux qui implémentent Base ) sont final then les sous-classes deA,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.

7
JohnK

Non, il n'y a aucun moyen de faire cela. Ce que vous voudrez peut-être faire, c’est toutefois de considérer Polymorphisme comme un moyen de traiter ce type de problèmes.

5

L'utilisation d'instructions switch comme celle-ci n'est pas orientée objet. Vous devriez plutôt utiliser le pouvoir de polymorphism . Écrivez simplement

this.do()

Ayant préalablement configuré une classe de base:

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

qui est la classe de base pour A, B et 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

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 d'instanceof ou d'un modèle de visiteur.

Pour moi, la logique devait être écrite 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 définir des valeurs de chaîne pour l'énum et le bloc de casse de commutateur fonctionnera toujours.

3
Gaʀʀʏ

Vous ne pouvez pas utiliser un commutateur uniquement avec les types byte, short, char, int, String et énuméré (et les versions d'objet des primitives, cela dépend également de votre version de Java. Les chaînes peuvent être switched dans Java 7).

2
Tnem

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

Malheureusement, ce n’est pas possible immédiatement car l’instruction switch-case attend une expression constante. Pour résoudre ce problème, une solution consisterait à utiliser des valeurs enum 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

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

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

Mais si vous devez appliquer "switch" à une autre variable. Je suggérerais une autre solution. Définir 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 qui devrait vous préoccuper est de garder les "types" uniques dans toutes les classes implémentant ClassTypeInterface. Ce n'est pas un gros problème, car dans le cas d'une intersection, vous recevez une erreur de compilation lors de l'instruction "switch-case".

1
Sergey Krivenkov

il existe un moyen encore plus simple d'émuler une structure de commutateur utilisant instanceof. Pour ce faire, vous créez un bloc de code dans votre méthode et vous le nommez avec une étiquette. Ensuite, vous utilisez des structures if pour émuler les instructions de cas. Si un cas est vrai, vous utilisez la balise LABEL_NAME pour sortir de la structure de votre 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;
            }
        }
1
Maurice

Je pense qu'il y a des raisons d'utiliser une instruction switch. Si vous utilisez du code généré par xText, par exemple. 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 la classe. c'est à dire: org.Eclipse.emf.ecore.util.EcoreUtil

instance.getClass().getSimpleName();

renvoie la représentation simple, à savoir: EcoreUtil

1
Thomas Haarhoff

Cela fonctionnera plus rapidement et fera sens au cas où
- vous ne pouvez pas changer de classe de modèle (bibliothèque externe)
- le processus est exécuté dans un contexte sencitif de performance
- vous avez relativement beaucoup de 'cas'

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
Mykhaylo Adamovych

Voici un moyen fonctionnel de le réaliser en 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)
        );
    }
0
Viswanath

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 le mécanisme standard de double dispatch. Un exemple où nous voulons "basculer" en fonction du type est le mappeur Jersey Exception où nous devons mapper une multitude d'exceptions aux réponses d'erreur. Bien que dans ce cas spécifique, il existe probablement un meilleur moyen (à savoir, l’utilisation d’une méthode polymorphe traduisant chaque exception en réponse à une erreur), l’utilisation du mécanisme de double distribution reste 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";
    }
}
0
Ravi Sanwal

Le cadre de modélisation Eclipse a une idée intéressante qui prend également en compte l'héritage. Le concept de base est défini dans le commutateur interface : la commutation est effectuée en appelant la méthode doSwitch .

Ce qui est vraiment intéressant, c'est la mise en oeuvre. Pour chaque type d’intérêt, un

public T caseXXXX(XXXX object);

method doit être implémenté (avec une implémentation par défaut renvoyant null). L'implémentation doSwitch tentera d'appeler toutes les méthodes caseXXX de 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;

La structure actuelle utilise un identifiant entier pour chaque classe, la logique est donc un commutateur pur:

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 regarder une implémentation complète de ECoreSwitch pour avoir une meilleure idée.

0
Arcanefoam

Créez un Enum avec les noms de classe.

public enum ClassNameEnum {
    A, B, C
}

Recherchez le nom de classe de l'objet . Ecrivez un commutateur cas sur l'enum.

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.

0
Siva Kumar

Java vous permet maintenant de basculer à la manière de l'OP. Ils l'appellent Pattern Matching pour switch. Il est actuellement en phase de brouillon, mais compte tenu du travail considérable qu’ils ont consacré aux commutateurs, je pense que le projet va aboutir. L’exemple donné dans le PEC 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.

0
A_Arnold