Basculer sur le type en java


Avant de commencer, je sais qu'il y a un tas de réponses à cette question qui suggèrent des approches alternatives. Je cherche de l'aide pour cette approche particulière quant à savoir s'il est possible, et sinon, des approches similaires qui pourraient fonctionner.

J'ai une méthode qui prend une superclasse et appelle une méthode basée sur le type de l'objet passé. par exemple:

public void handle(Object o){
  if (o instanceof A)
    handleA((A)o);
  else if (o instanceof B)
    handleB((B)o);
  else if (o instanceof C)
    handleC((C)o);
  else 
    handleUnknown(o);

Je ne peux pas modifier les sous-types pour remplacer une méthode handle(), comme cette réponse le suggérerait, parce que je n'ai pas les classes. Donc, la méthode instanceof est tout ce que j'ai.

J'aimerais utiliser une instruction switch au lieu de if/else, simplement parce que c'est beaucoup plus propre. Je sais que vous ne pouvez activer que les primitives et les chaînes, donc je change le nom de la classe:

switch(o.getClass().getCanonicalName()){
case "my.package.A":
  handleA((A)o);
  break;
case "my.package.B":
  handleB((B)o);
  break;
case "my.package.C":
  handleC((C)o);
  break;
default:
  handleUnknown(o);
  break;
}

Le hic ici est que les noms canoniques sont TRÈS longs (comme 12 sous-paquets), et je ne peux pas appeler ClassName.class.getCanonicalName() dans l'instruction case car Java ne le permet pas. Donc, ma prochaine solution était une énumération. C'est là que j'ai touché mon problème.

J'aimerais que mon code ressemble à ceci:

public enum Classes {
  A (A.getCanonicalName()),
  B (B.getCanonicalName()),
  C (C.getCanonicalName());
}

switch (o.getClass().getCanonicalName()){
case Classes.A:
  handleA((A)o);
  break;
case Classes.B:
  handleB((B)o);
  break;
case Classes.C:
  handleC((C)o);
  break;
default:
  handleUnknown(o);
  break;
}

Mais cela ne compile pas. Je ne sais pas pourquoi. J'aimerais une approche qui me permette de basculer sur le type sans avoir à taper le nom canonique entier. Si je fais cela, je pourrais aussi bien utiliser if/else et instanceof.

REMARQUE Il y a quelques types qui ont le même nom (classes internes), donc getSimpleName() est sorti.

Author: Community, 2015-04-11

7 answers

Voici une approche qui ne traite pas du tout des noms de classe, et expédie aussi vite qu'une instruction switch: faire une carte de hachage pour mapper des objets Class<T> à des gestionnaires spécifiques à la classe, et utiliser la carte au lieu d'un switch:

// Declare an interface for your polymorphic handlers to implement.
// There will be only anonymous implementations of this interface.
private interface Handler {
    void handle(Object o);
}
// Make a map that translates a Class object to a Handler
private static final Map<Class,Handler> dispatch = new HashMap<Class,Handler>();
// Populate the map in a static initializer
static {
    dispatch.put(A.class, new Handler() {
        public void handle(Object o) {
            handleA((A)o);
        }
    });
    dispatch.put(B.class, new Handler() {
        public void handle(Object o) {
            handleB((B)o);
        }
    });
    dispatch.put(C.class, new Handler() {
        public void handle(Object o) {
            handleC((C)o);
        }
    });
}
// This object performs the dispatch by looking up a handler,
// and calling it if it's available
private static void handle(Object o) {
    Handler h = dispatch.get(o.getClass());
    if (h == null) {
        // Throw an exception: unknown type
    }
    h.handle(o); // <<== Here is the magic
}
 26
Author: dasblinkenlight, 2015-04-10 21:26:03

En utilisant java 8 lambdas, vous pouvez obtenir quelque chose comme ceci:

Collection col = Arrays.asList(1,2,3);
switchType(col, 
       caze(Collection.class, c->System.out.println(c.size())),
       caze(ArrayBlockingQueue.class, bq->System.out.println(bq.remainingCapacity())),
       caze(Queue.class, q->System.out.println(q.poll())),
       caze(String.class, s->System.out.println(s.substring(0))),
       caze(ArrayList.class, al->System.out.println(al.get(0)))
);

Pour ce faire, vous devez définir les méthodes statiques suivantes:

public static <T> void switchType(Object o, Consumer... a) {
    for (Consumer consumer : a)
        consumer.accept(o);
}

public static <T> Consumer caze(Class<T> cls, Consumer<T> c) {
    return obj -> Optional.of(obj).filter(cls::isInstance).map(cls::cast).ifPresent(c);
}    
 15
Author: eitan, 2016-05-19 21:18:10

L'opérateur instanceof est une approche simple, lorsque vous ne possédez pas les classes. Un instanceof expression est vraie lorsque l'objet est la classe , ou une sous-classe.

Vous mentionnez que vous ne possédez pas les classes. Le propriétaire pourrait introduire des sous-classes dans une version ultérieure. Disons que le propriétaire introduit APlus en tant que sous-classe de A. Une instance d'APlus est un A. Le code qui fonctionne sur un A devrait également fonctionner sur un APlus. Si vous utilisez instanceof, votre code continuerait à fonctionner without sans effort de vous. Si vous utilisez des noms de classe, cela échouerait without sans préavis de votre compilateur.

Si vous allumez à plusieurs reprises le même objet, vous pourriez trouver utile d'envelopper l'objet une fois dans une classe wrapper qui implémente une interface. Par la suite, vous pouvez simplement appeler des méthodes sur l'interface with sans if, switch, ou carte.

public interface IWrapper {
    public void handle();
    public String describe();
}

public AWrapper implements IWrapper { ... }
public BWrapper implements IWrapper { ... }
public CWrapper implements IWrapper { ... }
public UnknownWrapper implements IWrapper { ... }

IWrapper wrap( Object o ) {
    if ( o instanceof A ) return new AWrapper((A) o);
    else if ( o instanceof B ) return new BWrapper((B) o);
    else if ( o instanceof C ) return new CWrapper((C) o);
    else return new UnknownWrapper(o);
}

Même en l'absence garantie de sous-classes, évitez de spécifier des noms de classe en tant que chaînes littérales dans les cas switch. Cela permet des erreurs que le compilateur ne trouvera pas, ce qui peut vous coûter du temps de débogage.

 2
Author: Andy Thomas, 2015-04-10 23:21:53

Vous étiez très proche de la solution avec enums. Il n'a pas compilé car votre enum a manqué le constructeur et la méthode coversion pour mapper enum à partir de String. En fait, vous pouvez le résoudre même sans chaîne, c'est-à-dire sans appeler getCanonicalName du tout:

public enum Classes {
  // changed enum constants a bit to avoid confusing with target class names
  ClsA (A.class),
  ClsB (B.class),
  ClsC (C.class),
  UNKNOWN(null);
  private final Class<?> targetClass;
  Classes(Class<?> targetClass) {
    this.targetClass = targetClass;
  }
  public static Classes fromClass(Class<?> cls) {
    for(Classes c : values()) {
      if(c.targetClass == cls)
         return c;
    }
    return UNKNOWN;
  }
}

switch (Classes.fromClass(o.getClass())) {
case ClsA:
  handleA((A)o);
  break;
case ClsB:
  handleB((B)o);
  break;
case ClsC:
  handleC((C)o);
  break;
default:
  handleUnknown(o);
  break;
}

Si vous obtenez un nombre significatif de classes connues, envisagez d'utiliser map au lieu d'itérer dans les classes.fromClass, par exemple:

public enum Classes {
  ClsA(A.class),
  ClsB(B.class),
  // etc...
  UNKNWON(null);

  // need a wrapper class to avoid compilation problem
  // with referring static enum field within an initializer 
  private static class Holder {
    public static final IdentityHashMap<Class<?>, Classes> map = new IdentityHashMap<>();
  }
  Classes(Class<?> targetClass) {
    Holder.map.put(targetClass, this);
  }
  public static Classes fromClass(Class<?> cls) {
    Classes c = Holder.map.get(cls);
    return c != null ? c : UNKNOWN;
  }
}
 1
Author: Ramiz, 2016-11-22 11:01:29

J'ai pu travailler avec java.lang.refléter

import java.lang.reflect.Method;

public class MyClass {

    public void validate(Object o) {    
        String className = o.getClass().getSimpleName();     
        try {
            //this line searches a method named as className
            Method m = this.getClass().getDeclaredMethod(className);
            //this line execute the method 
             m.invoke(this);
        } catch (Exception e) {
            e.printStackTrace();
            handleUnknown();
        }

    }

    //this methot will execute if the object o is instance of A
    public void A() {

    }
    //this methot will execute if the object o is instance of B
     public void B() {

    }
    //this methot will execute if the object o is instance of C
     public void C() {

    }
    //this methot will execute if the method is unknown
    public void handleUnknown(){

    }


}
 0
Author: Edgar, 2017-07-12 22:13:54

Pour basculer sur les types de classe connus, vous pouvez utiliser l'approche ci-dessous

Créer un Enum, avec les noms de Classe.

public enum ClassNameEnum {
    ClassA, ClassB, ClassC
}

Recherchez le Nom de classe de l'objet. Écrivez un switch case sur l'énumération.

private void switchByClassType(Object obj) {

        ClassNameEnum className = ClassNameEnum.valueOf(obj.getClass().getSimpleName());

        switch (className) {
            case ClassA:
                doA();
                break;
            case ClassB:
                doB();
                break;
            case ClassC:
                doC();
                break;
        }
    }
}
 0
