Question

J'ai un grand nombre d'énumérations qui implémentent cette interface :

/**
 * Interface for an enumeration, each element of which can be uniquely identified by it's code
 */
public interface CodableEnum {

    /**
     * Get the element with a particular code
     * @param code
     * @return
     */
    public CodableEnum getByCode(String code);

    /**
     * Get the code that identifies an element of the enum
     * @return
     */
    public String getCode();
}

Un exemple typique est :

public enum IMType implements CodableEnum {

    MSN_MESSENGER("msn_messenger"),
    GOOGLE_TALK("google_talk"),
    SKYPE("skype"),
    YAHOO_MESSENGER("yahoo_messenger");

    private final String code;

    IMType (String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }   

    public IMType getByCode(String code) {
        for (IMType e : IMType.values()) {
            if (e.getCode().equalsIgnoreCase(code)) {
                return e;
            }
        }
    }
}

Comme vous pouvez l'imaginer, ces méthodes sont pratiquement identiques dans toutes les implémentations de CodableEnum.J'aimerais éliminer cette duplication, mais franchement, je ne sais pas comment.J'ai essayé d'utiliser une classe telle que la suivante :

public abstract class DefaultCodableEnum implements CodableEnum {

    private final String code;

    DefaultCodableEnum(String code) {
        this.code = code;
    }

    public String getCode() {
        return this.code;
    }   

    public abstract CodableEnum getByCode(String code);  
}

Mais cela s'avère assez inutile car :

  1. Une énumération ne peut pas étendre une classe
  2. Les éléments d'une énumération (SKYPE, GOOGLE_TALK, etc.) ne peuvent pas étendre une classe
  3. Je ne peux pas fournir une implémentation par défaut de getByCode(), car DefaultCodableEnum n'est pas lui-même un Enum.J'ai essayé de changer DefaultCodableEnum pour étendre java.lang.Enum, mais cela ne semble pas être autorisé.

Des suggestions qui ne reposent pas sur la réflexion ?Merci, Don

Était-ce utile?

La solution

Vous pouvez factoriser le code dupliqué dans une CodeableEnumHelper classe:

public class CodeableEnumHelper {
    public static CodeableEnum getByCode(String code, CodeableEnum[] values) {
        for (CodeableEnum e : values) {
            if (e.getCode().equalsIgnoreCase(code)) {
                return e;
            }
        }
        return null;
    }
}

Chaque classe CodeableEnum devra toujours implémenter une méthode getByCode, mais sa mise en oeuvre réelle a au moins été centralisée à un seul endroit.

public enum IMType implements CodeableEnum {
    ...
    public IMType getByCode(String code) {
        return (IMType)CodeableEnumHelper.getByCode(code, this.values());
    } 
}

Autres conseils

Les énumérations abstraites sont potentiellement très utiles (et actuellement interdites). Mais une proposition et un prototype existent si vous souhaitez faire pression sur quelqu'un de Sun pour l'ajouter:

http://freddy33.blogspot.com/ 2007/11 / abstract-enum-ricky-carlson-way.html

Sun RFE:

http://bugs.sun.com/bugdatabase/view_bug.do? bug_id = 6570766

Pour ranger le code de Dave:

public class CodeableEnumHelper {
    public static <E extends CodeableEnum> E getByCode(
        String code, E[] values
    ) {
        for (E e : values) {
            if (e.getCode().equalsIgnoreCase(code)) {
                return e;
            }
        }
        return null;
    }
}

public enum IMType implements CodableEnum {
    ...
    public IMType getByCode(String code) {
        return CodeableEnumHelper.getByCode(code, values());
    } 
}

Ou plus efficacement:

public class CodeableEnumHelper {
    public static <E extends CodeableEnum> Map<String,E> mapByCode(
        E[] values
    ) {
        Map<String,E> map = new HashMap<String,E>();
        for (E e : values) {
            map.put(e.getCode().toLowerCase(Locale.ROOT), value) {
        }
        return map;
    }
}

public enum IMType implements CodableEnum {
    ...
    private static final Map<String,IMType> byCode =
        CodeableEnumHelper.mapByCode(values());
    public IMType getByCode(String code) {
        return byCode.get(code.toLowerCase(Locale.ROOT));
    } 
}

J'ai rencontré un problème similaire avec un composant de localisation que j'ai écrit. Mon composant est conçu pour accéder aux messages localisés avec des constantes enum d'indexation dans un ensemble de ressources, pas un problème difficile.

J'ai constaté que je copiais et collais le même " template " Enum code partout. Ma solution pour éviter la duplication est un générateur de code qui accepte un fichier de configuration XML avec les noms de constante enum et les arguments de constructeur. La sortie est le code source Java avec le & Quot; dupliqué & Quot; comportements.

Maintenant, je gère les fichiers de configuration et le générateur, pas tout le code dupliqué. Partout où j'aurais eu le code source enum, il existe maintenant un fichier de configuration XML. Mes scripts de génération détectent les fichiers générés périmés et invoquent le générateur de code pour créer le code enum.

Vous pouvez voir ce composant ici . Le modèle que je copiais et collais est factorisé dans une feuille de style XSLT . Le code générateur exécute la transformation de la feuille de style. entrée Le fichier est assez concis par rapport au code source généré.

HTH,
Greg

Malheureusement, je ne pense pas qu’il existe un moyen de le faire. Votre meilleur choix serait probablement d'abandonner complètement les emums et d'utiliser des membres de l'extension de classe conventionnelle et des membres statiques. Sinon, habituez-vous à dupliquer ce code. Désolé.

Créez une classe d'utilitaire sécurisée par le type qui chargera les énumérations par code:

L'interface se réduit à:

public interface CodeableEnum {
    String getCode();
}

La classe d'utilitaire est la suivante:

import java.lang.reflect.InvocationTargetException;


public class CodeableEnumUtils {
    @SuppressWarnings("unchecked")
    public static <T extends CodeableEnum>  T getByCode(String code, Class<T> enumClass) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        T[] allValues = (T[]) enumClass.getMethod("values", new Class[0]).invoke(null, new Object[0]);
        for (T value : allValues) {
            if (value.getCode().equals(code)) {
                return value;
            }
        }
        return null;
}

}

