I'm thinking of asking my team, of mixed skill levels, to use Google Guava. Prior to Guava, I'd have used the Apache Collections (or its generified version).
Guava, as opposed to Apache Collections, seems to be stronger in some ways, but perhaps less easy to use for less experienced programmers. Here's one area where I think might exemplify that.
The code I've inherited contains a lot of looping over lists of what are essentially maps of heterogeneous values, probing them for values, doing null checks, and then doing something trivial:
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 ; }
My initial thought was to use 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() ) ; }
Using Guava, I might go two ways:
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
}
Compared to Apache Collections, Functions.compose( g, f ) reverses the "intuitive" order: functions are applied right-to-left, rather than the "obvious" left-to-right of TransformerUtils.chainedTransformer.
A more subtle issue is that, as Guava returns a live view, calling contains
on the live view is likely to apply the (composed) function multiple times, so what I really ought to do is:
return ImmutableSet.copy( sa ).contains( target.toUpperCase() ) ;
But I might have nulls in my transformed set, so I can't quite do that. I can dump it into a java.util.Collection, of course.
But that's not going to be obvious to my (less experienced) team, and will likely be missed in the heat of coding even after I explain it. I'd hoped that perhaps Iterables.contains() would "do the right thing" and know some instance-of magic to distinguish a live-view proxy from a plain old Collection, but it doesn't. That makes Guava perhaps harder to use.
Perhaps I write something like a static method in my utility class to handle this?
// 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 ) ; }
or perhaps only copy sets above a certain size?
// 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 ) ; }
I suppose I'm asking, "why isn't there an 'easier' transform
in Guava", and I suppose the answer is, "fine, just always dump what it returns into a new Collection, or write your own transform that does that".
But if I need to do that, might not other clients of the Guava libraries? Perhaps there's a better way that is in Guava, that I don't know about?
Guava is an open-source “Collection Library” library for Java, developed by Google. It provides utilities for working with Java collections. As you dive deep into Guava you'll notice how it reduces coding errors, facilitates standard coding practices and boost productivity by making code concise and easy to read.
Guava is an open source, Java-based library developed by Google. It facilitates best coding practices and helps reduce coding errors. It provides utility methods for collections, caching, primitives support, concurrency, common annotations, string processing, I/O, and validations.
Guava: Google Core Libraries For Java » 24.0-jre Guava is a suite of core and expanded libraries that include utility classes, Google's collections, I/O classes, and much more. License. Apache 2.0. Categories. Core Utilities.
I'd say that Guava is most definitely not harder to use than Apache Collections. I'd say it's a lot easier, actually.
One of the big points in Guava's advantage is that it doesn't expose so many new object types... it likes to keep most of the actual implementation types it uses hidden neatly away behind static factory methods that only expose the interface. Take the various Predicate
s, for example. In Apache Collections, you have top level public implementation classes like:
NullPredicate NotNullPredicate NotPredicate AllPredicate AndPredicate AnyPredicate OrPredicate
Plus a ton more.
In Guava, these are neatly packaged up in a single top level class, Predicates
:
Predicates.isNull() Predicates.notNull() Predicates.not(...) Predicates.and(...) Predicates.or(...)
None of them expose their implementation class, because you don't need to know it! While Apache Collections does have an equivalent PredicateUtils
, the fact that it exposes the types of its Predicate
s makes it harder to use. As I see it, Apache Collections is just a whole mess of unnecessary visible classes and not-very-useful parts that add clutter and make it harder to get at and use the useful parts. The difference is clear when you look at the number of classes and interfaces the two libraries expose:
Add to that the way Guava is much more careful only to include truly useful utilities and classes, its rigorous adherence to the contracts of the interfaces it implements, etc. and I think it's a much higher quality, easier to use library.
To address some of your specific points:
I actually think that the order Guava chose for Functions.compose
is more intuitive (though I think that's quite a subjective argument to begin with). Note that in your example of composition with Guava, the order in which the functions will be applied reads from the end of the declaration toward the place where the final result is assigned. Another problem with your example is that it isn't type-safe to begin with, since the original example involves casting the result of the get
method to another type. An advantage of Guava's compose
over the array of Transformer
s in the Apache Commons example is that compose
can do a type-safe composition of functions, ensuring (at compile time) that the series of functions you're applying will work correctly. The Apache version is completely unsafe in this regard.
Views are superior to copies:
Second, about the live view "issue" of Collections2.transform
. To be blunt, you're completely wrong on that point. The use of a live view rather than copying all elements of the original Collection
into a new Collection
is actually far more efficient! Here's what's going to happen when you call Collections2.transform
and then call contains
on the Collection
it returns:
Collection
wrapping the original is created... the original and the Function
are both simply assigned to fields in it.Collection
's iterator is retrieved.Iterator
, the Function
will be applied, getting the transformed value of that element.equals
the object you're checking for is found, contains
will return. You only iterate (and apply the Function
) until a match is found! The Function
is applied at most once per element!Here's what the Apache Collections version does:
ArrayList
to store the transformed values.Collection
's iterator.Collection
's iterator, applies the function and adds the result to the new Collection
. This is done for every element of the original Collection
, even if the result of applying the Transformer
to the very first element would have matched the object we're looking for!contains
will iterate over each element in the new Collection
looking for the result.Here's the best and worst case scenarios for a Collection
of size N using both libraries. The best case is when the transformed value of the first element equals
the object you're looking for with contains
and the worst case is when the value you're looking for with contains
does not exist in the transformed collection.
Function
1 time, stores 0 additional elements.Function
N times, stores 0 additional elements.Transformer
N times, stores N additional elements (the transformed collection).Transformer
N times, stores N additional elements (the transformed collection).I hope it's obvious from the above that, in general, a view is a very good thing! Plus, it's really easy to copy a view into a non-view collection any time that would be useful, and that will have the same performance as the Apache version does to begin with. However, it would decidedly not be useful in any of the examples you've given.
As a final minor note, Iterables.contains
exists simply to allow you to check if an Iterable
that you do not know to be a Collection
contains a value. If the Iterable
you give it actually is a Collection
, it nicely just calls contains()
on that Collection
for you to allow for possible better performance (if it's a Set
, say).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With