Подтвердить что ты не робот

Является ли Google Guava более "трудным", чем Apache Collections?

Я подумываю просить мою команду о смешанных уровнях навыков использовать Google Guava. До Guava я бы использовал Apache Collections (или его расширенную версию).

Гуава, в отличие от Apache Collections, кажется в какой-то степени сильнее, но, возможно, менее проста в использовании для менее опытных программистов. Здесь одна область, где, на мой взгляд, может служить примером.

Код, который я унаследовал, содержит много циклов над списками того, что по существу является картами гетерогенных значений, исследуя их для значений, делая нулевые проверки, а затем делая что-то тривиальное:

boolean foo( final List< MapLike > stuff, final String target ) {
  final String upperCaseTarget = target.toUpperCase(0;

  for( MapLike m : stuff ) {
     final Maplike n = (MapLike) m.get( "hard coded string" );
     if( n != null ) {
         final String s = n.get( "another hard code string" );
         if( s != null && s.toUpperCase().equals( upperCaseTarget ) ) {
            return true ;
         }
     }
   return false ;
}

Моя первоначальная мысль заключалась в использовании Apache Collections Transformers:

boolean foo( final List< MapLike > stuff, final String target ) {
   Collection< String> sa = (Collection< String >) CollectionUtils.collect( stuff, 
     TransformerUtils.chainedTransformer( new Transformer[] { 
        AppUtils.propertyTransformer("hard coded string"),
        AppUtils.propertyTransformer("another hard coded string"),
        AppUtils.upperCaseTransformer()
         } ) );

    return sa.contains( target.toUpperCase() ) ;        

}

Используя Guava, я могу пойти двумя способами:

boolean foo( final List< MapLike > stuff, final String target ) {
   Collection< String > sa = Collections2.transform( stuff,
       Functions.compose( AppUtils.upperCaseFunction(), 
       Functions.compose( AppUtils.propertyFunction("another hard coded string"), 
                          AppUtils.propertyFunction("hard coded string") ) ) );

    return sa.contains( target.toUpperCase() ) ;    
    // or
    // Iterables.contains( sa, target.toUpperCase() );
    // which actually doesn't buy me much

}

По сравнению с коллекциями Apache функции .com.com(g, f) меняют "интуитивный" порядок: функции применяются справа налево, а не "очевидные" слева направо от TransformerUtils.chainedTransformer.

Более тонкая проблема заключается в том, что, поскольку Guava возвращает живое представление, вызов contains в режиме просмотра в реальном времени, скорее всего, применит (сложенную) функцию несколько раз, поэтому мне действительно нужно:

   return ImmutableSet.copy( sa ).contains( target.toUpperCase() ) ;

Но у меня могут быть нули в моем преобразованном наборе, поэтому я не могу этого сделать. Я могу сбросить его в java.util.Collection, конечно.

Но это не будет очевидно для моей (менее опытной) команды и, вероятно, будет пропущено в разгаре кодирования даже после того, как я объясню это. Я надеялся, что, возможно, Iterables.contains() "пойдет правильно" и узнает какой-то экземпляр магии, чтобы отличить прокси-сервер в реальном времени от простой старой коллекции, но это не так. Это делает Гуаву, возможно, труднее использовать.

Возможно, я пишу что-то вроде статического метода в моем классе утилиты, чтобы справиться с этим?

// List always uses linear search? So no value in copying?
// or perhaps I should copy it into a set?
boolean contains( final List list, final Object target ) {
  return list.contains( target ) ;
}

// Set doesn't use linear search, so copy?
boolean contains( final Set set, final Object target ) {
  //return ImmutableSet.copy( set ).contains( target ) ;
  // whoops, I might have nulls
  return Sets.newHashSet( set ).contains( target ) ;
}

или, возможно, только экземпляры копий выше определенного размера?

// Set doesn't use linear search, so copy?
boolean contains( final Set set, final Object target ) {
  final Set search = set.size() > 16 : Sets.newHashSet( set ) : set ;
  return search.contains( target ) ;
}

Я полагаю, что я спрашиваю: "Почему в Guava нет" легче "transform, и я полагаю, что ответ" отлично, просто всегда дампируйте то, что он возвращает в новую коллекцию, или напишите ваше собственное преобразование, которое делает это ".

Но если мне нужно это сделать, возможно, не другие клиенты библиотек Guava? Возможно, есть лучший способ, который есть в Гуаве, о котором я не знаю?

4b9b3361

Ответ 1

Я бы сказал, что Guava определенно не сложнее использовать, чем Apache Collections. На самом деле я бы сказал, что это намного проще.

Одна из главных достоинств преимуществ Guava заключается в том, что он не раскрывает так много новых типов объектов... ему нравится хранить большинство реальных типов реализации, которые он использует, скрытно скрываясь за статическими методами factory, которые только открывают интерфейс. Возьмем различные Predicate s, например. В Apache Collections у вас есть общедоступные классы реализации верхнего уровня, например:

NullPredicate
NotNullPredicate
NotPredicate
AllPredicate
AndPredicate
AnyPredicate
OrPredicate

Плюс больше тонны.

В Guava они аккуратно упакованы в один класс верхнего уровня, Predicates:

Predicates.isNull()
Predicates.notNull()
Predicates.not(...)
Predicates.and(...)
Predicates.or(...)

Ни один из них не раскрывает их класс реализации, потому что вам не нужно это знать! Хотя Apache Collections имеет эквивалент PredicateUtils, тот факт, что он предоставляет типы Predicate, затрудняет его использование. Как я вижу, Apache Collections - всего лишь беспорядок ненужных видимых классов и не очень полезных частей, которые добавляют беспорядок и затрудняют получение и использование полезных частей. Разница очевидна, когда вы смотрите на количество классов и интерфейсов, отображаемых двумя библиотеками:

  • Коллекции Apache предоставляют 309 типов.
  • Guava, включая все его пакеты (а не только коллекции), содержит только 191 тип.

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

Чтобы решить некоторые из ваших конкретных вопросов:

Я на самом деле думаю, что порядок, выбранный Guava для Functions.compose, более интуитивно понятен (хотя я считаю, что с самого начала субъективный аргумент). Обратите внимание, что в вашем примере композиции с Guava порядок, в котором будут применяться функции, читается с конца объявления в направлении места, где назначается конечный результат. Другая проблема с вашим примером заключается в том, что он не является безопасным для типов, поскольку исходный пример включает в себя передачу результата метода get другому типу. Преимущество Guava compose в массиве Transformer в примере Apache Commons заключается в том, что compose может выполнять набор функций, безопасный для типов, обеспечивая (во время компиляции), что ряд функций, которые вы применяете, будет работайте правильно. Версия Apache совершенно небезопасна в этом отношении.

Представления превосходят копии:

Во-вторых, о "представлении" в режиме реального времени Collections2.transform. Чтобы быть тупым, вы совершенно неправы в этом вопросе. Использование живого изображения вместо копирования всех элементов исходного Collection в новый Collection на самом деле намного эффективнее! Здесь, что произойдет, когда вы вызовете Collections2.transform, а затем вызовите contains на Collection, он вернется:

  • Создается представление Collection обертывания оригинала... оригинал и Function оба просто назначаются полям в нем.
  • Итератор Collection извлекается.
  • Для каждого элемента в Iterator применяется Function, получая преобразованное значение этого элемента.
  • Когда первый элемент, для которого преобразованное значение equals найден объект, который вы проверяете, возвращается contains. Вы только повторяете (и применяете Function) до тех пор, пока не будет найдено совпадение! Function применяется не более одного раза на элемент!

Вот что делает версия Apache Collections:

  • Создает новый ArrayList для хранения преобразованных значений.
  • Возвращает оригинальный итератор Collection.
  • Для каждого элемента исходного итератора Collection применяется эта функция и добавляется результат к новому Collection. Это делается для каждого элемента исходного Collection, даже если результат применения Transformer к самому первому элементу соответствовал бы объекту, который мы ищем!
  • Затем contains будет перебирать каждый элемент нового Collection в поисках результата.

Здесь лучшие и худшие сценарии для Collection размера N, использующего обе библиотеки. Лучшим случаем является преобразование значения первого элемента equals объекта, который вы ищете с помощью contains, а наихудший случай - когда значение, которое вы ищете с помощью contains, не существует в преобразованной коллекции.

  • гуавы:
    • Лучший случай: итерации 1 элемент, применяется Function 1 раз, сохраняет 0 дополнительных элементов.
    • Наихудший случай: выполняет итерацию N элементов, применяет Function N раз, сохраняет 0 дополнительных элементов.
  • Apache:
    • Лучший случай: итерации N + 1 элементов, применяется Transformer N раз, хранит N дополнительных элементов (преобразованная коллекция).
    • Худший случай: выполняет итерации 2N элементов, применяет Transformer N раз, сохраняет N дополнительных элементов (преобразованная коллекция).

Я надеюсь, что из сказанного очевидно, что, в общем, представление - это очень хорошая вещь! Кроме того, очень легко скопировать представление в коллекцию без просмотра в любое время, которое было бы полезно, и это будет иметь ту же производительность, что и версия Apache. Тем не менее, это было бы явно не полезно в любом из приведенных вами примеров.

Как заключительная незначительная заметка, Iterables.contains существует просто, чтобы вы могли проверить, есть ли Iterable, который вы не знаете как Collection, содержит значение. Если Iterable вы на самом деле это Collection, он просто вызывает contains() на этом Collection, чтобы вы могли обеспечить лучшую производительность (если это Set, скажем).

Ответ 2

Как один из разработчиков Guava, я явно предвзятый, но вот несколько комментариев.

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

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

Теперь есть случаи, когда копирование коллекции обеспечивает лучшую производительность, но для этого требуется только одна строка кода. Хотя Guava может включать методы transformAndCopy(), мы опускаем однострочные методы, за исключением чрезвычайно распространенных случаев, таких как Maps.newHashMap(). Чем больше методов присутствует, тем труднее найти нужный вам метод.