Author: Siva Kumar, 2018-06-27 05:29:12

Voici un exemple de ceci qui utilise un objet simple pour chaque cas.

package mcve.util;

import java.util.*;
import java.util.function.*;

/**
 * Allows switch-like statements with classes and consumers.
 */
public final class ClassSwitch implements Consumer<Object> {
    /**
     * For each of the specified cases, in order of their
     * appearance in the array, if cases[i].test(obj) returns
     * true, then invoke cases[i].accept(obj) and return.
     *
     * @param  obj   the object to switch upon
     * @param  cases the cases for the switch
     * @throws NullPointerException
     *         if any of the cases are null
     */
    public static void cswitch(Object obj, Case<?>... cases) {
        if (cases != null) {
            for (Case<?> c : cases) {
                if (c.test(obj)) {
                    c.accept(obj);
                    break;
                }
            }
        }
    }

    /**
     * @param  type   the type of the case
     * @param  action the action to perform
     * @param  <T>    the type of the case
     * @throws NullPointerException
     *         if the type or action is null
     * @return a new Case
     */
    public static <T> Case<T> ccase(Class<T> type, Consumer<? super T> action) {
        return new Case<>(type, action);
    }

    /**
     * @param <T> the type of the case
     */
    public static final class Case<T> implements Predicate<Object>,
                                                 Consumer<Object> {
        private final Class<T> type;
        private final Consumer<? super T> action;

        /**
         * @param  type   the type of the case
         * @param  action the action to perform
         * @throws NullPointerException
         *         if the type or action is null
         */
        public Case(Class<T> type, Consumer<? super T> action) {
            this.type   = Objects.requireNonNull(type,   "type");
            this.action = Objects.requireNonNull(action, "action");
        }

        /**
         * @param  obj the object to test
         * @return true if the object is an instance of T, else false
         */
        @Override
        public boolean test(Object obj) {
            return type.isInstance(obj);
        }

        /**
         * @param  obj the object to perform the action on
         * @throws ClassCastException
         *         if the object is not an instance of T
         */
        @Override
        public void accept(Object obj) {
            action.accept(type.cast(obj));
        }
    }

