Google Guava是否比Apache Collections“更难”使用?[已关闭]

2022-09-01 16:29:01

我正在考虑要求我的团队,混合技能水平,使用谷歌番石榴。在Guava之前,我会使用Apache Collections(或其通用版本)。

与Apache Collections相反,Guava在某些方面似乎更强大,但对于经验不足的程序员来说可能不太容易使用。这里有一个领域,我认为可以举例说明这一点。

我继承的代码包含大量循环访问本质上是异构值映射的列表,探测它们的值,执行空检查,然后执行一些微不足道的事情:

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

}

使用番石榴,我可能会走两种方式:

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 Collections相比,Functions.compose( g, f )颠倒了“直观”的顺序:函数是从右到左应用的,而不是TransformerUtils.chainedTransformer的“明显”从左到右。

一个更微妙的问题是,随着番石榴返回实时视图,调用实时视图可能会多次应用(组合)函数,所以我真正应该做的是:contains

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

但是我转换后的集合中可能有 null,所以我不能完全做到这一点。当然,我可以将其转储到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 ) ;
}

我想我是在问,“为什么番石榴中没有'更容易'”,我想答案是,“好吧,总是把它返回的东西转储到一个新的收藏中,或者写你自己的转换来做到这一点”。transform

但是,如果我需要这样做,番石榴图书馆的其他客户可能不可以吗?也许在番石榴中有更好的方式,我不知道?


答案 1

我想说的是,Guava绝对不比Apache Collections更难使用。我会说这实际上要容易得多。

Guava的优势之一是它没有暴露那么多新的对象类型......它喜欢将它使用的大多数实际实现类型整齐地隐藏在仅公开接口的静态工厂方法后面。以各种s为例。在Apache集合中,您有顶级的公共实现类,例如:Predicate

NullPredicate
NotNullPredicate
NotPredicate
AllPredicate
AndPredicate
AnyPredicate
OrPredicate

再加上一吨。

在番石榴,这些被整齐地包装在一个单一的顶级班级中, :Predicates

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

它们都没有公开他们的实现类,因为你不需要知道它!虽然Apache Collections确实有一个等价物,但它公开了其类型的事实使它更难使用。在我看来,Apache Collections只是一团糟的不必要的可见类和不太有用的部分,这些部分增加了混乱,使获取和使用有用部分变得更加困难。当您查看两个库公开的类和接口的数量时,差异很明显:PredicateUtilsPredicate

  • Apache Collections公开了309种类型。
  • 番石榴,包括其所有包装(不仅仅是收藏品)仅暴露了191种类型。

除此之外,Guava更加小心,只包含真正有用的实用程序和类,严格遵守其实现的接口的契约等,我认为它是一个质量更高,更易于使用的库。

要解决您的一些具体问题:

我实际上认为番石榴选择的顺序直观(尽管我认为这是一个非常主观的论点)。请注意,在使用番石榴进行组合的示例中,应用函数的顺序从声明的末尾读取到分配最终结果的位置。您的示例的另一个问题是,它一开始就不是类型安全的,因为原始示例涉及将方法的结果转换为另一种类型。与Apache Commons示例中的s数组相比,Guava的一个优点是可以执行类型安全的函数组合,确保(在编译时)您正在应用的一系列函数将正常工作。Apache版本在这方面是完全不安全的。Functions.composegetcomposeTransformercompose

视图优于副本:

二、关于实时取景的“问题”。坦率地说,你在这一点上完全错了。使用实时视图而不是将原始元素复制到新元素中实际上要高效得多!以下是当您调用然后调用它返回时将发生的情况:Collections2.transformCollectionCollectionCollections2.transformcontainsCollection

  • 将创建包装原始数据的视图...原始文件和两者都只是简单地分配给其中的字段。CollectionFunction
  • 的迭代器被检索。Collection
  • 对于 中的每个元素,将应用 ,获取该元素的转换值。IteratorFunction
  • 当找到要检查的对象的转换值的第一个元素时,将返回该元素。您只需迭代(并应用 )直到找到匹配项!每个元素最多应用一次!equalscontainsFunctionFunction

以下是Apache Collections版本的功能:

  • 创建一个新的来存储转换后的值。ArrayList
  • 获取原始的 迭代器。Collection
  • 对于原始迭代器中的每个元素,应用该函数并将结果添加到 new 。这是针对原始元素的每个元素完成的,即使将 应用于第一个元素的结果与我们正在寻找的对象相匹配!CollectionCollectionCollectionTransformer
  • 然后,将循环访问新元素中的每个元素以查找结果。containsCollection

以下是使用两个库的大小为 N 的 a 的最佳和最坏情况。最好的情况是,当您要查找的对象的第一个元素的转换值时,最坏的情况是当您要查找的值在转换后的集合中不存在时。Collectionequalscontainscontains

  • 番石榴
    • 最佳情况:迭代 1 个元素,应用 1 次,存储 0 个附加元素。Function
    • 最坏情况:迭代 N 个元素,应用 N 次,存储 0 个附加元素。Function
  • 阿帕奇
    • 最佳情况:迭代 N + 1 个元素,应用 N 次,存储 N 个附加元素(转换后的集合)。Transformer
    • 最坏情况:迭代 2N 个元素,应用 N 次,存储 N 个附加元素(转换后的集合)。Transformer

我希望从上面可以明显看出,总的来说,观点是一件非常好的事情!此外,随时将视图复制到非视图集合中非常容易,这将具有与Apache版本相同的性能。但是,在您给出的任何示例中,它肯定没有用处。

作为最后的次要注释,存在只是为了允许您检查您不知道是某个值的 a 是否包含值。如果你给它实际上是一个,它很好地只是要求你允许可能更好的性能(如果它是一个,比如说)。Iterables.containsIterableCollectionIterableCollectioncontains()CollectionSet


答案 2

作为番石榴的开发者之一,我显然有偏见,但这里有一些评论。

易用性是番石榴设计背后的主要目标之一。总有改进的余地,我们渴望听到任何建议或疑虑。设计决策背后通常有一个基本原理,尽管每个人都可能找到他们个人不同意的东西。

在实时视图方面,ColinD描述了某些用例存在的性能优势。此外,有时您希望对视图的更改更改原始集合,反之亦然。

现在,在某些情况下,复制集合可以提供更好的性能,但只需一行代码即可执行此操作。虽然Guava可以包含transformAndCopy()方法,但我们省略了单行方法,除了像Maps.newHashMap()这样非常常见的情况。存在的方法越多,找到所需的方法就越困难。