java - spanish - guava maven
¿Es Google Guava "más difícil" de usar que las colecciones Apache? (2)
Como uno de los desarrolladores de Guava, obviamente estoy parcializado, pero aquí hay algunos comentarios.
La facilidad de uso fue uno de los objetivos principales detrás del diseño de Guava. Siempre hay espacio para mejorar, y estamos ansiosos por escuchar cualquier sugerencia o inquietud. Por lo general, hay una razón detrás de las decisiones de diseño, aunque probablemente todos puedan encontrar cosas con las que personalmente no están de acuerdo.
En términos de las vistas en vivo, ColinD describió las ventajas de rendimiento que existen para algunos casos de uso. Además, a veces desea que los cambios en la vista alteren la colección original y viceversa.
Ahora, hay casos en los que copiar la colección proporciona un mejor rendimiento, pero solo se necesita una línea de código para hacerlo. Si bien Guava podría incluir métodos transformAndCopy (), omitimos métodos de una línea, excepto en casos extremadamente comunes como Maps.newHashMap (). Cuantos más métodos estén presentes, más difícil será encontrar el método que necesita.
Estoy pensando en pedirle a mi equipo, de niveles de habilidad mixtos, que use Google Guava. Antes de Guayaba, habría usado las Colecciones Apache (o su versión generada).
La guayaba, a diferencia de las colecciones de Apache, parece ser más fuerte en algunos aspectos, pero tal vez sea menos fácil de usar para los programadores menos experimentados. Aquí hay un área donde creo que podría ejemplificar eso.
El código que he heredado contiene una gran cantidad de bucles en listas de lo que son esencialmente mapas de valores heterogéneos, sondeando valores, realizando controles nulos y luego haciendo algo 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 ;
}
Mi pensamiento inicial fue usar Transformers de Apache Collections:
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() ) ;
}
Usando guayaba, puedo ir de dos maneras:
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
}
En comparación con las colecciones de Apache, Functions.compose (g, f) invierte el orden "intuitivo": las funciones se aplican de derecha a izquierda, en lugar del "obvio" de izquierda a derecha de TransformerUtils.chainedTransformer.
Un problema más sutil es que, cuando Guava devuelve una vista en vivo, es probable que las llamadas contains
en la vista en vivo apliquen la función (compuesta) varias veces, por lo que lo que realmente debo hacer es:
return ImmutableSet.copy( sa ).contains( target.toUpperCase() ) ;
Pero podría tener nulos en mi conjunto transformado, así que no puedo hacer eso. Puedo volcarlo en un java.util.Collection, por supuesto.
Pero eso no va a ser obvio para mi equipo (menos experimentado), y es probable que se pierda en el calor de la codificación incluso después de que lo explique. Esperaba que tal vez Iterables.contains () "hiciera lo correcto" y conociera algún ejemplo de magia para distinguir un proxy de vista en vivo de una antigua colección simple, pero no lo hace. Eso hace que la guayaba sea más difícil de usar.
¿Quizás escribo algo como un método estático en mi clase de utilidad para manejar esto?
// 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 ) ;
}
¿O quizás solo copiar conjuntos por encima de cierto tamaño?
// 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 ) ;
}
Supongo que estoy preguntando "por qué no hay una transform
''más fácil'' en Guava", y supongo que la respuesta es: "bien, simplemente descargue lo que devuelve en una nueva colección, o escriba su propia transformación que sí lo haga". ese".
Pero si necesito hacer eso, ¿no podrían otros clientes de las bibliotecas de Guayaba? Tal vez hay una mejor manera de hacerlo en Guava, que no conozco?
Yo diría que la Guayaba definitivamente no es más difícil de usar que las Colecciones Apache. Yo diría que es mucho más fácil, en realidad.
Uno de los puntos importantes en la ventaja de Guava es que no expone tantos tipos de objetos nuevos ... le gusta mantener ocultos la mayoría de los tipos de implementación reales que utiliza detrás de los métodos de fábrica estática que solo exponen la interfaz. Tomemos los distintos Predicate
, por ejemplo. En Apache Collections, tiene clases de implementación pública de nivel superior como:
NullPredicate
NotNullPredicate
NotPredicate
AllPredicate
AndPredicate
AnyPredicate
OrPredicate
Más una tonelada más.
En Guayaba, estos se empaquetan cuidadosamente en una sola clase de nivel superior, Predicates
:
Predicates.isNull()
Predicates.notNull()
Predicates.not(...)
Predicates.and(...)
Predicates.or(...)
Ninguno de ellos expone su clase de implementación, ¡porque no necesitas saberlo! Si bien las colecciones Apache tienen un PredicateUtils
equivalente, el hecho de que exponga los tipos de su Predicate
s hace que sea más difícil de usar. Como lo veo, las colecciones de Apache son solo un montón de clases visibles innecesarias y partes no muy útiles que agregan desorden y dificultan el acceso y uso de las partes útiles. La diferencia es clara cuando se observa el número de clases e interfaces que exponen las dos bibliotecas:
- Apache Collections expone 309 tipos.
- La guayaba, incluidos todos sus paquetes (no solo las colecciones), expone solo 191 tipos.
Agregue a eso la forma en que Guava es mucho más cuidadoso solo para incluir utilidades y clases realmente útiles, su rigurosa adhesión a los contratos de las interfaces que implementa, etc. y creo que es una biblioteca de mayor calidad y más fácil de usar.
Para abordar algunos de sus puntos específicos:
De hecho, creo que el orden que eligió Guava para Functions.compose
es más intuitivo (aunque creo que para empezar es un argumento bastante subjetivo). Tenga en cuenta que en su ejemplo de composición con guayaba, el orden en que se aplicarán las funciones se lee desde el final de la declaración hacia el lugar donde se asigna el resultado final. Otro problema con su ejemplo es que, para comenzar, no es seguro para el tipo, ya que el ejemplo original implica convertir el resultado del método get
a otro tipo. Una ventaja de la compose
de Guava sobre la matriz de Transformer
s en el ejemplo de Apache Commons es que compose
puede hacer una composición de funciones de tipo seguro, asegurando (en tiempo de compilación) que la serie de funciones que está aplicando funcionará correctamente. La versión de Apache es completamente insegura en este sentido.
Las vistas son superiores a las copias:
En segundo lugar, sobre el "problema" de visualización en vivo de Collections2.transform
. Para ser franco, estás completamente equivocado en ese punto. ¡El uso de una vista en vivo en lugar de copiar todos los elementos de la Collection
original en una nueva Collection
es en realidad mucho más eficiente! Esto es lo que sucederá cuando llame a Collections2.transform
y luego la llamada contains
en la Collection
que devuelve:
- Se crea una vista
Collection
envuelve el original ... el original y laFunction
se asignan simplemente a los campos que contiene. - Se recupera el iterador de la
Collection
. - Para cada elemento en el
Iterator
, se aplicará laFunction
, obteniendo el valor transformado de ese elemento. - Cuando se encuentra el primer elemento para el cual el valor transformado
equals
al objeto que está verificando,contains
retornará. ¡Solo se itera (y se aplica laFunction
) hasta que se encuentra una coincidencia! LaFunction
se aplica como máximo una vez por elemento!
Esto es lo que hace la versión de Apache Collections:
- Crea una nueva
ArrayList
para almacenar los valores transformados. - Obtiene el iterador de la
Collection
original. - Para cada elemento en el iterador de la
Collection
original, aplica la función y agrega el resultado a la nuevaCollection
. Esto se hace para cada elemento de laCollection
original, incluso si el resultado de aplicar elTransformer
al primer elemento hubiera coincidido con el objeto que estamos buscando. - Luego ,
contains
iterará sobre cada elemento en la nuevaCollection
buscando el resultado.
Estos son los mejores y peores escenarios para una Collection
de tamaño N que utiliza ambas bibliotecas. El mejor caso es cuando el valor transformado del primer elemento equals
al objeto que está buscando con contains
y el peor caso es cuando el valor que está buscando con contains
no existe en la colección transformada.
- Guayaba
- Mejor caso : itera 1 elemento, aplica el tiempo de la
Function
1, almacena 0 elementos adicionales. - El peor de los casos : itera N elementos, aplica la
Function
N veces, almacena 0 elementos adicionales.
- Mejor caso : itera 1 elemento, aplica el tiempo de la
- Apache
- Mejor caso : itera N + 1 elementos, aplica
Transformer
N veces, almacena N elementos adicionales (la colección transformada). - El peor de los casos : itera elementos 2N, aplica
Transformer
N veces, almacena N elementos adicionales (la colección transformada).
- Mejor caso : itera N + 1 elementos, aplica
Espero que sea obvio por lo anterior que, en general, ¡una vista es algo muy bueno! Además, es realmente fácil copiar una vista en una colección que no sea vista en cualquier momento que sea útil, y que tendrá el mismo rendimiento que la versión de Apache para empezar. Sin embargo, decididamente no sería útil en ninguno de los ejemplos que ha dado.
Como una pequeña nota final, Iterables.contains
existe simplemente para permitirte verificar si un Iterable
que no sabes que es una Collection
contiene un valor. Si el Iterable
le das es en realidad una Collection
, simplemente llama contains()
en esa Collection
para permitir un posible mejor rendimiento (si es un Set
, por ejemplo).