Должна ли быть определена строковая константа, если она будет использоваться только один раз?

24

Мы реализуем адаптер для Jaxen (библиотека XPath для Java), который позволяет нам использовать XPath для доступа к модели данных нашего приложения.

Это делается путем реализации классов, которые отображают строки (переданные нам из Jaxen) в элементы нашей модели данных. По нашим оценкам, нам потребуется около 100 классов с более чем 1000 сравнений строк.

Я думаю, что лучший способ сделать это - простые операторы if / else со строками, записанными непосредственно в код, а не определение каждой строки как константы. Например:

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();
    ...

Однако меня всегда учили, что мы не должны вставлять строковые значения непосредственно в код, а вместо этого создавать строковые константы. Это будет выглядеть примерно так:

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();
    ...

В этом случае я думаю, что это плохая идея. Константа будет использоваться только один раз, вgetNode() методе . Использование строк напрямую так же легко понять и понять, как и использование констант, и это экономит нам на написании как минимум тысячи строк кода.

Так есть ли причина определять строковые константы для одного использования? Или допустимо использовать строки напрямую?


PS. Прежде чем кто-либо предложит использовать перечисления вместо этого, мы прототипировали это, но преобразование перечисления в 15 раз медленнее, чем простое сравнение строк, поэтому оно не рассматривается.


Вывод: ответы ниже расширили сферу этого вопроса за пределы строковых констант, поэтому у меня есть два вывода:

  • Возможно, в этом сценарии можно использовать строки напрямую, а не строковые константы, но
  • Есть способы вообще избежать использования строк, которые могут быть лучше.

Итак, я собираюсь попробовать технику обертки, которая полностью избегает строк. К сожалению, мы не можем использовать оператор переключения строк, потому что мы еще не на Java 7. В конечном счете, я думаю, что лучший ответ для нас - попробовать каждую технику и оценить ее эффективность. Реальность такова, что если один метод явно быстрее, то мы, вероятно, выберем его независимо от его красоты или приверженности условностям.

gutch
источник
3
Вы не планируете вручную набирать 1000, если вы делаете заявления?
JeffO
1
Мне так грустно, как неприятно что-то такое простое в некоторых языках ...
Джон Пурди,
5
Java 7 допускает строки в качестве switchметок. Используйте переключатель вместо ifкаскадов.
Восстановите Монику - М. Шредер
3
преобразование enum будет в 15 раз медленнее, если вы преобразуете строку в ее значение enum! Передайте enum напрямую и сравните с другим значением enum того же типа!
Нил
2
Пахнет как HashMap может быть решением.
MarioDS

Ответы:

5

Попробуй это. Первоначальное размышление, безусловно, дорого, но если вы собираетесь использовать его много раз, что, я думаю, вам понадобится, это, безусловно, лучшее решение того, что вы предлагаете. Я не люблю использовать рефлексию, но я использую ее, когда мне не нравится альтернатива рефлексии. Я думаю, что это избавит вашу команду от головной боли, но вы должны передать имя метода (строчными буквами).

Другими словами, вместо того, чтобы передавать «имя», вы должны передать «полное имя», потому что имя метода get - «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;
}

Если вам нужен доступ к данным, содержащимся в членах контакта, вы можете рассмотреть возможность создания класса-обертки для контакта, который имеет все методы для доступа к любой необходимой информации. Это также было бы полезно для гарантии того, что имена полей доступа всегда будут оставаться одинаковыми (т. Е. Если класс-оболочка имеет getFullName () и вы вызываете с полным именем, он всегда будет работать, даже если метод getFullName () контакта был переименован - он приведет к ошибке компиляции, прежде чем это позволит вам сделать это).

public class ContactWrapper {
    private Contact contact;

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

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

Это решение спасло меня несколько раз, а именно, когда я хотел иметь единственное представление данных для использования в jsf datatables и когда эти данные нужно было экспортировать в отчет с помощью jasper (который по моему опыту плохо обрабатывает сложные средства доступа к объектам) ,

Нил
источник
Мне нравится идея объекта-обертки с методами, вызываемыми через via .invoke(), потому что он полностью исключает строковые константы. Я не очень заинтересован в отражении во время выполнения для настройки карты, хотя, возможно, выполнение getMethodMapping()в staticблоке будет в порядке, так что это происходит при запуске, а не после запуска системы.
Gutch
@gutch, я часто использую шаблон обертки, поскольку он имеет тенденцию вызывать множество проблем, связанных с интерфейсом / контроллером. Интерфейс всегда может использовать оболочку и быть довольным этим, а контроллер тем временем может быть вывернут наизнанку. Все, что вам нужно знать, это то, какие данные вы хотите получить в интерфейсе. И снова, я говорю для акцента, мне обычно не нравится рефлексия, но если это веб-приложение, то это вполне приемлемо, если вы делаете это при запуске, так как клиент не будет видеть никакого времени ожидания.
Нил
@Neil Почему бы не использовать BeanUtils из Apache Commons? Он также поддерживает встроенные объекты. Вы можете пройти через всю структуру данных obj.attrA.attrB.attrN, и у нее есть много других возможностей :-)
Laiv
Вместо сопоставлений с Картами я бы выбрал @Annotations. Что-то вроде JPA делает. Определить мою собственную аннотацию для отображения записей контроллера (строки) с определенным атрибутом или получателем. Работать с аннотациями довольно просто, и это доступно из Java 1.6 (я думаю)
Laiv
5

