1. Classes with useful static methods for working with lists, maps and sets
2. The Range class used to represent the boundaries around a continuous set of values
3. Immutable Collections
4. Bimaps
5. The Table collection type, which is a very powerful collection that is a replacement for using a map of maps.
6. Multimaps, which allow us to have more than one value associated with unique key.
7. The FluentIterable class, which represents a set of powerful interfaces for working with Iterable instances.
8. The Ordering class that gives us enhanced abilities when working with Comparators
1. FluentIterable
The FluentIterable class represents a powerful interface for working with Iterable instances in fluent style of programming.
The fluent programming style allows us to chain method calls together, making for a more readable code.
1> FluentIterable.filter(Predicate<T>)
@Test
public void filterTest() {
List<String> list = Lists.newArrayList("A", "B", "C", "D");
List<String> filteredList = FluentIterable.from(list)
.filter(Predicates.and(new Predicate<String>() {
public boolean apply(String input) {
return "A".compareTo(input) < 0;
}
}, new Predicate<String>() {
public boolean apply(String input) {
return "D".compareTo(input) > 0;
}
})).toList();
assertEquals(2, filteredList.size());
assertEquals("B", filteredList.get(0));
assertEquals("C", filteredList.get(1));
}
source code:
public static <E> FluentIterable<E> from(final Iterable<E> iterable) {
return (iterable instanceof FluentIterable) ? (FluentIterable<E>) iterable
: new FluentIterable<E>(iterable) {
@Override
public Iterator<E> iterator() {
return iterable.iterator();
}
};
}
FluentIterable(Iterable<E> iterable) {
this.iterable = checkNotNull(iterable); // set passed in iterable as property
}
public final FluentIterable<E> filter(Predicate<? super E> predicate) {
return from(Iterables.filter(iterable, predicate));
}
public static <T> UnmodifiableIterator<T> filter(
final Iterator<T> unfiltered, final Predicate<? super T> predicate) {
return new AbstractIterator<T>() {
@Override protected T computeNext() {
while (unfiltered.hasNext()) {
T element = unfiltered.next();
if (predicate.apply(element)) {
return element;
}
}
return endOfData();
}
};
}
2> FluentIterable.transform(Function<F, T>)
@Test
public void transformTest() {
List<String> list = Lists.newArrayList("A", "B", "C", "D", "E");
Map<String, String> map = Maps.newHashMap();
map.put("A", "A for Alcohol");
map.put("B", "B for Boycott");
map.put("C", "C for Combine");
List<String> transformedList = FluentIterable.from(list)
.transform(Functions.forMap(map, "Not Defined")).toList();
List<String> expectedList = Lists.newArrayList("A for Alcohol",
"B for Boycott", "C for Combine", "Not Defined", "Not Defined");
assertEquals(expectedList, transformedList);
}
source code:
public final <T> FluentIterable<T> transform(Function<? super E, T> function) {
return from(Iterables.transform(iterable, function));
}
public static <F, T> Iterable<T> transform(final Iterable<F> fromIterable,
final Function<? super F, ? extends T> function) {
return new FluentIterable<T>() {
@Override
public Iterator<T> iterator() {
return Iterators.transform(fromIterable.iterator(), function);
}
};
}
public static <F, T> Iterator<T> transform(final Iterator<F> fromIterator,
final Function<? super F, ? extends T> function) {
return new TransformedIterator<F, T>(fromIterator) {
@Override
T transform(F from) {
return function.apply(from);
}
};
}
2. Lists
1> Lists.newXXXList(): Factory method to simplify the creation of Generic Collection
public static <E> ArrayList<E> newArrayList(E... elements) {
int capacity = computeArrayListCapacity(elements.length);
ArrayList<E> list = new ArrayList<E>(capacity);
Collections.addAll(list, elements);
return list;
}
2> Lists.partition()
@Test
public void partitionTest() {
List<String> strList = Lists.newArrayList("A", "B", "C", "D");
List<List<String>> partedStrList = Lists.partition(strList, 2);
assertEquals(2, partedStrList.size());
assertEquals(2, partedStrList.get(0).size());
assertEquals(2, partedStrList.get(1).size());
partedStrList = Lists.partition(strList, 3);
assertEquals(2, partedStrList.size());
assertEquals(3, partedStrList.get(0).size());
assertEquals(1, partedStrList.get(1).size());
}
source code:
public static <T> List<List<T>> partition(List<T> list, int size) {
return (list instanceof RandomAccess)
? new RandomAccessPartition<T>(list, size)
: new Partition<T>(list, size);
}
private static class Partition<T> extends AbstractList<List<T>> {
final List<T> list;
final int size;
Partition(List<T> list, int size) {
this.list = list;
this.size = size;
}
@Override public List<T> get(int index) {
int start = index * size;
int end = Math.min(start + size, list.size());
return list.subList(start, end);
}
@Override public int size() {
return IntMath.divide(list.size(), size, RoundingMode.CEILING);
}
@Override public boolean isEmpty() {
return list.isEmpty();
}
}
3. Sets
1> Sets.newXXXSet(): Factory method to simplify the creation of Generic Collection
public static <E> HashSet<E> newHashSet(E... elements) {
HashSet<E> set = newHashSetWithExpectedSize(elements.length);
Collections.addAll(set, elements);
return set;
}
2> Sets.difference and Sets.intersection and Sets.union
@Test
public void partitionTest() {
Set<String> strSet1 = Sets.newHashSet("A", "B", "C", "D");
Set<String> strSet2 = Sets.newHashSet("D", "M", "O", "G");
Set<String> diffSet = Sets.difference(strSet1, strSet2);
assertEquals(3, diffSet.size());
Set<String> interSet = Sets.intersection(strSet1, strSet2);
assertEquals(1, interSet.size());
Set<String> unionSet = Sets.union(strSet1, strSet2);
assertEquals(7, unionSet.size());
}
source code:
public static <E> SetView<E> difference(
final Set<E> set1, final Set<?> set2) {
final Predicate<Object> notInSet2 = Predicates.not(Predicates.in(set2));
return new SetView<E>() {
@Override public Iterator<E> iterator() {
return Iterators.filter(set1.iterator(), notInSet2);
}
@Override public int size() {
return Iterators.size(iterator());
}
@Override public boolean isEmpty() {
return set2.containsAll(set1);
}
@Override public boolean contains(Object element) {
return set1.contains(element) && !set2.contains(element);
}
};
}
public static <E> SetView<E> intersection(
final Set<E> set1, final Set<?> set2) {
final Predicate<Object> inSet2 = Predicates.in(set2);
return new SetView<E>() {
@Override public Iterator<E> iterator() {
return Iterators.filter(set1.iterator(), inSet2);
}
@Override public int size() {
return Iterators.size(iterator());
}
@Override public boolean isEmpty() {
return !iterator().hasNext();
}
@Override public boolean contains(Object object) {
return set1.contains(object) && set2.contains(object);
}
@Override public boolean containsAll(Collection<?> collection) {
return set1.containsAll(collection)
&& set2.containsAll(collection);
}
};
}
public static <E> SetView<E> union(
final Set<? extends E> set1, final Set<? extends E> set2) {
final Set<? extends E> set2minus1 = difference(set2, set1);
return new SetView<E>() {
@Override public int size() {
return set1.size() + set2minus1.size();
}
@Override public boolean isEmpty() {
return set1.isEmpty() && set2.isEmpty();
}
@Override public Iterator<E> iterator() {
return Iterators.unmodifiableIterator(
Iterators.concat(set1.iterator(), set2minus1.iterator()));
}
@Override public boolean contains(Object object) {
return set1.contains(object) || set2.contains(object);
}
@Override public <S extends Set<E>> S copyInto(S set) {
set.addAll(set1);
set.addAll(set2);
return set;
}
@Override public ImmutableSet<E> immutableCopy() {
return new ImmutableSet.Builder<E>()
.addAll(set1).addAll(set2).build();
}
};
}
4. Maps
1> Maps.newXXXMap()
2> Maps.uniqueIndex(Iterator<V>, Function<V, K>)
@Test
public void traditionalTest() {
List<Person> personList = Lists.newArrayList(new Person("Davy", "Male",
24), new Person("Calypso", "Female", 22));
Map<String, Person> map = Maps.newHashMap();
for (Person p : personList) {
map.put(p.getName(), p);
}
assertEquals(2, map.size());
}
@Test
public void uniqueIndexTest() {
List<Person> personList = Lists.newArrayList(new Person("Davy", "Male",
24), new Person("Calypso", "Female", 22));
Map<String, Person> map = Maps.uniqueIndex(personList,
new Function<Person, String>() {
public String apply(Person input) {
return input.getName();
}
});
assertEquals(2, map.size());
}
class Person {
String name;
String gender;
int age;
public Person(String name, String gender, int age) {
super();
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person [name=" + name + ", gender=" + gender + ", age="
+ age + "]";
}
}
source code:
public static <K, V> ImmutableMap<K, V> uniqueIndex(
Iterable<V> values, Function<? super V, K> keyFunction) {
return uniqueIndex(values.iterator(), keyFunction);
}
public static <K, V> ImmutableMap<K, V> uniqueIndex(
Iterator<V> values, Function<? super V, K> keyFunction) {
ImmutableMap.Builder<K, V> builder = ImmutableMap.builder();
while (values.hasNext()) {
V value = values.next();
builder.put(keyFunction.apply(value), value);
}
return builder.build();
}
3> Maps.asMap(Set<K>, Function<K, V>)
The Maps.asMap method takes a set of objects to be used as keys, and Function is applied to each key to generate the value for entry into a map instance.
@Test
public void asMapTest() {
Set<String> nameSet = Sets.newHashSet("Davy", "Calypso");
Map<String, Person> map = Maps.asMap(nameSet,
new Function<String, Person>() {
public Person apply(String name) {
return new Person(name, "UNKNOWN", 0);
}
});
assertEquals(2, map.size());
}
4> Maps.transformEntries
@Test
public void transformEntriesTest() {
Map<String, Person> personMap = Maps.newHashMap();
personMap.put("Davy", new Person("Davy", "Male", 24));
personMap.put("Calypso", new Person("Calypso", "Female", 22));
Map<String, People> peopleMap = Maps.transformEntries(personMap,
new EntryTransformer<String, Person, People>() {
public People transformEntry(String key, Person value) {
return new People(value.getName(), value.getGender());
}
});
assertEquals(2, peopleMap.size());
}
5. Multimaps
1> ArrayListMultimap = HashMap<K, ArrayList<V>>
2> HashMultimap = HashMap<K, HashSet<V>>
3> LinkedHashMultimap = LinkedHashMap<K, LinkedHashSet<V>>
4> TreeMultimap = TreeMap<K, TreeSet<V>>
5> LinkedListMultimap = HashMap<K, LinkedList<V>>
Q: Why the realization of LinkedListMultimap is different from others' ?
Why don't they simply extends AbstractListMultimap and then generalize method createCollection() ?
6. Exercises:
1> Filter Lists
@Test
public void filterListTest() {
List<String> list = Lists.newArrayList("A", "B", "C", "D");
List<String> filteredList = FluentIterable.from(list)
.filter(new Predicate<String>() {
@Override
public boolean apply(String input) {
return "A".compareTo(input) < 0;
}
}).toList();
List<String> expectedList = Lists.newArrayList("B", "C", "D");
assertEquals(expectedList, filteredList);
List<Object> objList = Lists.<Object> newArrayList("A", "B",
new Double(1.2), new Double(1.0));
List<Double> doubleList = FluentIterable.from(objList)
.filter(Double.class).toList();
List<Double> expDoubleList = Lists.newArrayList(new Double(1.2),
new Double(1.0));
assertEquals(expDoubleList, doubleList);
}
2> Transform Lists
@Test
public void transformListTest() {
List<String> list = Lists.newArrayList("A", "B", "C", "D");
List<String> transformedList = FluentIterable.from(list)
.transform(new Function<String, String>() {
@Override
public String apply(String input) {
return input.concat("-Postfix");
}
}).toList();
List<String> expectedList = Lists.newArrayList("A-Postfix",
"B-Postfix", "C-Postfix", "D-Postfix");
assertEquals(expectedList, transformedList);
transformedList = FluentIterable.from(list)
.filter(new Predicate<String>() {
@Override
public boolean apply(String input) {
return !"A".equals(input);
}
}).transformAndConcat(new Function<String, Iterable<String>>() {
@Override
public Iterable<String> apply(String input) {
return Lists.newArrayList(input,
input.concat("-Postfix"));
}
}).toList();
expectedList = Lists.newArrayList("B", "B-Postfix", "C", "C-Postfix",
"D", "D-Postfix");
assertEquals(expectedList, transformedList);
}
3> Filter Map
@Test
public void filterMapTest() {
Map<String, String> map = Maps.newHashMap();
map.put("A", "A for Alcohol");
map.put("B", "B for Boycott");
map.put("C", "C for Cowboy");
map.put("D", "D for Dodge");
Map<String, String> filteredMap = Maps.filterEntries(map,
new Predicate<Map.Entry<String, String>>() {
@Override
public boolean apply(Entry<String, String> input) {
return "A".equals(input.getKey())
&& "A for Alcohol".equals(input.getValue());
}
});
Map<String, String> expectedMap = Maps.newHashMap();
expectedMap.put("A", "A for Alcohol");
assertEquals(expectedMap, filteredMap);
filteredMap = Maps.filterKeys(map, new Predicate<String>() {
@Override
public boolean apply(String input) {
return input.equals("B");
}
});
expectedMap = Maps.newHashMap();
expectedMap.put("B", "B for Boycott");
assertEquals(expectedMap, filteredMap);
filteredMap = Maps.filterValues(map, new Predicate<String>() {
@Override
public boolean apply(String input) {
return input.equals("C for Cowboy");
}
});
expectedMap = Maps.newHashMap();
expectedMap.put("C", "C for Cowboy");
assertEquals(expectedMap, filteredMap);
}
4> Transform Map
@Test
public void transformMapTest() {
Map<String, String> map = Maps.newHashMap();
map.put("A", "A for Alcohol");
map.put("B", "B for Boycott");
map.put("C", "C for Cowboy");
map.put("D", "D for Dodge");
Map<String, Person> transformedMap = Maps.transformValues(map,
new Function<String, Person>() {
@Override
public Person apply(String input) {
return new Person(input);
}
});
Map<String, Person> expectedMap = Maps.newHashMap();
expectedMap.put("A", new Person("A for Alcohol"));
expectedMap.put("B", new Person("B for Boycott"));
expectedMap.put("C", new Person("C for Cowboy"));
expectedMap.put("D", new Person("D for Dodge"));
assertEquals(expectedMap, transformedMap);
transformedMap = Maps.transformEntries(map,
new EntryTransformer<String, String, Person>() {
@Override
public Person transformEntry(String key, String value) {
return "A".equals(key) ? new Person(key) : new Person(
value);
}
});
expectedMap = Maps.newHashMap();
expectedMap.put("A", new Person("A"));
expectedMap.put("B", new Person("B for Boycott"));
expectedMap.put("C", new Person("C for Cowboy"));
expectedMap.put("D", new Person("D for Dodge"));
assertEquals(expectedMap, transformedMap);
}
5> Add element into ImmutableList/ImmutableSet/ImmutableMap
@Test
public void test() {
List<String> list = Lists.newArrayList("A", "B", "C", "D", "E");
List<String> filteredList = FluentIterable.from(list)
.filter(new Predicate<String>() {
@Override
public boolean apply(String input) {
return "A".equals(input) || "D".equals(input);
}
}).toList();
filteredList = ImmutableList.<String> builder().addAll(filteredList)
.add("F").build();
assertEquals(Lists.newArrayList("A", "D", "F"), filteredList);
}
@Test
public void test2() {
Function<String, String> postfixFunction = new Function<String, String>() {
@Override
public String apply(String input) {
return input.concat("-Postfix");
}
};
Map<String, String> map = Maps.toMap(
Lists.newArrayList("A", "B", "C", "D"), postfixFunction);
Map<String, String> buildedMap = ImmutableMap
.<String, String> builder().putAll(map).put("E", "E-Postfix")
.build();
assertEquals(Maps.toMap(Lists.newArrayList("A", "B", "C", "D", "E"),
postfixFunction), buildedMap);
}
Q: Why don't they return MutableCollection/MutableMap instead of ImmutableCollction/ImmutableMap for handy future manipulation(add/remove)?
Reference Links:
1) Getting Started with Google Guava -Bill Bejeck
2) http://stackoverflow.com/questions/12937938/adding-and-removing-items-to-a-guava-immutablelist