web-dev-qa-db-fra.com

Une constante de chaîne doit-elle être définie si elle ne doit être utilisée qu'une seule fois?

Nous implémentons un adaptateur pour Jaxen (une bibliothèque XPath pour Java) qui nous permet d'utiliser XPath pour accéder au modèle de données de notre application.

Cela se fait en implémentant des classes qui mappent des chaînes (transmises depuis Jaxen) en éléments de notre modèle de données. Nous estimons que nous aurons besoin d'environ 100 classes avec plus de 1000 comparaisons de chaînes au total.

Je pense que la meilleure façon de le faire est de simples instructions if/else avec les chaînes écrites directement dans le code - plutôt que de définir chaque chaîne comme une constante. Par exemple:

public Object getNode(String name) {
    if ("name".equals(name)) {
        return contact.getFullName();
    } else if ("title".equals(name)) {
        return contact.getTitle();
    } else if ("first_name".equals(name)) {
        return contact.getFirstName();
    } else if ("last_name".equals(name)) {
        return contact.getLastName();
    ...

Cependant, on m'a toujours enseigné que nous ne devons pas incorporer des valeurs de chaîne directement dans le code, mais plutôt créer des constantes de chaîne. Cela ressemblerait à quelque chose comme ceci:

private static final String NAME = "name";
private static final String TITLE = "title";
private static final String FIRST_NAME = "first_name";
private static final String LAST_NAME = "last_name";

public Object getNode(String name) {
    if (NAME.equals(name)) {
        return contact.getFullName();
    } else if (TITLE.equals(name)) {
        return contact.getTitle();
    } else if (FIRST_NAME.equals(name)) {
        return contact.getFirstName();
    } else if (LAST_NAME.equals(name)) {
        return contact.getLastName();
    ...

Dans ce cas, je pense que c'est une mauvaise idée. La constante ne sera utilisée qu'une seule fois, dans la méthode getNode(). L'utilisation directe des chaînes est tout aussi facile à lire et à comprendre que l'utilisation des constantes et nous évite d'écrire au moins mille lignes de code.

Y a-t-il donc une raison de définir des constantes de chaîne pour un usage unique? Ou est-il acceptable d'utiliser directement des chaînes?


PS. Avant que quiconque ne suggère d'utiliser des énumérations à la place, nous avons fait un prototype, mais la conversion d'énumérations est 15 fois plus lente que la simple comparaison de chaînes, donc ce n'est pas envisagé.


Conclusion: Les réponses ci-dessous ont élargi la portée de cette question au-delà des constantes de chaîne, j'ai donc deux conclusions:

  • Il est probablement correct d'utiliser les chaînes directement plutôt que les constantes de chaîne dans ce scénario, mais
  • Il existe des moyens d'éviter d'utiliser des chaînes, ce qui pourrait être mieux.

Je vais donc essayer la technique du wrapper qui évite complètement les cordes. Malheureusement, nous ne pouvons pas utiliser l'instruction string switch car nous ne sommes pas encore sur Java 7. En fin de compte, cependant, je pense que la meilleure réponse pour nous est d'essayer chaque technique et d'évaluer ses performances. La réalité est que si une technique est clairement plus rapide, nous la choisirons probablement quelle que soit sa beauté ou son respect des conventions.

26
gutch

Essaye ça. La réflexion initiale est certainement coûteuse, mais si vous allez l'utiliser plusieurs fois, ce que je pense que vous le ferez, c'est certainement une meilleure solution que ce que vous proposez. Je n'aime pas utiliser la réflexion, mais je me retrouve à l'utiliser quand je n'aime pas l'alternative à la réflexion. Je pense que cela sauvera beaucoup de maux de tête à votre équipe, mais vous devez passer le nom de la méthode (en minuscules).

En d'autres termes, plutôt que de passer "nom", vous passeriez "nom complet" car le nom de la méthode get est "getFullName ()".

Map<String, Method> methodMapping = null;

public Object getNode(String name) {
    Map<String, Method> methods = getMethodMapping(contact.getClass());
    return methods.get(name).invoke(contact);
}

public Map<String, Method> getMethodMapping(Class<?> contact) {
    if(methodMapping == null) {
        Map<String, Method> mapping = new HashMap<String, Method>();
        Method[] methods = contact.getDeclaredMethods();
        for(Method method : methods) {
            if(method.getParameterTypes().length() == 0) {
                if(method.getName().startsWith("get")) {
                    mapping.put(method.getName().substring(3).toLower(), method);
                } else if (method.getName().startsWith("is"))) {
                    mapping.put(method.getName().substring(2).toLower(), method);
                }
            }
        }
        methodMapping = mapping;
    }
    return methodMapping;
}

Si vous devez accéder aux données contenues dans les membres de contact, vous pouvez envisager de créer une classe wrapper pour contact qui dispose de toutes les méthodes pour accéder aux informations requises. Cela serait également utile pour garantir que les noms des champs d'accès resteront toujours les mêmes (c'est-à-dire si la classe wrapper a getFullName () et que vous appelez avec fullname, cela fonctionnera toujours même si getFullName () du contact a été renommé - il entraînerait une erreur de compilation avant de vous permettre de le faire).

public class ContactWrapper {
    private Contact contact;

    public ContactWrapper(Contact contact) {
        this.contact = contact;
    }

    public String getFullName() {
        return contact.getFullName();
    }
    ...
}

Cette solution m'a sauvé plusieurs fois, à savoir quand je voulais avoir une seule représentation de données à utiliser dans les tables de données jsf et quand ces données devaient être exportées dans un rapport à l'aide de jasper (qui ne gère pas bien les accesseurs d'objets compliqués dans mon expérience) .

5
Neil

Si possible, utilisez Java 7 qui vous permet d'utiliser des chaînes dans les instructions switch.

De http://docs.Oracle.com/javase/tutorial/Java/nutsandbolts/switch.html

public class StringSwitchDemo {

    public static int getMonthNumber(String month) {

        int monthNumber = 0;

        if (month == null) {
            return monthNumber;
        }

        switch (month.toLowerCase()) {
            case "january":
                monthNumber = 1;
                break;
            case "february":
                monthNumber = 2;
                break;
            case "march":
                monthNumber = 3;
                break;
            case "april":
                monthNumber = 4;
                break;
            case "may":
                monthNumber = 5;
                break;
            case "june":
                monthNumber = 6;
                break;
            case "july":
                monthNumber = 7;
                break;
            case "august":
                monthNumber = 8;
                break;
            case "september":
                monthNumber = 9;
                break;
            case "october":
                monthNumber = 10;
                break;
            case "november":
                monthNumber = 11;
                break;
            case "december":
                monthNumber = 12;
                break;
            default: 
                monthNumber = 0;
                break;
        }

        return monthNumber;
    }

    public static void main(String[] args) {

        String month = "August";

        int returnedMonthNumber =
            StringSwitchDemo.getMonthNumber(month);

        if (returnedMonthNumber == 0) {
            System.out.println("Invalid month");
        } else {
            System.out.println(returnedMonthNumber);
        }
    }
}

Je n'ai pas mesuré, mais je crois que les instructions switch se compilent dans une table de saut au lieu d'une longue liste de comparaisons. Cela devrait être encore plus rapide.

Concernant votre question réelle: Si vous ne l'utilisez qu'une seule fois , vous n'avez pas besoin d'en faire une constante. Considérez cependant qu'une constante peut être documentée et apparaît dans Javadoc. Cela peut être important pour les valeurs de chaîne non triviales.

6
user1249

Si vous voulez maintenir cela (apporter des modifications non triviales), je pourrais envisager d'utiliser une sorte de génération de code basée sur des annotations (peut-être via CGLib ) ou même juste un script qui écrit tout le code pour vous. Imaginez le nombre de fautes de frappe et d'erreurs qui pourraient s'introduire dans l'approche que vous envisagez ...

4
Steven Schlansker

J'utiliserais toujours des constantes définies en haut de vos classes. Cela rend votre code plus facile à gérer car il est plus facile de voir ce qui peut être modifié ultérieurement (si nécessaire). Par exemple, "first_name" pourrait devenir "firstName" à un moment ultérieur.

2
Bernard

Si votre dénomination est cohérente (aka "some_whatever" Est toujours mappée sur getSomeWhatever()), vous pouvez utiliser la réflexion pour déterminer et exécuter la méthode get.

1
scarfridge

Je suppose que le traitement des annotations pourrait être la solution, même sans annotations. C'est la chose qui peut générer tout le code ennuyeux pour vous. L'inconvénient est que vous obtiendrez N classes générées pour N classes de modèle. Vous ne pouvez pas non plus ajouter quoi que ce soit à une classe existante, mais écrire quelque chose comme

public Object getNode(String name) {
    return SomeModelClassHelper.getNode(this, name);
}

une fois par classe ne devrait pas être un problème. Alternativement, vous pouvez écrire quelque chose comme

public Object getNode(String name) {
    return getHelper(getClass()).getNode(this, name);
}

dans une superclasse commune.


Vous pouvez utiliser la réflexion au lieu du traitement des annotations pour la génération de code. L'inconvénient est que vous avez besoin de votre code pour compiler avant de pouvoir utiliser la réflexion dessus. Cela signifie que vous ne pouvez pas compter sur le code généré dans vos classes de modèle, sauf si vous générez des stubs.


J'envisagerais également l'utilisation directe de la réflexion. Bien sûr, la réflexion est lente, mais pourquoi est-elle lente? C'est parce qu'il doit faire tout ce que vous devez faire, par exemple, activer le nom du champ.

0
maaartinus