Learning through exercises
- space: next page
- down arrow: next page in the current section
- right arrow: next section
- What is an iteration pattern?
- Sort is one example.
- We want to sort a list of people by last name, first name.
- Which method in the JCF can we use?
Person john = new Person("John", "Smith");
Person jane = new Person("Jane", "Smith");
Person z = new Person("Z.", "Jones");
List<Person> people = new ArrayList<Person>();
people.add(john);
people.add(jane);
people.add(z);
public static void java.util.Collections.sort(
List<T> list, Comparator<? super T> c)
- Sorts the specified list according to the order induced by the specified comparator. All elements in the list must be mutually comparable.
Collections.sort(people, (Person o1, Person o2) -> {
int lastName = o1.getLastName().compareTo(o2.getLastName());
if (lastName != 0) {
return lastName;
}
return o1.getFirstName().compareTo(o2.getFirstName());
});
public static void java.util.Collections.sort(
List<T> list, Comparator<? super T> c)
- Sorts the specified list according to the order induced by the specified comparator. All elements in the list must be mutually comparable.
- Does anything bother you about
Collections.sort()
?
public static void java.util.Collections.sort(
List<T> list, Comparator<? super T> c)
- Sorts the specified list according to the order induced by the specified comparator. All elements in the list must be mutually comparable.
- Does anything bother you about
Collections.sort()
?
- Why isn’t
sort()
a method on every List?
Collections.sort(list, comparator);
vs.
list.sort(comparator);
public static void java.util.Collections.sort(
List<T> list, Comparator<? super T> c)
- Sorts the specified list according to the order induced by the specified comparator. All elements in the list must be mutually comparable.
- Does anything bother you about
Collections.sort()
?
- Where are all the iteration patterns?
- java.util.Collections provides methods for
sort()
,min()
,max()
and just a few others. - The most common iteration patterns are missing:
- Collect a list of each person’s address.
- Select only those people whose age is 18 or higher.
- We want the methods
sort()
,min()
,max()
,collect()
,select()
, etc. on every collection. - How can we accomplish this in code?
- We want the methods
sort()
,min()
,max()
,collect()
,select()
, etc. on every collection. - How can we accomplish this in code?
public interface MutableList<T> extends List<T>
{
MutableList<T> sortThis(Comparator<? super T> comparator);
<V> MutableList<V> collect(Function<? super T, ? extends V> function);
MutableList<T> select(Predicate<? super T> predicate);
...
}
- Collect (aka map or transform).
- Returns a new collection where each element has been transformed
- e.g. collect each person’s address.
- Function is the type that takes an object and returns an object of a
different type
- aka Transformer
List<Person> people = ...
List<Address> addresses = new ArrayList<Address>();
for (Person person : people)
{
addresses.add(person.getAddress());
}
- Function is the type that takes an object and returns an object of a different type
MutableList<Person> people = ...
MutableList<Address> addresses = people.collect(
new Function<Person, Address>()
{
public Address valueOf(Person person)
{
return person.getAddress();
}
});
- Function is the type that takes an object and returns an object of a different type
MutableList<Person> people = ...
// Lambda
MutableList<Address> addresses =
people.collect(person -> person.getAddress());
// Method Reference
MutableList<Address> addresses =
people.collect(Person::getAddress);
- The loop moves in to the implementation of
collect()
. - Let’s look at a realistic implementation of
collect()
forFastList
.
public <V> MutableList<V> collect(Function<? super T, ? extends V> function)
{
MutableList<V> result = FastList.newList(this.size);
for (int i = 0; i < this.size; i++) {
result.add(function.valueOf(this.items[i]));
}
return result;
}
- Select (aka filter).
- Returns the elements of a collection that satisfy some condition
- e.g. select only those people whose age is 18 or higher.
- Predicate is the type that takes an object and returns a boolean.
List<Person> people = ...
List<Person> adults = new ArrayList<>();
for (Person person : people)
{
if (person.getAge() >= 18)
{
adults.add(person);
}
}
- Select (aka filter).
- Returns the elements of a collection that satisfy some condition
- e.g. select only those people whose age is 18 or higher.
- Predicate is the type that takes an object and returns a boolean.
MutableList<Person> people = ...
MutableList<Person> adults = people.select(
new Predicate<Person>()
{
public boolean accept(Person each)
{
return each.getAge() >= 18;
}
});
- Select (aka filter).
- Returns the elements of a collection that satisfy some condition
- e.g. select only those people whose age is 18 or higher.
- Predicate is the type that takes an object and returns a boolean.
MutableList<Person> people = ...
MutableList<Person> adults =
people.select(each -> each.getAge() >= 18);
- Collect returns a new collection where each element has been transformed.
- Select returns the elements of a collection that satisfy some condition.
- sortThis() takes a Comparator, which is a strategy interface.
- collect() takes a Function.
- select() takes a Predicate.
- Don’t get hung up on these names because the IDE will remind you.
- LineItems, Companies, Orders, Customers, and Suppliers.
- All tests extend CompanyDomainForKata, which sets up some customers, suppliers and orders for a company.
- Data is available using getters on
this.company
- For example
this.company.getCustomers()
;
- For example
- Most changes will be under
src/test
. - Some changes will be under
src/main
. - Feel free to refactor the domain under
src/main
. - Pay close attention to the Javadoc for instructions and hints.
- Use the IDE support for Javadoc
@links
.
-
Find
Exercise1Test
; it has assertion failures if you run it as a test. -
Figure out how to get the tests to pass using what you have seen so far.
-
Should take about 15 minutes.
-
right: Exercise 1 solutions
- Eclipse Collections distribution includes
eclipse-collections-testutils.jar
.- Includes helpful utility for writing unit tests.
- Collection-specific.
- Implemented as an extension of JUnit.
- Most important class is called
Verify
.
Example from the previous solution
Verify.assertSize(2, customersFromLondon);
Instead of
Assertions.assertEquals(2, customersFromLondon.size());
- Several other self-explanatory examples:
Verify.assertEmpty(Lists.mutable.empty());
Verify.assertNotEmpty(Lists.mutable.with(1));
Verify.assertContains(1, Lists.mutable.with(1));
- It’s possible to go too far.
- The first form is more verbose.
- The second form asserts more because of the contract of equals().
Verify.assertSize(3, list);
Verify.assertContainsAll(list, 1, 2, 3);
Assertions.assertEquals(Lists.mutable.with(1, 2, 3), list);
- Increased readability.
- Reduced duplication – less to test.
- Simplified iteration.
- Optimized by type for memory and speed.
- Before Java 8 there were no lambdas or closures
- Verbose anonymous inner class syntax
- Object-oriented programming is a paradigm that groups data with the methods that predicates on the data.
- Functional programming is a paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data.
- Possible to merge the two paradigms and wind up with very readable code.
- Where they sometimes clash is mutable state.
x = 5
x = 6
- In Java this is OK.
- The value of x changes over time.
- In pure math this makes no sense.
- The value of x is 5, it cannot be reassigned.
- In imperative programming, time is important.
- The truth of conditions changes with the state of the program.
- This can decrease readability.
- can be difficult to reason about.
- can be difficult to test.
- Think architecture that is concurrent and based on asynchronous callbacks.
List<Person> people = ...
List<Address> addresses = new ArrayList<Address>();
// Not true yet! It’s an empty list, not a list of addresses!
for (Person person : people)
{
addresses.add(person.getAddress());
}
fibonacci()
function returns the same output for the same input.- We can tell it doesn’t use mutable state; it can be static.
- Iterators are not functional.
next()
takes no input, yet returns different values over time.- It obviously uses internal state.
for (int i = 0; i < 10; i++)
{
System.out.println(this.fibonacci(i));
}
// vs.
for (int i = 0; i < 10; i++)
{
System.out.println(this.fibonacciIterator.next());
}
- Iteration patterns are methods on our collections.
- Simply naming the patterns enhances readability:
people.collect(...)
- These methods take a parameter which is a bit of code:
- how to collect or select.
- The thing inside the parentheses is now less readable:
- represented as an anonymous inner class.
- Ideally, Java would have native support for anonymous functions (aka lambda expressions, erroneously aka closures).
- The syntax below works with Java 8+.
MutableList<Person> people = ...;
// Lambda
MutableList<Address> addresses =
people.collect(person -> person.getAddress());
// Method Reference
MutableList<Address> addresses =
people.collect(Person::getAddress);
- We have other tricks for dealing with boilerplate code.
- Eclipse Collections provides several “lambda factories,” like
Predicates
.
MutableList<Integer> mutableList =
Lists.mutable.with(25, 50, 75, 100);
MutableList<Integer> selected =
mutableList.select(Predicates.greaterThan(50));
Predicates
also lets you create common Predicates from Functions.- Combines well with the previous tip.
MutableList<Person> people = ...;
MutableList<Person> theSmiths = people.select(
Predicates.attributeEqual(Person::getLastName, "Smith"));
MutableList
extends List.FastList
is a drop-in replacement forArrayList
.FastList
could extend ArrayList, but we chose not to.- Iteration patterns were pulled up higher into
RichIterable
.
MutableSet
extendsSet
.UnifiedSet
is a drop in replacement for HashSet.- Everything about Lists is analogous for Sets (and Bags).
- This is why iteration patterns were pulled up higher into
RichIterable
min()
for example is identical across collection types.
MutableMap
extendsMap
.UnifiedMap
is a drop in replacement forHashMap
- Why would we prefer immutable data structures?
- Why wouldn’t we prefer immutable data structures?
- Easier to reason about because no complex state changes over time.
- Can pass them around without making defensive copies.
- Safe for concurrent access and as hash-table keys.
- If an object is mutated after it is placed into a
HashSet
, that object may not be found the next time you look.
- Can require large object graph to be copied where otherwise an update could be done in place.
- It is common for libraries to provide mutable alternatives to immutable classes.
- For example,
StringBuilder
is a mutable alternative toString
- For example,
toList()
,toSortedList()
,toSet()
,toSortedSet()
,toBag()
- Return mutable copies.
- Return new copy even when called on a collection of the same type.
MutableList<Integer> list = Lists.mutable.with(3, 1, 2, 2, 1);
MutableList<Integer> noDupes = list.toSet().toSortedList();
Assertions.assertEquals( Lists.mutable.with(1, 2, 3), noDupes);
ImmutableCollection
interface does not extendjava.util.Collection
:- No mutating methods.
- Mutating requires a new copy.
- Eclipse Collections also has “memory-efficient” collections but they are largely
superseded by
ImmutableCollections
.
ImmutableList<Integer> immutableList =
Lists.mutable.with(1, 2, 3).toImmutable();
ImmutableList<Integer> immutableList2 =
Lists.immutable.of(1, 2, 3);
Verify.assertInstanceOf(ImmutableTripletonList.class, immutableList);
- Should a mutable list equal an immutable list?
MutableList<Integer> mutable =
Lists.mutable.with(1, 2, 3);
ImmutableList<Integer> immutable =
Lists.immutable.with(1, 2, 3);
mutable.equals(immutable);
- Should a list and a set be equal?
MutableList<Integer> list =
Lists.mutable.with(1, 2, 3);
MutableSet<Integer> set =
Sets.mutable.with(1, 2, 3);
list.equals(set);
- Should a mutable list equal an immutable list?
- A list is a
List
because it allows duplicates and preserves order, so yes.
MutableList<Integer> mutable =
Lists.mutable.with(1, 2, 3);
ImmutableList<Integer> immutable =
Lists.immutable.with(1, 2, 3);
mutable.equals(immutable);
Here’s the implementation of ArrayList.equals()
, really on AbstractList
:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof List))
return false;
ListIterator<E> e1 = this.listIterator();
ListIterator e2 = ((List) o).listIterator();
while (e1.hasNext() && e2.hasNext()) {
E o1 = e1.next();
Object o2 = e2.next();
if (!(o1 == null ? o2 == null : o1.equals(o2))) {
return false;
}
}
return !(e1.hasNext() || e2.hasNext());
}
- Implementations are Collections in order to satisfy the existing
contract of equals() on
List
andSet
. - Can be cast to
Collection
. - Brings back the mutating methods.
- Brings back the
UnsupportedOperationExceptions
. - Convenient for interop.
List<Integer> list = immutableList.castToList();
Verify.assertThrows(
UnsupportedOperationException.class,
() -> list.add(4););
- Select returns the items that satisfy the
Predicate
. - Reject returns the items that do not satisfy the
Predicate
. - Count returns the # of items that satisfy the
Predicate
.
- Detect finds the first item that satisfies the
Predicate
. - AnySatisfy returns true if any item satisfies the
Predicate
. - AllSatisfy returns true if all items satisfy the
Predicate
. - NoneSatisfy returns true if no items satisfy the
Predicate
.
- Fix
Exercise2Test
. - Use the other iteration patterns that take a
Predicate
. - Should take about 20 minutes.
Verify
includes additional assertions based on iteration patterns.
MutableList<Integer> list =
Lists.mutable.with(1, 2, 0, -1);
Verify.assertAllSatisfy(list, IntegerPredicates.isPositive());
junit.framework.AssertionFailedError: The following
items failed to satisfy the condition <[0, -1]>
- Let's say we have 3 people: mrSmith, mrsSmith, mrJones.
- The first two share the same address.
- What will get printed by the following code?
MutableSet<Person> people =
Sets.mutable.with(mrSmith, mrsSmith, mrJones);
int numAddresses =
people.collect(addressFunction).size();
System.out.println(numAddresses);
select()
,collect()
, etc. are defined with covariant return types:MutableCollection.collect()
returns aMutableCollection
MutableList.collect()
returns aMutableList
MutableSet.collect()
returns aMutableSet
- Alternate forms take target collections.
MutableSet<Person> people =
Sets.mutable.with(mrSmith, mrsSmith, mrJones);
int numAddresses =
people.collect(addressFunction).size();
System.out.println(numAddresses);
select()
,collect()
, etc. are defined with covariant return types:MutableCollection.collect()
returns aMutableCollection
MutableList.collect()
returns aMutableList
MutableSet.collect()
returns aMutableSet
- Alternate forms take target collections.
MutableSet<Person> people = Sets.mutable.with(mrSmith, mrsSmith, mrJones);
MutableList<Address> targetList = Lists.mutable.empty();
int numAddresses = people.collect(addressFunction, targetList).size();
System.out.println(numAddresses);
flatCollect()
is a special case ofcollect()
.- With
collect()
, when theFunction
returns a collection, the result is a collection of collections.
MutableList<Person> people = ...;
Function<Person, MutableList<Address>> addressesFunction =
person -> person.getAddresses();
MutableList<MutableList<Address>> addresses =
people.collect(addressesFunction);
flatCollect()
outputs a single “flattened” collection instead of a collection of collections.- The signature of
flatCollect()
is similar tocollect()
, except that theFunction
parameter must map to anIterable
type.
flatCollect(Function<? super T, ? extends Iterable<V>> function);
MutableList<Person> people = ...;
MutableList<Address> addresses = people.flatCollect(person -> person.getAddresses());
MutableList<MutableList<Address>> addresses =
people.collect(Person::getAddresses);
MutableList<Address> addresses =
people.flatCollect(Person::getAddresses);
- Fix
Exercise3Test
. - Should take about 20 minutes
- Using methods on the interfaces is the preferred, object-oriented approach.
MutableList<Integer> mutableList = ...;
MutableList<Integer> selected =
mutableList.select(Predicates.greaterThan(50));
- Using methods on the interfaces is the preferred, object-oriented
approach.
- But it’s not always feasible.
- Static utility classes like
Iterate
,ListIterate
, etc. provide interoperability with JCF.
List<Integer> list = ...;
MutableList<Integer> selected =
ListIterate.select(list, Predicates.greaterThan(50));
- Using methods on the interfaces is the preferred, object-oriented
approach.
- But it’s not always feasible.
- Static utility classes like
Iterate
,ListIterate
, etc. provide interoperability with JCF. - Static utility classes like
ArrayIterate
andStringIterate
show that iteration patterns work on other types as well.
Integer[] array = ...;
MutableList<Integer> selected =
ArrayIterate.select(array, Predicates.greaterThan(50));
String string = StringIterate.select( "1a2a3", CharPredicate.IS_DIGIT);
Assertions.assertEquals("123", string);
- Static utility for parallel iteration.
- Hides complexity of writing concurrent code.
- Looks like the serial case.
- Notice the lack of locks, threads, pools, executors, etc.
- Order is preserved in the result.
List<Integer> list = ...;
Collection<Integer> selected =
ParallelIterate.select(list, Predicates.greaterThan(50));
- Iterate (Iterable)
- ListIterate (List)
- ArrayListIterate (ArrayList)
- RandomAccessListIterate (List & RandomAccess)
- MapIterate (Map)
- LazyIterate (Iterable)
- ArrayIterate (T[])
- StringIterate (String)
- ParallelIterate (Iterable)
- ParallelMapIterate (Map)
- ParallelArrayIterate (T[])
- Let’s look at the full implementation of Collections.sort()
- What’s wrong with this code?
public static <T> void sort(List<T> list, Comparator<? super T> c) {
Object[] array = list.toArray();
Arrays.sort(array, (Comparator) c);
ListIterator iterator = list.listIterator();
for (int i = 0; i < array.length; i++) {
iterator.next();
iterator.set(array[i]);
}
}
- This code is fine for LinkedList.
- The code is suboptimal for ArrayList (and FastList).
- Unnecessary array copy.
- Unnecessary iterator created.
- Unnecessary calls to set().
public static <T> void sort(List<T> list, Comparator<? super T> c) {
Object[] array = list.toArray();
Arrays.sort(array, (Comparator) c);
ListIterator iterator = list.listIterator();
for (int i = 0; i < array.length; i++) {
iterator.next();
iterator.set(array[i]);
}
}
- FastList has a simpler and more optimal implementation.
- Objects group logic with the data it operates on.
- This logic makes sense for an array-backed structure.
public FastList<T> sortThis(Comparator<? super T> comparator)
{
Arrays.sort(this.items, 0, this.size, comparator);
return this;
}
- Fix the five methods in
Exercise4Test
. - Solve them using the static utility classes.
- Exercise 4 should take about 25 minutes.
List<Integer> integers = new ArrayList<Integer>();
integers.add(1);
integers.add(2);
integers.add(3);
List<Integer> integers = new FastList<Integer>();
integers.add(1);
integers.add(2);
integers.add(3);
FastList
is a drop-in replacement forArrayList
.- More memory efficient.
- Opens up the refactoring opportunities coming up next.
List<Integer> integers = new FastList<Integer>();
integers.add(1);
integers.add(2);
integers.add(3);
List<Integer> integers = Lists.mutable.empty();
integers.add(1);
integers.add(2);
integers.add(3);
- The static factory methods can infer generic types.
List<Integer> integers = Lists.mutable.empty();
integers.add(1);
integers.add(2);
integers.add(3);
List<Integer> integers = Lists.mutable.with(1, 2, 3);
- Varargs support; any number of arguments.
- Never mutated; so you could make it unmodifiable:
Lists.mutable.with(1, 2, 3).asUnmodifiable();
- There is also a form that takes another iterable:
Lists.mutable.withAll(list);
List<Integer> integers = Lists.mutable.with(1, 2, 3);
MutableList<Integer> integers = Lists.mutable.with(1, 2, 3);
MutableList
is a drop in replacement forList
- Methods available on interface instead of utility classes.
- Better type information:
Iterate.select()
returns aCollection
, butMutableList.select()
returns aMutableList
- These refactorings are analogous for UnifiedSet and UnifiedMap
MutableSet<Integer> set =
Sets.mutable.with(1, 2, 3);
MutableMap<Integer, String> map =
Maps.mutable.with(
1, "1",
2, "2",
3, "3");
- Fix
Exercise5Test
. - Two of the ones you just solved.
- This time, don’t use static utility, refactor the domain instead.
- Exercise 5 should take about 10 minutes
- Book by Michael C. Feathers
- “The primary purpose of a compiler is to translate source code into some other form, but in statically typed languages, you can do much more with a compiler. You can take advantage of its type checking and use it to identify changes you need to make. I call this practice leaning on the compiler.”
- Lean on the IDE and the Compiler
select
returns the items that satisfy aPredicate
.reject
returns the items that do not satisfy aPredicate
.count
returns the number of items that satisfy thePredicate
.collect
transforms the items using aFunction
.flatCollect
transforms & flattens using aFunction
.detect
finds the first item that satisfies aPredicate
.anySatisfy
returns true if any item satisfies aPredicate
.allSatisfy
returns true if all items satisfy aPredicate
.noneSatisfy
returns true if none satisfy aPredicate
.
forEach
executes theProcedure
on each item.injectInto
injects an accumulator and executes aFunction2
over each item passing the accumulated result.chunk
splits the collection into chunks of the given size.zip
joins two collections into a collection of Pairs.makeString
liketoString()
, with a customizable separator, start, and end string.appendString
likemakeString()
, but uses the specifiedAppendable
.
toList
/toSet
converts the collection to a new copy of the correct type.toSortedList
returns a new list sorted according to some Comparator.sortThis
sorts the list in place (mutating method) according to someComparator
.min
/max
returns the min / max element of a collection according to someComparator
.
makeString()
returns aString
representation, similar totoString()
.- Three forms:
makeString(start, separator, end)
makeString(separator)
defaults start and end to empty stringsmakeString()
defaults the separator to ", " (comma and space)
MutableList<Integer> list = Lists.mutable.with(1, 2, 3);
assertEquals("[1/2/3]", list.makeString("[", "/", "]"));
assertEquals("1/2/3", list.makeString("/"));
assertEquals("1, 2, 3", list.makeString());
assertEquals(list.toString(), list.makeString("[", ", ", "]"));
appendString()
is similar tomakeString()
, but it appends to anAppendable
and is void.- Common Appendables are
StringBuilder
,PrintStream
,BufferedWriter
, etc. - Same three forms, with additional first argument, the
Appendable
.
- Common Appendables are
MutableList<Integer> list = Lists.mutable.with(1, 2, 3);
Appendable appendable = new StringBuilder();
list.appendString(appendable, "[", "/", "]");
assertEquals("[1/2/3]", appendable.toString());
chunk()
splits aRichIterable
into fixed size pieces.- Final chunk will be smaller if the size doesn't divide evenly.
MutableList<Integer> list =
Lists.mutable.with(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
RichIterable<RichIterable<Integer>> chunks =
list.chunk(4);
System.out.println(chunks);
// prints [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
zip()
takes a secondRichIterable
and pairs up all the elements.- If one of the two
RichIterable
s is longer, its extra elements are ignored.
MutableList<String> list1 =
Lists.mutable.with("One", "Two", "Three", "Truncated");
MutableList<String> list2 =
Lists.mutable.with("Four", "Five", "Six");
MutableList<Pair<String, String>> pairs = list1.zip(list2);
System.out.println(pairs);
// prints [One:Four, Two:Five, Three:Six]
- A special case is when you want to
zip()
the elements in a collection with their index positions. - You could accomplish that with
zip()
and Interval, or usezipWithIndex()
.
MutableList<String> list =
Lists.mutable.with("One", "Two", "Three");
MutableList<Pair<String, Integer>> pairs =
list.zipWithIndex();
System.out.println(pairs);
// prints [One:0, Two:1, Three:2]
min()
andmax()
take aComparator
and return the extreme elements.- Overloads don’t take a Comparator.
- If the elements are not
Comparable
, you get aClassCastException
. - What if you don’t want the maximum age, but instead the oldest Person?
MutableList<Person> people = ...;
Integer maxAge = people.collect(Person.TO_AGE).max();
min()
andmax()
take aComparator
and return the extreme elements.- Overloads don’t take a
Comparator
.- If the elements are not
Comparable
, you get aClassCastException
.
- If the elements are not
- What if you don’t want the maximum age, but instead the oldest Person?
- Use
minBy()
andmaxBy()
instead.
- Use
MutableList<Person> people = ...;
Integer maxAge = people.collect(Person.TO_AGE).max();
Person oldestPerson = people.maxBy(Person.TO_AGE);
toSortedList()
takes aComparator
and returns a new sorted list.- Overload doesn’t take a
Comparator
.- If the elements are not
Comparable
, you get aClassCastException
.
- If the elements are not
- What if you don’t want to sort the ages, but instead sort the people by
age?
- Use
sortThisBy()
instead
- Use
MutableList<Person> people = ...;
MutableList<Integer> sortedAges =
people.collect(Person.TO_AGE).toSortedList();
MutableList<Person> peopleSortedByAge =
people.toSortedListBy(Person.TO_AGE);
- Fix
Exercise6Test
. - Exercises use some of the iteration patterns you have just learned.
- Some use a combination of iteration patterns you have already seen.
- Exercise 6 should take about 20 minutes.
java.util.Stack
extendsVector
- How does
java.util.Stack
iterate?
java.util.Stack stack = new java.util.Stack();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(stack);
// Prints [1, 2, 3]
ArrayStack
is not a drop-in replacement forjava.util.Stack
.MutableStack
does not extendjava.util.Collection
.
push()
adds an item to the top of theMutableStack
MutableStack<Integer> stack =
Stacks.mutable.with(1, 2, 3);
System.out.println(stack);
// Prints [3, 2, 1]
stack.push(4);
System.out.println(stack);
// Prints [4, 3, 2, 1]
- The different ways to create a MutableStack
System.out.println(Stacks.mutable.with(1, 2, 3));
// Prints [3, 2, 1]
System.out.println(
Stacks.mutable.withReversed(1, 2, 3));
// Prints [1, 2, 3]
System.out.println(
Stacks.mutable.withAll(Lists.mutable.with(1, 2, 3)));
// Prints [3, 2, 1]
System.out.println(
Stacks.mutable.withAllReversed(
Lists.mutable.with(1, 2, 3)));
// Prints [1, 2, 3]
- Overloaded
pop()
methods:pop()
pop(int count)
pop(int count, R targetCollection)
MutableStack<Integer> stack1 = Stacks.mutable.with(1, 2, 3);
Assertions.assertEquals(
Lists.mutable.with(3, 2),
stack1.pop(2));
MutableStack<Integer> stack2 = Stacks.mutable.with(1, 3, 3);
Assertions.assertEquals(
Sets.mutable.with(3),
stack2.pop(2, Sets.mutable.empty()));
MutableStack<Integer> stack3 = Stacks.mutable.with(1, 2, 3);
Assertions.assertEquals(
Stacks.mutable.with(3, 2),
stack3.pop(2, Stacks.mutable.empty()));
MutableStack
has an overloadedpeek()
method that returns aListIterable
MutableStack<Integer> stack =
Stacks.mutable.with(1, 2, 3);
Assertions.assertEquals(
Integer.valueOf(3),
stack.peek());
Assertions.assertEquals(
Lists.mutable.with(3, 2),
stack.peek(2));
MutableStack
does not extendjava.util.List
(orCollection
)
java.util.Stack stack = new java.util.Stack();
stack.push(1);
stack.push(2);
stack.push(3);
Assertions.assertEquals(Lists.mutable.with(1, 2, 3), stack);
stack.add(2, 4);
Assertions.assertEquals(Lists.mutable.with(1, 2, 4, 3), stack);
stack.remove(1);
Assertions.assertEquals(Lists.mutable.with(1, 4, 3), stack);
Methods | Inherited From |
---|---|
select() , collect() , etc. |
RichIterable |
peek() |
Stack Iterable |
push() , pop() |
MutableStack |
StackIterable<Integer> stack =
Stacks.mutable.withReversed(1, 2, 3, 4, 5);
StackIterable<Integer> evens = stack.select(integer ->
{
System.out.print(integer + " ");
return integer % 2 == 0;
});
// Prints 1 2 3 4 5
Assertions.assertEquals(Stacks.mutable.withReversed(2, 4), evens);
- Useful when you would otherwise use
Map<K, Integer>
- For example, find the number of people who live in each state
MutableList<Person> people = ...;
MutableList<String> usStates = people.collect(US_STATE_FUNCTION);
MutableMap<String, Integer> stateCounts = Maps.mutable.empty();
...
int newYorkers = stateCounts.get("NY");
- Useful when you would otherwise use
Map<K, Integer>
- For example, find the number of people who live in each state.
- Lots of boilerplate code to deal with uninitialized counts.
MutableList<String> usStates = people.collect(US_STATE_FUNCTION);
MutableMap<String, Integer> stateCounts = Maps.mutable.empty();
for (String state : usStates) {
Integer count = stateCounts.get(state);
if (count == null) {
count = 0;
}
stateCounts.put(state, count + 1);
}
MutableList<String> usStates = people.collect(US_STATE_FUNCTION);
MutableMap<String, Integer> stateCounts = Maps.mutable.empty();
for (String state : usStates) {
Integer count = stateCounts.get(state);
if (count == null) {
count = 0;
}
stateCounts.put(state, count + 1);
}
MutableList<String> usStates = people.collect(US_STATE_FUNCTION);
MutableBag<String> stateCounts = Bags.mutable.empty();
for (String state : usStates) {
stateCounts.add(state);
}
int newYorkers = stateCounts.occurrencesOf("NY");
MutableList<String> usStates = people.collect(US_STATE_FUNCTION);
MutableBag<String> stateCounts = Bags.mutable.empty();
for (String state : usStates) {
stateCounts.add(state);
}
int newYorkers = stateCounts.occurrencesOf("NY");
MutableList<String> usStates = people.collect(US_STATE_FUNCTION);
MutableBag<String> stateCounts = usStates.toBag();
int newYorkers = stateCounts.occurrencesOf("NY");
MutableBag<String> stateCounts = people.countBy(US_STATE_FUNCTION);
int newYorkers = stateCounts.occurrencesOf("NY");
- Implemented as a map of key to count.
- Like a
List
, but unordered. - Like a
Set
, but allows duplicates.
MutableBag<String> stateCounts = people.countBy(US_STATE_FUNCTION);
int newYorkers = stateCounts.occurrencesOf("NY");
Methods | Inherited From |
---|---|
select() , collect() , etc. |
RichIterable |
add() , remove() , iterator() , etc. |
MutableCollection (java.util.Collection ) |
occurrencesOf() , forEachWithOccurrences() , toMapOfItemToCount() |
Bag |
addOccurrences() , removeOccurrences() |
MutableBag |
MutableBag<String> bag =
Bags.mutable.with("one", "two", "two", "three", "three", "three");
Assertions.assertEquals(3, bag.occurrencesOf("three"));
bag.add("one");
Assertions.assertEquals(2, bag.occurrencesOf("one"));
bag.addOccurrences("one", 4);
Assertions.assertEquals(6, bag.occurrencesOf("one"));
Multimap
is similar toMap
, but associates a key to multiple values.- Useful when you would otherwise use
Map<K, Collection<V>>
- For example, find which people live in each state.
MutableList<Person> people = ...;
MutableMap<String, MutableList<Person>> statesToPeople =
Maps.mutable.empty();
...
MutableList<Person> newYorkers = statesToPeople.get("NY");
Multimap
is similar toMap
, but associates a key to multiple values.- Useful when you would otherwise use
Map<K, Collection<V>>
- For example, find which people live in each state.
- Lots of boilerplate code to deal with uninitialized backing collections.
MutableMap<String, MutableList<Person>> statesToPeople =
Maps.mutable.empty();
for (Person person : people) {
String state = US_STATE_FUNCTION.valueOf(person);
MutableList<Person> peopleInState = statesToPeople.get(state);
if (peopleInState == null) {
peopleInState = Lists.mutable.empty();
statesToPeople.put(state, peopleInState);
}
peopleInState.add(person);
}
MutableList<Person> newYorkers = statesToPeople.get("NY");
MutableMap<String, MutableList<Person>> statesToPeople =
Maps.mutable.empty();
for (Person person : people) {
String state = US_STATE_FUNCTION.valueOf(person);
MutableList<Person> peopleInState = statesToPeople.get(state);
if (peopleInState == null) {
peopleInState = Lists.mutable.empty();
statesToPeople.put(state, peopleInState);
}
peopleInState.add(person);
}
MutableList<Person> newYorkers = statesToPeople.get("NY");
MutableListMultimap<String, Person> statesToPeople =
Multimaps.mutable.list.empty();
for (Person person : people) {
String state = US_STATE_FUNCTION.valueOf(person);
statesToPeople.put(state, person);
}
MutableList<Person> newYorkers = statesToPeople.get("NY");
MutableListMultimap<String, Person> statesToPeople =
Multimaps.mutable.list.empty();
for (Person person : people) {
String state = US_STATE_FUNCTION.valueOf(person);
statesToPeople.put(state, person);
}
MutableList<Person> newYorkers = statesToPeople.get("NY");
MutableListMultimap<String, Person> statesToPeople =
people.groupBy(US_STATE_FUNCTION);
MutableList<Person> newYorkers = statesToPeople.get("NY");
- What happens if you add the same key and value twice?
MutableMultimap<String, Person> multimap = ...;
multimap.put("NY", person);
multimap.put("NY", person);
RichIterable<Person> ny = multimap.get("NY");
Verify.assertIterableSize(?, ny);
- What happens if you add the same key and value twice?
- Depends on the type of the backing collection.
MutableListMultimap<String, Person> multimap =
Multimaps.mutable.list.empty();
multimap.put("NY", person);
multimap.put("NY", person);
MutableList<Person> ny = multimap.get("NY");
Verify.assertIterableSize(2, ny);
- What happens if you add the same key and value twice?
- Depends on the type of the backing collection
MutableSetMultimap<String, Person> multimap =
Multimaps.mutable.set.empty();
multimap.put("NY", person);
multimap.put("NY", person);
MutableSet<Person> ny = multimap.get("NY");
Verify.assertIterableSize(1, ny);
-
groupByEach()
is a special case ofgroupBy()
. -
Analogous to the difference between
collect()
andflatCollect()
. -
Appropriate when the
Function
returns anIterable
. -
The return type is the same as
groupBy()
. -
Refactor
7.mapOfItemsToSuppliers()
to usegroupByEach()
.
MutableListMultimap<String, Person> statesToPeople =
people.groupBy(US_STATE_FUNCTION);
MutableListMultimap<String, Person> statesToPeople =
people.groupByEach(US_STATES_FUNCTION);
Type | Mu- table | Immu- table | Prim- itive | Synch- ronized | Unmod- ifiable | Multi- Reader |
---|---|---|---|---|---|---|
List |
Yes | Yes | Yes | Yes | Yes | Yes |
Set |
Yes | Yes | Yes | Yes | Yes | Yes |
Bag |
Yes | Yes | Yes | Yes | Yes | Yes |
Stack |
Yes | Yes | Yes | Yes | Yes | No |
Map |
Yes | Yes | Yes | Yes | Yes | No |
BiMap |
Yes | Yes | No | Yes | Yes | No |
Multimap |
Yes | Yes | No | Yes | Yes | Yes |
- Fix
Exercise7Test
. - Refactor the repetition at TODO 7 in CompanyDomainForKata without breaking anything.
- Exercise 7 should take about 30 minutes.
- This example uses eager evaluation.
- When do the calls to
valueOf()
andaccept()
take place? - We can create our own
Function
andPredicate
to answer the question.
Person person1 = new Person(address1);
Person person2 = new Person(address2);
Person person3 = new Person(address3);
MutableList<Person> people =
Lists.mutable.with(person1, person2, person3);
MutableList<Address> addresses =
people.collect(Person::getAddress);
Assertions.assertTrue(addresses.anySatisfy(address2::equals));
Function
fromPerson
toAddress
.- Maintains internal mutable state.
- Not functional style.
- Not thread-safe.
public class AddressFunction implements Function<Person, Address> {
private int counter = 1;
public Address valueOf(Person person) {
System.out.println("Function: " + this.counter);
this.counter++;
return person.getAddress();
}
}
Predicate
returns true when address is the same reference asthis.address
- Same warnings as
AddressFunction
public class EqualsAddressPredicate implements Predicate<Address> {
private final Address address;
private int counter = 1;
private EqualsAddressPredicate(Address address) {
this.address = address;
}
public boolean accept(Address address) {
System.out.println("Predicate: " + this.counter);
this.counter++;
return address == this.address;
}
}
- When do the calls to
valueOf()
andaccept()
take place?
MutableList<Address> addresses =
people.collect(new AddressFunction());
addresses.anySatisfy(
new EqualsAddressPredicate(address2));
- When do the calls to
valueOf()
andaccept()
take place?
MutableList<Address> addresses =
people.collect(new AddressFunction());
// Function: 1
// Function: 2
// Function: 3
addresses.anySatisfy(new EqualsAddressPredicate(address2));
// Predicate: 1
// Predicate: 2
- According to Wikipedia, lazy evaluation is
“the technique of delaying a computation until its value is actually required.”
- When do the calls to
valueOf()
andaccept()
take place?
LazyIterable<Person> peopleLazy = people.asLazy();
LazyIterable<Address> addressesLazy =
peopleLazy.collect(new AddressFunction());
addressesLazy.anySatisfy(new EqualsAddressPredicate(address2));
- When do the calls to
valueOf()
andaccept()
take place?
LazyIterable<Person> peopleLazy = people.asLazy();
LazyIterable<Address> addressesLazy =
peopleLazy.collect(new AddressFunction());
addressesLazy.anySatisfy(new EqualsAddressPredicate(address2));
// Function: 1
// Predicate: 1
// Function: 2
// Predicate: 2
MutableList<Address> addresses =
people.collect(new AddressFunction());
// Function: 1
// Function: 2
// Function: 3
addresses.anySatisfy(new EqualsAddressPredicate(address2));
// Predicate: 1
// Predicate: 2
LazyIterable<Person> peopleLazy = people.asLazy();
LazyIterable<Address> addressesLazy =
peopleLazy.collect(new AddressFunction());
addressesLazy.anySatisfy(new EqualsAddressPredicate(address2));
// Function: 1
// Predicate: 1
// Function: 2
// Predicate: 2
- Why would we prefer lazy evaluation?
- According to Wikipedia, lazy evaluation is
“the technique of delaying a computation until its value is actually required.”
- Benefits include:
- Performance increases due to avoiding unnecessary calculations.
- Avoiding error conditions in the evaluation of compound expressions.
- The ability to construct potentially infinite data structures.
LazyIterate
provides the utility methods for lazy evaluation.
MutableList<Person> people =
Lists.mutable.with(person1, person2, null);
LazyIterable<Address> addresses =
LazyIterate.collect(people, Person::getAddress);
Assertions.assertTrue(
addresses.anySatisfy(address2::equals));
asLazy
returnsLazyIterable
asParallel
returnsParallelIterable
- API is similar to lazy-serial, and lazy methods return
ParallelIterable
asParallel
takes anExecutorService
and a batchSize- When evaluation is forced, the backing collections is divided into batches
which are processed in parallel in the
ExecutorService
int numCores = Runtime.getRuntime().availableProcessors();
ExecutorService executorService =
Executors.newFixedThreadPool(numCores);
ParallelListIterable<Person> peopleLazy =
people.asParallel(executorService, 2);
ParallelListIterable<Address> addressesLazy =
peopleLazy.collect(Person::getAddress);
Assertions.assertTrue(addressesLazy.anySatisfy(address2::equals));
executorService.shutdownNow();
- It’s possible to cancel a parallel-lazy computation in progress
- Just shut down the
ExecutorService
- Batches in progress won’t halt but new batches won’t start
- Means you can’t share the thread pools among multiple computations
- In the code example,
anySatisfy
will throw aRuntimeException
// In one thread
addressesLazy.anySatisfy(address2::equals);
// In another thread
executorService.shutdownNow();
- asUnmodifiable() returns a wrapper which throws on mutating methods.
Verify.assertThrows(
UnsupportedOperationException.class,
() -> richIterable.asUnmodifiable().add(0);
);
Collection<Integer> synch =
Collections.synchronizedCollection(collection);
synchronized (synch) {
for (Integer integer : synch) {
System.out.println(integer);
}
}
Collection<Integer> synch =
Collections.synchronizedCollection(collection);
synch.forEach(integer -> System.out.println(integer););
MutableCollection<Integer> synch = collection.asSynchronized();
synch.forEach(integer -> System.out.println(integer););
- Assume that synchronizedList is shared by several threads.
- What’s wrong with this code?
List<Integer> synchronizedList =
Collections.synchronizedList(list);
printAll(synchronizedList);
<T> void printAll(List<T> list) {
for (T element : list) {
System.out.println(element);
}
}
- For-Each loop syntax gets compiled to bytecode that uses an iterator.
- This code produces identical bytecode.
Iterator<T> iterator = list.iterator();
while (iterator.hasNext()) {
T element = iterator.next();
System.out.println(element);
}
List<Integer> synchronizedList =
Collections.synchronizedList(list);
printAll(synchronizedList);
<T> void printAll(List<T> list) {
for (T element : list) {
System.out.println(element);
}
}
iterator()
is the one method that is not synchronized- From the JavaDoc of
Collections.synchronizedList()
- It is imperative that the user manually synchronize on the returned list when iterating over it
List list = Collections.synchronizedList(new ArrayList());
synchronized (list) {
Iterator i = list.iterator();
while (i.hasNext())
foo(i.next());
}
}
- Failure to follow this advice may result in non-deterministic behavior.
- Using
MutableList
does not help. - It is not possible to use
Iterator
in a thread-safe way. - How can we fix this code?
MutableList<Integer> synchronizedList = list.asSynchronized();
this.printAll(synchronizedList);
<T> void printAll(List<T> list) {
for (T element : list) {
System.out.println(element);
}
}
- We could put a synchronized block inside the
printAll()
method. - Very strange, since the list might not be synchronized.
- We would have to do this in every method that takes a collection.
<T> void printAll(List<T> list) {
synchronized (list) {
for (T element : list) {
System.out.println(element);
}
}
}
- The
forEach()
method is the safe way. - The
forEach()
method is the object-oriented way. - Why does this work?
<T> void printAll(MutableList<T> list) {
list.forEach(element -> System.out.println(element););
}
SynchronizedMutableList
holds the lock for the duration of the iteration.- This is the compelling reason to use the
forEach()
method.
public void forEach(Procedure<? super E> block) {
synchronized (this.lock) {
this.collection.forEach(block);
}
}
- The code does the correct thing for a:
FastList
FastList
in aSynchronizedMutableList
FastList
in aSynchronizedMutableList
in aListAdapter
in a …
- Even if
FastList.forEach()
is implemented by using anIterator
.
<T> void printAll(MutableList<T> list) {
list.forEach(element -> System.out.println(element););
}
MultiReader
collections completely encapsulate synchronization.iterator()
andlistIterator()
throwUnsupportedOperationException
.withReadLockAndDelegate()
andwithWriteLockAndDelegate()
allow complete access to the backing collection in a synchronized context.
MultiReaderList<String> list =
Lists.multiReader.with("1", "2", "3");
list.withWriteLockAndDelegate(backingList -> {
Iterator<String> iterator = backingList.iterator();
iterator.next();
iterator.remove();
});
Assertions.assertEquals(Lists.mutable.with("2", "3"), list);
- Fix Exercise8Test.
- The final set of exercises is the most difficult and is optional
You have completed the Company Kata!
Enjoy happy Java development with Eclipse Collections!