    /**
     * An unmodifiable list of the cases in this switch.
     */
    private final List<Case<?>> cases;

    /**
     * @param  cases the cases for this switch
     * @throws NullPointerException
     *         if any of the cases are null
     */
    public ClassSwitch(Case<?>... cases) {
        if (cases == null) {
            this.cases = Collections.emptyList();
        } else {
            List<Case<?>> list = new ArrayList<>(cases.length);
            for (Case<?> c : cases) {
                list.add(Objects.requireNonNull(c, "case"));
            }
            this.cases = Collections.unmodifiableList(list);
        }
    }

    /**
     * @return an unmodifiable view of the cases in this switch
     */
    public List<Case<?>> getCases() { return cases; }

    /**
     * For each of the cases in this switch, in order of their
     * appearance in the list, if cases.get(i).test(obj) returns
     * true, then invoke cases.get(i).accept(obj) and return.
     *
     * @param obj the object to switch upon
     */
    @Override
    public void accept(Object obj) {
        for (Case<?> c : cases) {
            if (c.test(obj)) {
                c.accept(obj);
                break;
            }
        }
    }
}

Un exemple d'utilisation serait quelque chose comme ceci, en supposant des importations de par exemple import static mcve.util.ClassSwitch.*;:

cswitch(anObject,
    ccase(Byte.class,    b -> System.out.println("Byte")),
    ccase(Short.class,   s -> System.out.println("Short")),
    ccase(Integer.class, i -> System.out.println("Integer")),
    ccase(Long.class,    l -> System.out.println("Long")),
    ccase(Float.class,   f -> System.out.println("Float")),
    ccase(Double.class,  d -> System.out.println("Double"))
);