Если это вообще возможно, используйте Java 7, который позволяет использовать строки в switchвыражениях.

От 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);
        }
    }
}

Я не измерял, но я считаю, что операторы switch компилируются в таблицу переходов вместо длинного списка сравнений. Это должно быть еще быстрее.

Относительно вашего фактического вопроса: если вы используете его только один раз, вам не нужно превращать его в константу. Однако учтите, что константа может быть задокументирована и отображена в Javadoc. Это может быть важно для нетривиальных строковых значений.


источник
2
По поводу прыжкового стола. Переключатель String заменяется на switch, первый основан на хеш-коде (равенство проверяется для всех констант с одинаковым хэш-кодом) и выбирает индекс ветви, второй переключается на индекс ветви и выбирает исходный код ветви. Последний явно подходит для таблицы ветвлений, первый не связан с распределением хеш-функции. Таким образом, любое преимущество в производительности, вероятно, связано с реализацией на основе хеша.
шарфридж
Очень хороший момент; если он работает хорошо, возможно, стоит перейти на Java 7 только для этого ...
gutch
4

Если вы собираетесь поддерживать это (вносить какие-либо нетривиальные изменения когда-либо), я мог бы на самом деле подумать об использовании какого-либо рода генерации кода на основе аннотаций (возможно, через CGLib ) или даже просто сценария, который пишет весь код для вас. Представьте себе количество опечаток и ошибок, которые могут возникнуть с подходом, который вы рассматриваете ...

Стивен Шланскер
источник
Мы рассмотрели аннотирование существующих методов, но некоторые сопоставления пересекают несколько объектов (например, сопоставление «страны» object.getAddress().getCountry()), которые трудно представить с помощью аннотаций. Сравнение строк if / else не очень красиво, но они быстрые, гибкие, простые для понимания и удобные для модульного тестирования.
gutch
1
Вы правы относительно возможности опечаток и ошибок; Моя единственная защита - это юнит-тесты. Конечно, это означает еще больше кода ...
Gutch
2

Я бы все еще использовал константы, определенные в начале ваших классов. Это делает ваш код более понятным, так как легче увидеть, что можно изменить позже (при необходимости). Например, "first_name"может стать "firstName"в более позднее время.

Бернард
источник
Я согласен, однако, если этот код будет сгенерирован автоматически, а константы не используются в другом месте, то это не имеет значения (ОП говорит, что они должны сделать это в 100 классах).
NoChance
5
Я просто не вижу угла «ремонтопригодности», здесь вы меняете «first_name» на «GivenName» один раз в одном месте в любом случае. Однако в случае именованных констант теперь вам остается грязная переменная «first_name», которая ссылается на строка "GivenName", так что вы, вероятно, хотите изменить это, чтобы у вас было три изменения в двух местах
Джеймс Андерсон,
1
При правильной IDE эти изменения тривиальны. Я защищаю то, что становится более очевидным, где вносить эти изменения, потому что вы потратили время на объявление констант в верхней части класса и не должны читать остальную часть кода в классе, чтобы внести эти изменения.
Бернард
Но когда вы читаете оператор if, вы должны вернуться и проверить, что константа содержит строку, которую вы считаете нужной, - здесь ничего не сохраняется.
Джеймс Андерсон
1
Возможно, но именно поэтому я называю свои константы хорошо.
Бернард
1

Если ваше именование непротиворечиво (иначе "some_whatever"всегда отображается на getSomeWhatever()), вы можете использовать отражение, чтобы определить и выполнить метод get.

scarfridge
источник
Лучше getSome_whwhat (). Может быть, разбить верблюжий случай, но гораздо важнее убедиться, что отражение работает. Плюс у этого есть дополнительное преимущество, которое заставляет вас говорить: «Какого черта мы сделали это таким образом ... о, подождите ... Подождите! Джордж, не меняйте название этого метода!"
Нил
0

Я думаю, обработка аннотаций может быть решением, даже без аннотаций. Это то, что может генерировать весь скучный код для вас. Недостатком является то, что вы получите N сгенерированных классов для N модельных классов. Вы также не можете ничего добавить к существующему классу, но пишете что-то вроде

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

Один раз в класс не должно быть проблемой. Кроме того, вы могли бы написать что-то вроде

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

в общем суперклассе.


Вы можете использовать отражение вместо обработки аннотаций для генерации кода. Недостатком является то, что вам нужно, чтобы ваш код компилировался, прежде чем вы сможете использовать рефлексию. Это означает, что вы не можете полагаться на сгенерированный код в классах модели, если вы не сгенерируете некоторые заглушки.


Я также рассмотрел бы прямое использование отражения. Конечно, размышление медленное, но почему оно медленное? Это потому, что он должен делать все, что вам нужно, например, включать имя поля.

maaartinus
источник