Un scénario de test démontrant l'utilisation:

import junit.framework.TestCase;


public class CodeableEnumUtilsTest extends TestCase {
    public void testWorks() throws Exception {
    assertEquals(A.ONE, CodeableEnumUtils.getByCode("one", A.class));
      assertEquals(null, CodeableEnumUtils.getByCode("blah", A.class));
    }

enum A implements CodeableEnum {
    ONE("one"), TWO("two"), THREE("three");

    private String code;

    private A(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }   
}
}

Maintenant, vous ne faites que dupliquer la méthode getCode () et la méthode getByCode () se trouve au même endroit. Il peut également être intéressant d’envelopper toutes les exceptions dans une seule exception RuntimeException:)

Ici, j'ai une autre solution:

interface EnumTypeIF {
String getValue();

EnumTypeIF fromValue(final String theValue);

EnumTypeIF[] getValues();

class FromValue {
  private FromValue() {
  }

  public static EnumTypeIF valueOf(final String theValue, EnumTypeIF theEnumClass) {

    for (EnumTypeIF c : theEnumClass.getValues()) {
      if (c.getValue().equals(theValue)) {
        return c;
      }
    }
    throw new IllegalArgumentException(theValue);
  }
}

Le truc, c'est que la classe interne peut être utilisée pour contenir & "méthodes globales &";

.

a très bien fonctionné pour moi. OK, vous devez implémenter 3 méthodes, mais ces méthodes, ne sont que des délégués.

Il semble que vous implémentez réellement des informations de type à l'exécution. Java fournit cela comme une fonctionnalité de langage.

Je vous suggère de rechercher RTTI ou une réflexion.

Je ne pense pas que ce soit possible. Cependant, vous pouvez utiliser la méthode valueOf (nom de chaîne) de l'énum si vous voulez utiliser le nom de la valeur enum comme code.

Que diriez-vous d’une méthode générique statique ?Vous pouvez le réutiliser à partir des méthodes getByCode() de votre énumération ou simplement l'utiliser directement.J'utilise toujours des identifiants entiers pour mes énumérations, donc ma méthode getById() n'a qu'à faire ceci :valeurs de retour()[id].C'est beaucoup plus rapide et plus simple.

Si vous voulez vraiment l'héritage, n'oubliez pas que vous pouvez implémenter le modèle d'énumération. vous-même , comme dans les mauvais vieux jours de Java 1.4.

À peu près aussi près que possible de ce que vous souhaitiez, c'était de créer un modèle dans IntelliJ qui "implémenterait" le code générique (en utilisant la valeur enO de valueOf (nom de chaîne)). Pas parfait mais fonctionne assez bien.

Dans votre cas, les méthodes getCode () / getByCode (String code) semblent très proches (euphémiquement parlant) du comportement des méthodes toString () / valueOf (String value) fournies par toutes les énumérations. Pourquoi ne veux-tu pas les utiliser?

Une autre solution serait de ne rien mettre dans l’énum lui-même, mais de fournir une carte bidirectionnelle Enum < - > Code pour chaque énumération. Vous pourriez par exemple utilisez ImmutableBiMap de Google Collections pour cela.

De cette façon, il n'y a pas de code en double du tout.

Exemple:

public enum MYENUM{
  VAL1,VAL2,VAL3;
}

/** Map MYENUM to its ID */
public static final ImmutableBiMap<MYENUM, Integer> MYENUM_TO_ID = 
new ImmutableBiMap.Builder<MYENUM, Integer>().
put(MYENUM.VAL1, 1).
put(MYENUM.VAL2, 2).
put(MYENUM.VAL3, 3).
build();

À mon avis, ce serait le moyen le plus simple, sans réflexion ni ajout de wrapper supplémentaire à votre enum.

Vous créez une interface que votre enum implémente:

public interface EnumWithId {

    public int getId();

}

Ensuite, dans une classe d'assistance, vous créez simplement une méthode comme celle-ci:

public <T extends EnumWithId> T getById(Class<T> enumClass, int id) {
    T[] values = enumClass.getEnumConstants();
    if (values != null) {
        for (T enumConst : values) {
            if (enumConst.getId() == id) {
                return enumConst;
            }
        }
    }

    return null;
}

Cette méthode pourrait ensuite être utilisée comme ceci:

MyUtil.getInstance().getById(MyEnum.class, myEnumId);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top