Chapter 14 Generics and Collections Flashcards
Functional interfaces
-
Supplier<T>
T get()
-
Consumer<T>
void accept(T t)
-
BiConsumer<T, U>
void accept(T t, U u)
-
Predicate<T>
boolean test(T t)
-
BiPredicate<T, U>
boolean test(T t, U u)
-
Function<T, R>
R apply(T t)
-
BiFunction<T, U, R>
R apply(T t, U u)
-
UnaryOperator<T>
static <T> UnaryOperator<T> identity()
Using Method References
@FunctionalInterface public interface LearnToSpeak { void speak(String sound); } public class DuckHelper { public static void teacher(String name, LearnToSpeak trainer) { // exercise patience trainer.speak(name); } }
This code implements the functional interface using a lambda:
LearnToSpeak learner = s -> System.out.println(s);
A method reference lets us remove that redundancy and instead write this:
LearnToSpeak learner = System.out::println;
The :: operator tells Java to call the println() method later.
four types of method references
There are four formats for method references:
- Static methods
- Instance methods on a particular instance
- Instance methods on a parameter to be determined at runtime
- Constructors
> [!NOTE]
Remember that :: is like a lambda, and it is used for deferred execution with a functional interface.
A method reference and a lambda behave the same way at runtime.
You can pretend the compiler turns your method references into lambdas for you.
CALLING STATIC METHODS
14: Consumer<List<Integer>> methodRef = Collections::sort;
- Java is inferring information from the context.
- In this case, we said that we were declaring a Consumer, which takes only one parameter.
- Java looks for a method that matches that description.
- If it can’t find it or it finds multiple ones that could match multiple methods, then the compiler will report an error.
- The latter is sometimes called an ambiguous type error.
CALLING INSTANCE METHODS ON A PARTICULAR OBJECT
Predicate is a functional interface that takes one parameter and returns a boolean.
18: var str = "abc"; 19: Predicate<String> methodRef = str::startsWith; 20: Predicate<String> lambda = s -> str.startsWith(s);
A method reference doesn’t have to take any parameters.
var random = new Random(); Supplier<Integer> methodRef = random::nextInt; Supplier<Integer> lambda = () -> random.nextInt();
CALLING INSTANCE METHODS ON A PARAMETER
14: Consumer<List<Integer>> methodRef = Collections::sort; 15: Consumer<List<Integer>> lambda = x -> Collections.sort(x);
- Line 23 says the method that we want to call is declared in String.
- It looks like a static method, but it isn’t.
- Instead, Java knows that isEmpty() is an instance method that does not take any parameters.
- Java uses the parameter supplied at runtime as the instance on which the method is called.
BiPredicate, which takes two parameters and returns a boolean.
26: BiPredicate<String, String> methodRef = String::startsWith; 27: BiPredicate<String, String> lambda = (s, p) -> s.startsWith(p);
- Since the functional interface takes two parameters, Java has to figure out what they represent.
- The first one will always be the instance of the object for instance methods.
- Any others are to be method parameters.
- Remember that line 26 may look like a static method, but it is really a method reference declaring that the instance of the object will be specified later.
- Line 27 shows some of the power of a method reference. We were able to replace two lambda parameters this time.
CALLING CONSTRUCTORS
A constructor reference
is a special type of method reference that uses new
instead of a method, and it instantiates an object.
30: Supplier<List<String>> methodRef = ArrayList::new; 31: Supplier<List<String>> lambda = () -> new ArrayList();
32: Function<Integer, List<String>> methodRef = ArrayList::new; 33: Function<Integer, List<String>> lambda = x -> new ArrayList(x);
Java sees that we are passing an Integer parameter and calls the constructor of ArrayList that takes a parameter.
REVIEWING METHOD REFERENCES
Method references
NUMBER OF PARAMETERS IN A METHOD REFERENCE
public class Penguin { public static Integer countBabies(Penguin… cuties) { return cuties.length; } }
10: Supplier<Integer> methodRef1 = Penguin::countBabies; 11: Supplier<Integer> lambda1 = () -> Penguin.countBabies(); 12: 13: Function<Penguin, Integer> methodRef2 = Penguin::countBabies; 14: Function<Penguin, Integer> lambda2 = (x) -> Penguin.countBabies(x); 15: 16: BiFunction<Penguin, Penguin, Integer> methodRef3 = Penguin::countBabies; 17: BiFunction<Penguin, Penguin, Integer> lambda3 = (x, y) -> Penguin.countBabies(x, y);
- Lines 10 and 11 do not take any parameters because the functional interface is a Supplier.
- Lines 13 and 14 take one parameter.
- Lines 16 and 17 take two parameters.
Using Wrapper Classes
- With
autoboxing
, the compiler automatically converts a primitive to the corresponding wrapper. - Unsurprisingly,
unboxing
is the process in which the compiler automatically converts a wrapper class back to a primitive.
Wrapper classes initializing
- Boolean.valueOf(true)
- Byte.valueOf((byte) 1)
- Short.valueOf((short) 1)
- Integer.valueOf(1)
- Long.valueOf(1)
- Float.valueOf((float) 1.0)
- Double.valueOf(1.0)
- Character.valueOf(‘c’)
Can you spot the autoboxing and unboxing in this example?
12: Integer pounds = 120; 13: Character letter = "robot".charAt(0); 14: char r = letter;
- Line 12 is an example of autoboxing as the int primitive is autoboxed into an Integer object.
- Line 13 demonstrates that autoboxing can involve methods. The charAt() method returns a primitive char. It is then autoboxed into the wrapper object Character.
- Finally, line 14 shows an example of unboxing. The Character object is unboxed into a primitive char.
This innocuous-looking code throws an exception:
15: var heights = new ArrayList<Integer>(); 16: heights.add(null); 17: int h = heights.get(0); // NullPointerException
- On line 16, we add a null to the list. This is legal because a null reference can be assigned to any reference variable.
- On line 17, we try to unbox that null to an int primitive. This is a problem. Java tries to get the int value of null. Since calling any method on null gives a NullPointerException, that is just what we get.
- Be careful when you see null in relation to autoboxing.
WRAPPER CLASSES AND NULL
What do you think this code outputs?
23: List<Integer> numbers = new ArrayList<Integer>(); 24: numbers.add(1); 25: numbers.add(Integer.valueOf(3)); 26: numbers.add(Integer.valueOf(5)); 27: numbers.remove(1); 28: numbers.remove(Integer.valueOf(5)); 29: System.out.println(numbers);
> [!NOTE]
one advantage of a wrapper class over a primitive is that it can hold a null value.
It actually outputs [1].
- On lines 24 through 26, we add three Integer objects to numbers, numbers contains [1, 3, 5].
- On line 27, Java sees a matching signature for int, so it doesn’t need to autobox the call to the method.
- Now numbers contains [1, 5].
- Line 28 calls the other remove() method, and it
removes the matching object, which leaves us with just [1].
Using the Diamond Operator
- The diamond operator, <>, was added to the language.
- The diamond operator is a shorthand notation that allows you to omit the generic type from the right side of a statement when the type can be inferred.
- It is called the diamond operator because <> looks like a diamond.
List<Integer> list = new ArrayList<>(); Map<String,Integer> map = new HashMap<>(); Map<Long,List<Integer>> mapOfLists = new HashMap<>();
The diamond operator cannot be used as the type in a variable declaration.
List<> list = new ArrayList<Integer>(); // DOES NOT COMPILE Map<> map = new HashMap<String, Integer>(); // DOES NOT COMPILE class InvalidUse { void use(List<> data) {} // DOES NOT COMPILE }
Do you think these two statements compile and are equivalent?
var list = new ArrayList<Integer>(); var list = new ArrayList<>();
- The first one creates an
ArrayList<Integer>
- The second one creates an
ArrayList<Object>
Using Lists Sets Maps and Queues
- A
collection
is a group of objects contained in a single object. - The
Java Collections Framework
is a set of classes injava.util
for storing collections. - There are four main interfaces in the Java Collections Framework.
-
List: A list is an
ordered
collection of elements that allowsduplicate
entries. Elements in a list can be accessed by an intindex
. -
Set: A set is a collection that does not allow
duplicate
entries. -
Queue: A queue is a collection that
orders
its elements in a specific order for processing. A typical queue processes its elements in afirst-in, first-out
order, but other orderings are possible. -
Map: A map is a collection that maps
keys
tovalues
, withno duplicate keys
allowed. The elements in a map arekey/value pairs
.
-
List: A list is an
Map doesn’t implement the Collection interface.
COMMON COLLECTIONS METHODS
- inserts a new element into the Collection and returns whether it was successful.
boolean add(E element)
- removes a single matching value in the Collection and returns whether it was successful.
boolean remove(Object object)
- The isEmpty() and size() methods look at how many elements are in the Collection.
boolean isEmpty()
int size()
- The clear() method provides an easy way to discard all elements of the Collection.
void clear()
- The contains() method checks whether a certain value is in the Collection.
boolean contains(Object object)
- The removeIf() method removes all elements that match a condition.
boolean removeIf(Predicate<? super E> filter)
- Looping through a Collection.
void forEach(Consumer<? super T> action)
add()
inserts a new element into the Collection and returns whether it was successful.
The method signatures are as follows:boolean add(E element)
A List allows duplicates, making the return value true each time.
A Set does not allow duplicates,
will return false if tried to add a duplicate
remove()
removes a single matching value in the Collection and returns whether it was successful.
The method signatures are as follows:boolean remove(Object object)
the boolean return value tells us whether a match was removed.
Remember that there are overloaded remove() methods.
One takes the element to remove.
The other takes the index of the element to remove.
calling remove() on a List with an int uses the index, an index that doesn’t exist will throw an exception. For example, birds.remove(100);
throws an IndexOutOfBoundsException
.
DELETING WHILE LOOPING
Java does not allow removing elements from a list while using the enhanced for loop
.
Collection<String> birds = new ArrayList<>(); birds.add("hawk"); birds.add("hawk"); birds.add("hawk"); for (String bird : birds) // ConcurrentModificationException birds.remove(bird);
isEmpty() and size()
The isEmpty() and size() methods look at how many elements are in the Collection.
The method signatures are as follows:boolean isEmpty()
int size()
clear()
The clear() method provides an easy way to discard all elements of the Collection. The method signature is as follows:void clear()
contains()
The contains() method checks whether a certain value is in the Collection.
The method signature is as follows:boolean contains(Object object)
removeIf()
The removeIf() method removes all elements that match a condition.
The method signature looks like the following:boolean removeIf(Predicate<? super E> filter)
examples:
list.removeIf(s -> s.startsWith("A")); set.removeIf(String::isEmpty); // s -> s.isEmpty()
forEach()
Looping through a Collection.
The method signature is as follows:void forEach(Consumer<? super T> action)
examples:
cats.forEach(System.out::println); cats.forEach(c -> System.out.println(c));
USING THE LIST INTERFACE
- You use a list when you want an ordered collection that can contain duplicate entries.
- Items can be retrieved and inserted at specific positions in the list based on an int index much like an array.
- Unlike an array, though, many List implementations can change in size after they are declared.
Comparing List Implementations
ArrayList
* An ArrayList is like a resizable array. When elements are added, the ArrayList automatically grows.
* The main benefit of an ArrayList is that you can look up any element in constant time.
* Adding or removing an element is slower than accessing an element.
* This makes an ArrayList a good choice when you are reading more often than (or the same amount as) writing to the ArrayList.
LinkedList
* A LinkedList is special because it implements both List and Queue.
* It also has additional methods to facilitate adding or removing from the beginning and/or end of the list.
* The main benefits of a LinkedList are that you can access, add, and remove from the beginning and end of the list in constant time.
* The trade-off is that dealing with an arbitrary index takes linear time.
* LinkedList a good choice when you’ll be using it as Queue.
Creating a List with a Factory
Factory methods to create a List
* Arrays.asList(varargs)
Returns fixed size list backed by an array
* List.of(varargs)
Returns immutable list
* List.copyOf(collection)
Returns immutable list with copy of original collection’s values
example of these three methods.
16: String[] array = new String[] {"a", "b", "c"}; 17: List<String> asList = Arrays.asList(array); // [a, b, c] 18: List<String> of = List.of(array); // [a, b, c] 19: List<String> copy = List.copyOf(asList); // [a, b, c] 20: 21: array[0] = "z"; 22: 23: System.out.println(asList); // [z, b, c] 24: System.out.println(of); // [a, b, c] 25: System.out.println(copy); // [a, b, c] 26: 27: asList.set(0, "x"); 28: System.out.println(Arrays.toString(array)); // [x, b, c] 29: 30: copy.add("y"); // throws UnsupportedOperationException
Working with List Methods
List methods
-
boolean add(E element)
Adds element to end (available on all Collection APIs) -
void add(int index, E element)
Adds element at index and moves the rest toward the end -
E get(int index)
Returns element at index -
E remove(int index)
Removes element at index and moves the rest toward the front -
void replaceAll(UnaryOperator<E> op)
Replaces each element in the list with the result of the operator -
E set(int index, E e)
Replaces element at index and returns original. ThrowsIndexOutOfBoundsException
if the index is larger than the maximum one set
3: List<String> list = new ArrayList<>(); 4: list.add("SD"); // [SD] 5: list.add(0, "NY"); // [NY,SD] 6: list.set(1, "FL"); // [NY,FL] 7: System.out.println(list.get(0)); // NY 8: list.remove("NY"); // [FL] 9: list.remove(0); // [] 10: list.set(0, "?"); // IndexOutOfBoundsException
- On line 3, list starts out empty.
- Line 4 adds an element to the end of the list.
- Line 5 adds an element at index 0 that bumps the original index 0 to index 1. Notice how the ArrayList is now automatically one larger.
- Line 6 replaces the element at index 1 with a new value.
- Line 7 uses the get() method to print the element at a specific index.
- Line 8 removes the element matching NY. Finally,
- line 9 removes the element at index 0, and list is empty again.
- Line 10 throws an IndexOutOfBoundsException because there are no elements in the List. Since there are no elements to replace, even index 0 isn’t allowed. If line 10 were moved up between lines 4 and 5, the call would have succeeded.
Now, let’s look at using the replaceAll() method. It takes a UnaryOperator that takes one parameter and returns a value of the same type.
List<Integer> numbers = Arrays.asList(1, 2, 3); numbers.replaceAll(x -> x*2); System.out.println(numbers); // [2, 4, 6]
This lambda doubles the value of each element in the list. The replaceAll() method calls the lambda on each element of the list and replaces the value at that index.
ITERATING THROUGH A LIST
enhanced for loop
for (String string: list) { System.out.println(string); }
another approach
Iterator<String> iter = list.iterator(); while(iter.hasNext()) { String string = iter.next(); System.out.println(string); }
USING THE SET INTERFACE
- You use a set when you don’t want to allow duplicate entries.
Comparing Set Implementations
HashSet
- A HashSet stores its elements in a hash table, which means the keys are a hash and the values are an Object.
- This means that it uses the hashCode() method of the objects to retrieve them more efficiently.
- The main benefit is that adding elements and checking whether an element is in the set both have constant time.
- The trade-off is that you lose the order in which you inserted the elements.
- Most of the time, you aren’t concerned with this in a set anyway, making HashSet the most common set.
TreeSet
- A TreeSet stores its elements in a sorted tree structure.
- The main benefit is that the set is always in sorted order.
- The trade-off is that adding and checking whether an element exists take longer than with a HashSet, especially as the tree grows larger.