Vous pouvez également créer un objet réutilisable:

ClassSwitch ts =
    new ClassSwitch(ccase(String.class, System.out::println),
                    ccase(Double.class, System.out::println));
ts.accept(anObject);

Notes:

  • Si vous voulez un default cas, vous pouvez utiliser Object.class, comme le dernier cas.

  • Il n'y a aucun moyen de faire un cas qui gère null, mais il pourrait être modifié un peu pour cela. Vous pourriez par exemple faire un class NullCase dont la méthode test renvoie obj == null.


Ce que vous pouvez également faire est de générer des surcharges au lieu d'utiliser varargs. Cela vous permet d'associer des classes avec des consommateurs en utilisant uniquement des déclarations de méthode génériques. Ce qui suit est un exemple assez simple de ceci:

package mcve.util;

import java.util.*;
import java.util.function.*;

/**
 * Allows switch-like statements with classes and consumers.
 */
public final class GeneratedClassSwitch {
    private GeneratedClassSwitch() {}

    /**
     * Generates overloads for a class switch to System.out.
     *
     * For example, if max=4, then 5 methods are generated:
     * with 0, 1, 2, 3, and 4 cases.
     *
     * @param  max
     *         the number of cases in the largest overload generated
     * @param  indents
     *         the number of indents to indent each generated method
     * @throws IllegalArgumentException
     *         if max is negative or greater than 26, or if indents
     *         is negative
     */
    public static void generateFixedOverloads(int max, int indents) {
        if (max < 0 || max > 26) {
            throw new IllegalArgumentException("max=" + max);
        }
        String indent = String.join("", Collections.nCopies(indents, "    "));

        for (int i = 0; i <= max; ++i) {
            System.out.print(indent);
            System.out.print("public static ");

            if (i > 0) {
                System.out.print("<");

                for (char ch = 'A'; ch < 'A' + i; ++ch) {
                    if (ch != 'A') {
                        System.out.print(", ");
                    }
                    System.out.print(ch);
                }

                System.out.print("> ");
            }

            System.out.print("void cswitch");

            if (i > 0) {
                System.out.println();
                System.out.print(indent + "       (Object o, ");

                for (char ch = 'A'; ch < 'A' + i; ++ch) {
                    if (ch != 'A') {
                        System.out.println(",");
                        System.out.print(indent + "                  ");
                    }
                    System.out.print("Class<" + ch + "> class" + ch);
                    System.out.print(", Consumer<? super " + ch + "> action" + ch);
                }
            } else {
                System.out.print("(Object o");
            }

            System.out.println(") {");

            for (char ch = 'A'; ch < 'A' + i; ++ch) {
                if (ch == 'A') {
                    System.out.print(indent + "    ");
                } else {
                    System.out.print(" else ");
                }
                System.out.println("if (class" + ch + ".isInstance(o)) {");
                System.out.print(indent + "        ");
                System.out.println("action" + ch + ".accept(class" + ch + ".cast(o));");
                System.out.print(indent + "    ");
                System.out.print("}");
                if (ch == ('A' + i - 1)) {
                    System.out.println();
                }
            }

            System.out.print(indent);
            System.out.println("}");
        }
    }

