web-dev-qa-db-fra.com

Changer de type en java

Avant de commencer, je sais que de nombreuses réponses à cette question suggèrent des approches alternatives. Je cherche de l'aide pour cette approche particulière afin de savoir si c'est possible, sinon des approches similaires qui pourraient fonctionner.

J'ai une méthode qui prend une superclasse et appelle une méthode en fonction du type de l'objet transmis. 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 suggérerait, car je ne possède 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 net. Je sais que vous ne pouvez activer que les primitives et les chaînes, alors je change de nom de 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 problème ici est que les noms canoniques sont TRÈS longs (comme 12 sous-packages) et je ne peux pas appeler ClassName.class.getCanonicalName() dans l'instruction case car Java ne le permet pas. Donc ma solution suivante était un Enum. C'est là que j'ai frappé 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 changer de type sans avoir à taper le nom canonique complet. Si je le fais, je pourrais aussi bien utiliser if/else et instanceof.

NOTE Il y a deux types qui ont le même nom (classes internes), donc getSimpleName() est éteint.

22
ewok

Voici une approche qui ne traite pas du tout les noms de classe et distribue aussi rapidement qu'une instruction switch: crée une table de hachage pour mapper des objets Class<T> à des gestionnaires spécifiques à une classe et utiliser la carte au lieu de 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
}
28
dasblinkenlight

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

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

Vous mentionnez que vous n'êtes pas propriétaire des cours. Le propriétaire peut introduire des sous-classes dans une version ultérieure. Supposons que le propriétaire présente APlus en tant que sous-classe de A. Une instance de APlus est un A. Le code qui fonctionne sur un A devrait également fonctionner sur un APlus. Si vous utilisez instanceof, votre code continuera à fonctionner - sans effort de votre part. Si vous utilisez des noms de classe, cela échouera - sans préavis de votre compilateur.

Si vous basculez de manière répétée sur le même objet, vous trouverez peut-être utile d'envelopper l'objet une fois dans une classe wrapper qui implémente une interface. Ensuite, vous pouvez simplement appeler des méthodes sur l'interface, sans if, switch ni map.

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 les noms de classe sous forme de chaînes littérales dans switch cas. Cela permet des erreurs que le compilateur ne trouvera pas, ce qui peut vous coûter du temps de débogage.

2
Andy Thomas

Vous étiez très proche de la solution avec enums . Elle n’a pas été compilée, car votre enum a manqué le constructeur et la méthode coversion pour mapper enum de String. En fait, vous pouvez le résoudre même sans String, 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 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
Ramiz

J'ai pu travailler avec Java.lang.reflect

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
Edgar

Voici un exemple de cela 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 que les 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);

Remarques:

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

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


Ce que vous pouvez également faire, c'est générer des surcharges au lieu d'utiliser varargs. Cela vous permet d'associer des classes à des consommateurs en utilisant uniquement des déclarations de méthode génériques. Voici 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 si vous avez plus de 8 observations, vous pouvez dire quelque chose comme ce qui suit:

GeneratedClassSwitch.generateFixedOverloads(16, 1);

Cela générera des méthodes pour 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 sur son type de consommateur associé, c'est-à-dire .Class<A> avec Consumer<? super A>, Class<B> avec Consumer<? super B>, etc. C'est en fait impossible avec varargs (à partir de la version actuelle de Java, qui est 10).

Notre exemple d’utilisation est maintenant le même, en supposant que les 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 identiques à celles du premier exemple.)

0
Radiodef

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

Créez un Enum avec les noms de classe.

public enum ClassNameEnum {
    ClassA, ClassB, ClassC
}

Trouver le Nom de la classe de l'objet . Écrire un commutateur cas sur l'enum.

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
Siva Kumar