- operations available on the
Collectionsclass - Algorithms, factories, utilities
Algorithms
public static void Collections.rotate(List<?> l, int distance): takes the last element of the list, moves to beginning, and shuffles all others down. Distance is the number of times to do thispublic static void Collections.shuffle(List<?> l, Random rndm): random rearrangment of list items. Can accept a Random class object to determine the randomizationpublic static void Collections.sort(List<T> l, Comparator<? super T> c): accepts a comparator to organize the list by- in Java 8, the sort method is on the List interface itself
list.sort(Comparator c)
- in Java 8, the sort method is on the List interface itself
Collection Factories
- static methods on the
Collectionsclass that will create a new collection with specific properties Singleton: immutable single value of collection- use when you want to pass a single value to a method that takes a collection
- Example: Singletons
Set<Integer> set = Collections.singleton(1); List<String> list = Collections.singletonList("one"); Map<Integer, String> map = Collections.singletonMap(1, "one");Empty: immutable empty collection- use when you want to pass no values to a method that requires a collection
- Example: empties
Set<Integer> set = Collections.emptySet(); List<String> list = Collections.emptyList(); Map<Integer, String> map = Collections.emptyMap();SingletonandEmptyoften have performance advantages to creating anArrayListorHashMapwith no valuesunmodifiable: creates a collection that has an immutable view, but mutable collectino- will throw an
UnsupportedOperationExceptionif attempts to mutate the view (add / remove from a view) - Example: attempted mutation
UnmodifiableList list = new UnmodifiableList(); // the list itself is still mutable list.add(Product.door); // will throw an error, view not mutable list.getProducts().add(Product.window);- will throw an
Utilities
Collections.addAll(Collection<? extends T> c, T i, T i, ...): lets you add multiple objects to a collection at onceCollections.min/max(Collection<? extends T> c, Comparator<? super T> comp): optionally takes a comparator
why map?
- does not extend or implement the Collection interface
- methods:
V put(K key, V value): single valuevoid putAll(Map<? extends K, ? extends V> values): another map- null keys and values are implementation specific
V get(Object key)boolean containsKey(Object key)boolean constainsValue(Objecet value)V remove(Object key)void clear()int size()boolean isEmpty()
- Example: MapProductLookupTable
public class MapProductLookupTable implements ProductLookupTable {
private final Map<Integer, Product> idToProduct = new HashMap()<>;
@Override
public void addProduct(final Product productToAdd) {
final int id = productToAdd.getId();
if (idToProduct.containsKey(id) throw new IllegalArgumentException("Unable to add product, duplicate id for " + productToAdd);
idToProduct.put(id, productToAdd);
}
@Override
public Product lookupById(final int id) {
// will return null if not found
return idToProduct.get(id);
}
public void clear() {
idToProduct.clear();
}
}
views over maps
- can create a segemented view of a map depending on the key or the value
- methods:
Collection<Object O> keySet(): returns a set of the keys- if you manipulate the keys in this set, the original map will also be manipulated (removal)
- cannot add directly to the Set, will throw an error
Collection<Object O> keyValues(): returns a set of the values- can do a remove as well, but no add
Set<Map.Entry<Object, Object>> entrySet(): useful for iteration
- Example: entrySet iteration
final Set<Map.Entry<Integer, Product>> entries = idToProduct.entrySet();
for (Map.Entry<Integer, Product> entry : entries) {
...
// .setValue() method is useful during this time to update multiple fields
}
Sorted and Navigable map
- traversal in key ascending order
SortedMapis superseded byNavigableMapSortedMapmethods:K firstKey()K lastKey()SortedMap<K, V> tailMap(E fromKey): inclusiveSortedMap<K, V> headMap(E toKey): exclusiveSortedMap<K, V> subMap(E fromKey, K toKey)
- in order to have sorting, must have a key that is comparable or have a specific comparator
NavigableMapmethods:Map.Entry<K, V> firstEntry()Map.Entry<K, V> lastEntry()Map.Entry<K, V> pollFirstEntry(): removalMap.Entry<K, V> polllastEntry(): removalMap.Entry<K, V> lowerEntry(K key): greatest element that is strictly less than the keyMap.Entry<K, V> higherEntry(K key): lowest element that is structly greater than the keyK lowerKey(K key)K higherKey(K key)Map.Entry<K, V> floorEntry(K key): next in orderMap.Entry<K, V> ceilingEntry(K key): next in orderK floorKey(K key)K ceilingKey(K key)NavigableMap<K, V> descendingMap(): reverse orderNavigableSet<K> descendingKeySet(): reverse orderNavigableSet<K> navigableKeySet()NavigableMap<K, V> tailMap(E fromKey, boolean incl)NavigableMap<K, V> headMap(E toKey, boolean incl)NavigableMap<K, V> subMap(K fromKey, boolean frominclusive, K toKey, boolean toInclusive)
Java 8 enhancements
- methods:
replace(key, value): if key does not exist, will do nothingreplaceAll(BiFunction<K, V, V>): replaces all in map based upon the functionremove(key, value)getOrDefault(K, Default): has option for default if does not existputIfAbsent(K, V): add if does not existcompute(K, Func): takes a key and a function, then computes a new value based on the functioncomputeIfAbsent/computeIfPresent
merge(K, V, V, BiFunction<K, V, V>): merges a given key, oldvalue, newvalue with a given functionforEach: callback based iteration
- Example: bifunction
idToProduct.replaceAll((id, oldProduct) ->
new Product(id, oldProduct.getName(), oldProduct.getWeight() + 10));
implementations
HashMap- good general purpose implementation
- use
.hashCode()method just likeHashSet - maintains an array of buckets
- hash % bucket_count
- buckets are linked lists to accomodate collisions
- if two different keys are associated with the same bucket, will be a
LinkedListunder the hood
- if two different keys are associated with the same bucket, will be a
- buckets can be trees
- number of buckets increases with more elements
- if you have too many collisions, will turn into a
Treeunder the hood, performance improvement
- if you have too many collisions, will turn into a
TreeMap- implemented using red-black tree (balanced binary)
- navigable and sorted
- uses comparable / comparator to define the order
- will regularly rebalance to become efficient when searching
- maximum depth is bound log2(N)
LinkedHashMap- based on
HashMap - maintains an order based on either insertion, or access
protected boolean removeEldestEntry(Map.Entry<K, V> eldest)- good for implementing caches
- based on
WeakHashMap- weak references key
- can be removed when unreachable
- used as a cache
EnumMap- for when keys are enums, faster than other maps
- implementation based upon bitsets (stored as a singel long for <= 64 elements
| Map | put | get/containsKey | next |
|---|---|---|---|
| HashMap | O(N), Omega(1) | O(log N), Omega(1) | O(Capacity/N) |
| LinkedHashMap | O(N), Omega(1) | O(log N), Omega(1) | O(Capacity/N) |
| IdentityHashMap | O(N), Omega(1) | O(N), Omega(1) | O(Capacity/N) |
| TreeMap | O(log N) | O(log N) | O(log N) |
| EnumMap | O(1) | O(1) | O(1) |
Mutable HashMap keys
- do not allow mutability of hash keys, will create a different hashcode
- will break the map
Queues
- first in, first out
- elements leave in the order they went in
- methods:
boolean offer(E e): returns false if the queue is fullboolean add(E e): returns exceptions if the quese is full- returns false if element already exists
E remove(): throws exception if emptyE poll(): returns null if emptyE element(): throws exception if emptyE peek(): returns null if empty- probably want to use
offerandpollmore often thanaddandremove
- Example: HelpDesk
public class CategorisedHelpDesk {
private final Queue<Enquiry> queue = new ArrayDeque<>();
public void enquire(final Custom customer, final Category category) {
queue.offer(new Enquiry(customer, category));
}
// general processing
public void processAllEnquiries() {
Enquiry enquiry;
while ((enquiry = enquiries.poll()) != null) {
// using the remove() method, will throw errors
// while (!enquiries.isEmpty()) {
final Enquiry = enquiry = enquiries.remove();
enquiry.getCustom().reply("Have you tried turning it off and on again?");
}
}
public void processEnquiry(predicate, message) {
// will look into the queue to see if we should handle, if not, leave it alone on the queue
final Enquiry enquiry = enquiries.peek();
if (enquiry != null && predicate.test(enquiry)) {
enquiry.remove()
enquiry.getCustom().reply(message);
} else {
System.out.println("No work here, get snacks!");
}
}
public void processPrinterEnquiry() {
// in Java 8, can use predicate
Predicate<Enquiry> predicate = enquiry -> enquiry.getCategory() == PRINTER;
final String message = "Have you tried turning it off and on again?";
processEnquiry(predicate, message);
}
public void processGeneralEnquiry() {
// in Java 8, can use predicate
Predicate<Enquiry> predicate = enquiry -> enquiry.getCategory() != PRINTER;
final String message = "Is it out of paper?";
processEnquiry(predicate, message);
}
public static void main(String[] args) {
HelpDesk helpDesk = new HelpDesk(0;
helpDesk.enquire(Customer.JACK, Category.PHONE);
helpDesk.enquire(Custom.JILL, Category.PRINTER);
helpDesk.processPrinterEnquiry();
helpDesk.processGeneralEnquiry();
}
}
Priority Queues
- highest priority out
- just defines ordering
- Example: PriorityHelpDesk
public class PriorityHelpDesk {
private static final Comparator<Enquiry> BY_CATEGORY = new Comparator<Enquiry>() {
public int compare(final Enquiry o1, final Enquiry o2) {
// Enums have a compareTo method built in from their ordering
return o1.getCategory().compareTo(o2.getCategory());
}
}
// PriorityQueue requires a comparator to be passed in
private final Queue<Enquiry> queue = new PriorityQueue<>(BY_CATEGORY);
public void enquire(final Custom customer, final Category category) {
enquiries.offer(new Enquiry(customer, category));
}
// general processing
public void processAllEnquiries() {
Enquiry enquiry;
while ((enquiry = enquiries.poll()) != null) {
final Enquiry = enquiry = enquiries.remove();
enquiry.getCustom().reply("Have you tried turning it off and on again?");
}
}
public static void main(String[] args) {
HelpDesk helpDesk = new HelpDesk(0);
helpDesk.enquire(Customer.JACK, Category.PHONE);
helpDesk.enquire(Custom.JILL, Category.PRINTER);
helpDesk.enquire(Customer.MARY, Category.COMPUTER);
helpDesk.processPrinterEnquiry();
helpDesk.processGeneralEnquiry();
}
}
Stacks and Deques
Stacks: last in, first out- depricated with java.utils, DO NOT USE
Deque: double ended que, better stack, use instead- add or remove from both ends! Use as a Queue or a Stack
- methods:
boolean offerFirst(E e)/boolean offerLast(E e)- returns false if queue is full
void addFirst(E e)/void addLast(E e)- throws exception if full
E removeFirst()/E removeLast()- throws exception if empty
E pollFirst() / E pollLast()- returns null if empty
E getFirst()/E getLast()- throws exception when empty
E peekFirst()/E peekLast()- returns null
void push(E e)E pop()
- Example: Calculator
public class Calculator {
public int evaluate(final String input) {
final Deque<String> stack = new ArrayDeque<>();
final String[] tokens = input.split(" ");
for (String token : tokens) {
stack.add(token);
}
while (stack.size() > 1) {
int left = parseInt(stack.pop());
String operator = stack.pop();
int right = parseInt(stack.pop());
int result = 0;
switch (operator) {
case "+":
result = left + right;
break;
case "-"
result = left - right;
break;
}
// convert back into a string
stack.push(String.valueOf(result));
}
// return as an integer
return parseInt(stack.pop()); }
}
implementations
- there are multiple concurrent implementations of Queue
ArrayDeque- RingBuffer based implementation
- constant time addition / removal
- less memory, faster
- no random access
LinkedList- previously discussed in Lists
- very seldom used as a Queue
- slower, uses more memory
- has random access, but
O(N) - allows null elements
- can cause bugs
- collection of distinct elements
- no additional methods
- Example: Catalogue
public class ProductCatalogue implements Iterable<Product> {
// compares each Product's BY_NAME
private final Set<Product> products = new TreeSet<>(BY_NAME);
public void isSuppliedBy(Supplier supplier) {
products.addAll(supplier.products());
}
public Iterator<Product> iterator() { return products.iterator(); }
}
-
TreeSetwill throw an error if it is not given a recipe for how to order the elements given- if no recipe is added, will assume all Objects given are comparable (will have a natural built-in sort order)
-
Example: Product
public class Product {
// comparator for the TreeSet
public static final Comparator<Product> BY_NAME = Comparator.comparing(Product::getName);
...
}
SortedSet and NavigableSet
-
a collection with distinct elements that also have order
-
SortedSetdefines an order- no index associated, but subset views possible
- methods
E first();E last();
- methods for creating views
SortedSet<E> tailSet(E fromElement);(inclusive)SortedSet<E> headSet(E toElement);(exclusive)SortedSet<E> subSet(E fromElement, E toElement);(inclusive, exclusive)
- if you remove an element from a view, it will be removed from the original collection (does not create a new collection)
-
NavigableSetextendsSortedSet- provides ways to move through order, implemented by
TreeSet - methods
E lower();: proceededE higher();: nextE floor();E ceiling();E pollFirst();E pollLast();
- provides ways to move through order, implemented by
-
Example: Catalogue
public class Product {
// comparator for the TreeSet
public static final Comparator<Product> BY_NAME = Comparator.comparing(Product::getName);
public static final Comparator<Product> BY_WEIGHT = Comparator.comparing(Product::getWeight);
public void isSuppliedBy(Supplier supplier) { products.addAll(supplier.products())); }
public Iterator<Product> iterator() { return products.iterator(); }
public Set<Product> lightVanProducts() {
Product heaviestLightVanProduct = findHeaviestLightVanProduct();
// does not include
return products.headSet(heaviestLightVanProduct);
}
public Set<Product> heavyVanProducts() {
Product heaviestLightVanProduct = findHeaviestLightVanProduct();
// includes
return products.tailSet(heaviestLightVanProduct);
}
private Product findHeaviestLightVanProduct() {
for (Product product:products) {
if (product.getWeight() > 20) return product;
}
// return the last element if none are greater than 20
return products.last();
}
}
Set Implementations
HashSet: based uponHashMap, calles hashCode() on element and looks up location- must implement the hashCode() method for lookups
- will return a number than can be used to lookup the desired Object
- good general purpose implementation
- resizes when runs out of space
- must follow the hashCode equals contract
object.equals(other) ==> object.hashCode() == other.hasCode()- hashcodes must match
- to properly hashcode, you should combine information from each field
- call hashCode() on each field, add it to a total for the Object, then multiply by some non-prime number
- call `Arrays.hashCode() for arrays
- (int) (l ^ (l >> 32)) for longs (give me the first 32 bits of information from the long to convert into an int)
Float.floatToIntBits(f)for floats- most IDE have refactor methods to do this automatically
- can use
Object.hashCode() - always use the same fields as the
equals()method
- must implement the hashCode() method for lookups
TreeSet: based uponTreeMap, uses Binary Tree with a requied sort order- keep elements in given order (SortedSet, NavigableSet)
EnumSet: specialized implementation for enums- uses a bitset based upon the ordinal of the enum
| set | add | contains | next |
|---|---|---|---|
| HashSet | O(N),Omega(1) | O(N),Omega(1) | O(Capacity/N) |
| TreeSet | O(logN) | O(logN) | O(logN) |
| EnumSet | O(1) | O(1) | O(1) |
- Example: HashCode and equals methods
public class Product {
...
// intelliJ generates
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Product product = (Product) o;
if (weight != product.weight) return false;
// return !(name != null ? !name.equals(product.name) : product.name != null);
// OR from Java 8
return Objects.equals(name, product.name);
}
// intelliJ generates
// public int hashCode() {
// int result = name != null ? name.hashCode() : 0;
// result = 31 * result + weight;
// return result;
// }
// OR from Java 8
public int hashCode() {
return Objects.hash(name, weight);
}
}
Key features
- collection with iteration order
- has an index
intrepresenting position
- has an index
- modification methods include index
void add(int index, E e)E get(int index)E remove(int index): all elements after the removed element shift down in indexE set(int index, E element)boolean addAll(int index, Collection<? extends E> c)int indexOf(Object o): will return -1 if not found, will only return fist foundInt lastIndexOf(Object o): will return -1 if not found, will only return last foundList<E> subList(int fromIndex, int toIndex): from is inclusive, to is exclusive
- Example: shipments class
public class Shipment implements Iterable<Product> {
private static final int LIGHT_VAN_MAX_WEIGHT = 20;
private final List<Product> products = new ArrayList<>();
private List<Product> lightVanProducts;
private List<Product> heavyVanProducts;
public void add(Product product) {
products.add(product);
}
public void replace(Product oldProduct, Product newProduct) {
final int oldProductIndex = products.indexOf(oldProduct);
if (oldProductIndex > -1) products.set(oldProductIndex, newProduct);
}
public void prepare() {
// sort list of products by weight
// Collections.sort(products, Product.BY_WEIGHT
// can use method in Java 8+
products.sort(Product.BY_WEIGHT);
// find the product index that needs heavy van
int splitpoint = findSplitPoint();
// assign view of product list for heavy and light vans
lightVanProducts = products.subList(0, splitPoint);
// since subList is exclusive on the end, can use size to include the last element
heavyVanProducts = products.subList(splitPoint, products.size());
}
private int findSplitPoint() {
for ( int i = 0; i < products.size(); i++) {
Product product = products.get(i);
if (product.getWeight() > LIGHT_VAN_MAX_WEIGHT) return i;
}
return 0;
}
public List<Product> getHeavyVanProducts() { return heavyVanProducts; }
public List<Product> getLightVanProducts() { return lightVanProducts; }
// implementation of Iterable
public Iterator<Product> iterator() { return products.iterate(); }
}
- example: comparing method
// method in Java 8
public static final Comparator<Product> BY_WEIGHT = comparing(Product::getWeight);
implementations
- you can implement your own if you want
- Java ships with
ArrayListandLinkedList ArrayList- contains a backing
Arrayand has a certain size - when end of
ArrayListis reached, will need to resize the backingArray, which is expensive- will automatically double in size
- good general purpose, use as default
- CPU cache sympathetic
- contains a backing
LinkedList- double-linked (reference to previous and next)
- good for repeatedly adding and removing elements (from the start)
- slow to iterate over, worst performance
- use when manipulating elements at the start, bad for manipulation deep within
| Collection | get | add | contains | next | remove |
|---|---|---|---|---|---|
| ArrayList | O(1) | O(N), Omega(1) | O(N) | O(1) | O(N) |
| LinkedList | O(N) | O(1) | O(N) | O(1) | O(1) |
- all collections extend the java class
Collectionand have common features - children Interfaces (implementations) include:
List(ArrayList,LinkedList)Set(HashSet) ->SortedSet(TreeSet): combine sorting abilities of aListwith uniqueness of aSet, cannot index intoQueue(PriorityQueue): first in first out ordering ->Deque(LinkedList,ArrayDeque): first in first out, last in first out orderingMap(HashMap) : key value pairs ->SortedMap(TreeMap): sort order allows for iteration
Collection of Collections
- segregation between interface and implementation is cornerstone to Collections API
- Interfaces:
- multiple data structures
- functional characteristics
- random access to memory, sortable
- prefer as variable type
- often has a popular implementation
- Implementation:
- can have multiple implementations of a given interface
- specific data structure
- both
ArrayListandLinkedList
- both
- performance characteristics
- tradeoffs between different implementations
- concrete and instantiable
how to pick a collection?
- are elements keyed?
- YES -> order important?
- NO ->
Map - YES ->
SortedMap
- NO ->
- NO -> are elements unique?
- NO -> first in, first out?
- YES ->
QueueorDeque - NO -> last in, first out?
- NO ->
List - YES ->
Deque
- NO ->
- YES ->
- YES -> order important?
- NO ->
Set - YES ->
SortedSet
- NO ->
- NO -> first in, first out?
- YES -> order important?
Collection Behaviors
CollectionextendsIterablewhich means we can pull anIteratorout of it, and move through the collection one at a time- methods that all share:
| method | functionality |
|---|---|
| size() | number of elements in Collection |
| isEmpty() | true if size() == 0 |
| add(element) | add to beginning, returns true if was added |
| addAll(collection) | add elements of argument collection to this collection |
| remove(element) | remove element from collection |
| removeAll(collection) | remove all elements of argument collection to this collection |
| retainAll(collection) | remove all elements of this collection not in argument collection |
| contains(element) | true if element is in collection |
| containsAll(collection) | true if all elements of argument collection are in this collection |
| clear() | remove all elements from collection |
- declaring Collections
// must give a Generic type to the collection to tell what will be inside it
// can use an empty <> on righthand side as the type is inferred from the left
Collection<Product> products = new ArrayList<>();
products.add(door);
products.add(floorPanel);
products.add(window);
// instantiate the iterator
final Iterator<Product> productIterator = products.iterator();
while(productIterator.hasNext()) {
Product product = productIterator.next();
if (product.getWeight() > 20) {
System.out.println(product);
} else {
productIterator.remove();
}
}
// instead, can use a method that works just like the iterator
for (Product product: products) { ... }
// for loop will not allow modification of the collection while looping
// cannot remove, clear
// will throw ConcurrentModificationException
- using arrays is not debugger friendly
- must use
System.out.println(Arrays.toString(array));
- must use
- arrays do not resize after initial creation
- lacks methods for adding, removing, finding index, etc.
- would have to implement own code version for adding / removing / finding index
- arrays allow duplicates