Имеет ли смысл моделирование замыканий в Java? - PullRequest
8 голосов
/ 27 ноября 2009

Языки с замыканиями, такие как Ruby, позволяют элегантным конструкциям преобразовывать списки. Предположим, у нас есть класс

class QueryTerm {
  String value;
  public String getValue() {...}
}

и список терминов List<QueryTerm> terms, которые мы хотим преобразовать в его список значений List<String> values.

В Ruby мы могли бы написать что-то вроде:

values1 = terms.collect do |term|
    term.getValue()
end

Java вынуждает нас создавать список результатов и самим проходить итерацию по коллекции терминов (по крайней мере, с тех пор, как был введен foreach), итератор или позиция индекса не задействованы:

Collection<String> values2 = new HashSet<String>();
for (QueryTerm term : terms) {
    values2.add(term.getValue());
}

Apache Commons и Google Collections2 пытаются эмулировать замыкания в Java с помощью анонимных классов:

Collection<String> values3 = Collections2.transform(terms, new Function<QueryTerm, String>() {
    @Override
    public String apply(QueryTerm term) {
        return term.getValue();
    }
});

Странно то, что в этой версии больше строк кода и больше символов, чем в оригинальной версии! Я бы предположил, что по этой причине это сложнее понять. Поэтому я отклонил эту идею назад, когда увидел ее в Apache Commons. Однако, увидев его недавно в коллекциях Google, я начинаю задумываться, не упустил ли я что-то.

Поэтому мой вопрос: что вы думаете о конструкциях выше? Считаете ли вы версию Collections2.transform() более читаемой / понятной, чем версия по умолчанию? Я что-то упустил полностью?

С наилучшими пожеланиями, Johannes

Ответы [ 7 ]

5 голосов
/ 27 ноября 2009

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

Для меня сразу есть несколько преимуществ:

  • Функциональный стиль объявления ваших операций более соответствует нашему взгляду на них, т. Е. Вот моя функция, пожалуйста, примените его к этим данным.
  • Цепочка преобразований происходит посредством прозрачного использования различных вызовов для применения / фильтрации / преобразования. Вы можете просто продолжать связывать их друг за другом, и вы видите все операции, собранные после друг друга в одной строке. Конечно, вокруг него все еще много шаблонов, но если все сделано правильно, то он будет в высшей степени читаемым для любого Java-программиста.
  • Инверсия управления: скажем, вы хотите разложить свою работу на несколько разных потоков. Если вы написали свой собственный цикл, вы застряли: вы должны самостоятельно разбить его на несколько различных циклов. Это обычно уродливо и сложно писать код, и вам потребуется создать несколько Runnables, которые вы передадите другим потокам. Так что на самом деле вы просто снова работаете с замыканиями.

    Одно из предложений JDK7, которое не сделает этого, по-видимому, было чем-то, что сделало именно это для вас, когда вы просто отправляете свои данные и определяете, какие фильтры для них выполняете, и вы отпускаете их в свое удовольствие. Вы можете скачать код для этого (пакет extra166y). Все операции, которые вы можете увидеть в javadoc для этого пакета, могут быть устранены и заменены замыканиями. Так что это хороший пример того, почему определенный подход терпит неудачу из-за отсутствия реальных замыканий, и «имитация» замыканий уже не совсем его сокращает.

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

Одна из фреймворков, которые я использовал, которая использует что-то вроде этого подхода для удаления большого количества шаблонов, - Spring-Jdbc . Здесь вы передаете JdbcTemplate простую функцию, которая захватывает ваши объекты из ResultSet, и вы передаете его вместе со своим запросом в template.query(). Это устраняет много обострений и нечитаемости обычного кода JDBC. Определенная победа, если вы будете поддерживать что-то подобное.

4 голосов
/ 27 ноября 2009

IMO, даже с большим количеством строк кода, версия transform в вашем конкретном примере выглядит более читабельной. Зачем? Из-за этого слова transform . Вы сразу знаете, что в коллекции выполняется преобразование, в то время как с версией normal вы должны следить за потоком кода, чтобы понять его назначение. Как только вы овладеете этими новыми конструкциями, вы найдете их более читабельными.

2 голосов
/ 27 ноября 2009

Мне они нравятся и много их используют. Я считаю, что главное преимущество заключается не в синтаксисе, а в том, что вы можете заранее определить и повторно использовать код, объявленный выше как анонимные классы. Например, вы можете предопределить стандартные преобразования, которые вы можете передать в функцию transform, даже NullTransform, которая ничего не делает для получения эффекта шаблона Null Object. Это позволяет вам более декларативный стиль кодирования.

1 голос
/ 27 ноября 2009

В разработке программного обеспечения ясность и понятность (читай: поддерживаемость) важнее краткости, если только вы не развлекаетесь с одноразовым кодом или скриптом perl: -)

Если вы хотите преобразовать список объектов в список объектов другого типа, ваш первый пример на Java является самым ясным в отношении намерений кода, imho.

Пример Google collecions также ясно показывает, для чего предназначен код, хотя, как сказал Ханнес, я бы не использовал анонимные классы, а явно определенный класс с полезным именем.

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

1 голос
/ 27 ноября 2009

Что Бруно Конде сказал. Кроме того, некоторые IDE (например, Eclipse) могут скрывать анонимные классы, сокращая код Google Collection до одной строки. Теперь , что доступно для чтения! :)

0 голосов
/ 27 ноября 2009

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

Collection<String> values2 = new HashSet<String>();
for (QueryTerm term : terms) {
    values2.add(term.getValue());
}

в этом:

Collection<String> values2 = extract(terms, on(QueryTerm.class).getValue());

Тебе не кажется, что это так читается?

0 голосов
/ 27 ноября 2009

Это очень уродливо, и люди жаждут лучшего синтаксиса. Через некоторое время к этому можно привыкнуть, но для начинающих это пугает.

Забудьте @Override - код уже загроможден. @Override решает проблему, которой нет на самом деле, но сейчас люди используют ее, как если бы это был критический семантический элемент.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...