    // Generated code pasted below.

    public static void cswitch(Object o) {
    }
    public static <A> void cswitch
           (Object o, Class<A> classA, Consumer<? super A> actionA) {
        if (classA.isInstance(o)) {
            actionA.accept(classA.cast(o));
        }
    }
    public static <A, B> void cswitch
           (Object o, Class<A> classA, Consumer<? super A> actionA,
                      Class<B> classB, Consumer<? super B> actionB) {
        if (classA.isInstance(o)) {
            actionA.accept(classA.cast(o));
        } else if (classB.isInstance(o)) {
            actionB.accept(classB.cast(o));
        }
    }
    public static <A, B, C> void cswitch
           (Object o, Class<A> classA, Consumer<? super A> actionA,
                      Class<B> classB, Consumer<? super B> actionB,
                      Class<C> classC, Consumer<? super C> actionC) {
        if (classA.isInstance(o)) {
            actionA.accept(classA.cast(o));
        } else if (classB.isInstance(o)) {
            actionB.accept(classB.cast(o));
        } else if (classC.isInstance(o)) {
            actionC.accept(classC.cast(o));
        }
    }
    public static <A, B, C, D> void cswitch
           (Object o, Class<A> classA, Consumer<? super A> actionA,
                      Class<B> classB, Consumer<? super B> actionB,
                      Class<C> classC, Consumer<? super C> actionC,
                      Class<D> classD, Consumer<? super D> actionD) {
        if (classA.isInstance(o)) {
            actionA.accept(classA.cast(o));
        } else if (classB.isInstance(o)) {
            actionB.accept(classB.cast(o));
        } else if (classC.isInstance(o)) {
            actionC.accept(classC.cast(o));
        } else if (classD.isInstance(o)) {
            actionD.accept(classD.cast(o));
        }
    }
    public static <A, B, C, D, E> void cswitch
           (Object o, Class<A> classA, Consumer<? super A> actionA,
                      Class<B> classB, Consumer<? super B> actionB,
                      Class<C> classC, Consumer<? super C> actionC,
                      Class<D> classD, Consumer<? super D> actionD,
                      Class<E> classE, Consumer<? super E> actionE) {
        if (classA.isInstance(o)) {
            actionA.accept(classA.cast(o));
        } else if (classB.isInstance(o)) {
            actionB.accept(classB.cast(o));
        } else if (classC.isInstance(o)) {
            actionC.accept(classC.cast(o));
        } else if (classD.isInstance(o)) {
            actionD.accept(classD.cast(o));
        } else if (classE.isInstance(o)) {
            actionE.accept(classE.cast(o));
        }
    }
    public static <A, B, C, D, E, F> void cswitch
           (Object o, Class<A> classA, Consumer<? super A> actionA,
                      Class<B> classB, Consumer<? super B> actionB,
                      Class<C> classC, Consumer<? super C> actionC,
                      Class<D> classD, Consumer<? super D> actionD,
                      Class<E> classE, Consumer<? super E> actionE,
                      Class<F> classF, Consumer<? super F> actionF) {
        if (classA.isInstance(o)) {
            actionA.accept(classA.cast(o));
        } else if (classB.isInstance(o)) {
            actionB.accept(classB.cast(o));
        } else if (classC.isInstance(o)) {
            actionC.accept(classC.cast(o));
        } else if (classD.isInstance(o)) {
            actionD.accept(classD.cast(o));
        } else if (classE.isInstance(o)) {
            actionE.accept(classE.cast(o));
        } else if (classF.isInstance(o)) {
            actionF.accept(classF.cast(o));
        }
    }
    public static <A, B, C, D, E, F, G> void cswitch
           (Object o, Class<A> classA, Consumer<? super A> actionA,
                      Class<B> classB, Consumer<? super B> actionB,
                      Class<C> classC, Consumer<? super C> actionC,
                      Class<D> classD, Consumer<? super D> actionD,
                      Class<E> classE, Consumer<? super E> actionE,
                      Class<F> classF, Consumer<? super F> actionF,
                      Class<G> classG, Consumer<? super G> actionG) {
        if (classA.isInstance(o)) {
            actionA.accept(classA.cast(o));
        } else if (classB.isInstance(o)) {
            actionB.accept(classB.cast(o));
        } else if (classC.isInstance(o)) {
            actionC.accept(classC.cast(o));
        } else if (classD.isInstance(o)) {
            actionD.accept(classD.cast(o));
        } else if (classE.isInstance(o)) {
            actionE.accept(classE.cast(o));
        } else if (classF.isInstance(o)) {
            actionF.accept(classF.cast(o));
        } else if (classG.isInstance(o)) {
            actionG.accept(classG.cast(o));
        }
    }
    public static <A, B, C, D, E, F, G, H> void cswitch
           (Object o, Class<A> classA, Consumer<? super A> actionA,
                      Class<B> classB, Consumer<? super B> actionB,
                      Class<C> classC, Consumer<? super C> actionC,
                      Class<D> classD, Consumer<? super D> actionD,
                      Class<E> classE, Consumer<? super E> actionE,
                      Class<F> classF, Consumer<? super F> actionF,
                      Class<G> classG, Consumer<? super G> actionG,
                      Class<H> classH, Consumer<? super H> actionH) {
        if (classA.isInstance(o)) {
            actionA.accept(classA.cast(o));
        } else if (classB.isInstance(o)) {
            actionB.accept(classB.cast(o));
        } else if (classC.isInstance(o)) {
            actionC.accept(classC.cast(o));
        } else if (classD.isInstance(o)) {
            actionD.accept(classD.cast(o));
        } else if (classE.isInstance(o)) {
            actionE.accept(classE.cast(o));
        } else if (classF.isInstance(o)) {
            actionF.accept(classF.cast(o));
        } else if (classG.isInstance(o)) {
            actionG.accept(classG.cast(o));
        } else if (classH.isInstance(o)) {
            actionH.accept(classH.cast(o));
        }
    }
}

Si vous souhaitez générer des surcharges, par exemple pour avoir plus de 8 cas, vous pouvez dire quelque chose comme ceci:

GeneratedClassSwitch.generateFixedOverloads(16, 1);

Qui générera des méthodes à System.out qui suivent la forme générale de:

public static <A, B, C> void cswitch
       (Object o, Class<A> classA, Consumer<? super A> actionA,
                  Class<B> classB, Consumer<? super B> actionB,
                  Class<C> classC, Consumer<? super C> actionC) {
    if (classA.isInstance(o)) {
        actionA.accept(classA.cast(o));
    } else if (classB.isInstance(o)) {
        actionB.accept(classB.cast(o));
    } else if (classC.isInstance(o)) {
        actionC.accept(classC.cast(o));
    }
}

Notez que nous sommes en mesure de mapper chaque type de classe à son type de consommateur associé, c'est-à-dire Class<A> avec Consumer<? super A>, Class<B> avec Consumer<? super B>, et ainsi de suite. C'est en fait impossible avec varargs (à partir de la version actuelle de Java, de toute façon, qui est 10).

Notre exemple d'utilisation est maintenant comme ceci, en supposant à nouveau des importations de par exemple import static mcve.util.GeneratedClassSwitch.*;:

cswitch(anObject,
    Byte.class,    b -> System.out.println("Byte"),
    Short.class,   s -> System.out.println("Short"),
    Integer.class, i -> System.out.println("Integer"),
    Long.class,    l -> System.out.println("Long"),
    Float.class,   f -> System.out.println("Float"),
    Double.class,  d -> System.out.println("Double")
);

(Les notes sur les cas default et null sont les mêmes que le premier exemple.)

 0
Author: Radiodef, 2018-07-14 22:40:50