• 6.0 Enum Types

    An Enum (enumeration) is a type defined to represent a fixed set of constant values, i.e. it is a complete order listing of a collection of a values of a type. The fields of the type represent the values and are specified in capitals as constant values; all the values must be specified at compilation time. Variables of an Enum type can only hold one of the constant values.

    public enum Month{
      JANUARY, FEBRUARY, MARCH, APRIL, MAY,
      JUNE, JULY, AUGUST, SEPTEMBER,
      OCTOBER, NOVEMBER, DECEMBER
    }
    
    Month firstMonth = Month.JANUARY;
    Month lastMonth = Month.DECEMBER;
    
    for (Month m : Month.values())
       System.out.println(m);
    

    Note, "values()" method returns all values of an Enum type.

    Enum types are more powerful in Java than other languages where an Enum type is a class which can have methods and other fields, other methods are added by the compiler. The constructor for an Enum type must be package-private or private access. Enums automatically creates the constants that are defined at the beginning of the Enum body.

    public enum CustomerType {
        INTERNAL_ACCOUNT (0.25, 2),
        EXTERNAL_ACCOUNT (0.1, 1),
        EXTERNAL_NOACCOUNT (0, 3);
        
        private final double discount;
        private final int priorityLevel;
        
        CustomerType(double discount, int priorityLevel) {
            this.discount = discount;
            this.priorityLevel = priorityLevel;
        }
        
        private double getDiscount() { return this.discount; }
        
        private int getPriorityLevel() { return this.priorityLevel; }
    }
    public class Customer { 
        private int customerId;
        private String customerName;
        private String customerAddress;
        private String customerEmail;
        private CustomerType customerType;
        
        private static int numberOfCustomers=0;
    public Customer() { 
       this.customerId = ++numberOfCustomers;
       this.customerName = null;
       … 
       this.customerType = CustomerType.INTERNAL_ACCOUNT;
    }
    public Customer(String customerName,
                    String customerAddress,
                    String customerEmail,
                    CustomerType customerType) {
       … 
       this.customerType = customerType;
    } 
    Customer c =
       new Customer(
             "Puyol", "Segur",
             "charlie@barca.com", 
             CustomerType.EXTERNAL_ACCOUNT);
    
  • 6.1 Collections

    A collection is an object that groups multiple elements – generally of the same class. They are used to store, retrieve and manipulate data which forms a group for the app’s purpose. Java defines a "Collections Framework" which provides an architecture for defining different types of collections and their manipulation. The framework consists of three parts:
    Interfaces
    Implementation
    Algorithms

    Collections have several benefits, they:
    Reduces programming effort
    Increases program speed and quality
    Allows interoperability among unrelated APIs
    Reduces effort to learn and to use new APIs
    Reduces effort to design new APIs
    Facilitates software reuse

    Interfaces

    Interfaces are abstract data types that represent collections, they allow collections to be manipulated independently of the details of their representation and generally form a hierarchy. The core collection interfaces are the foundation of java collections and encapsulate six main types of collections; Set, List, Map, Queue, Deque, SortedSet, SortedMap. Note, a Map is not a true collection.

    Interfaces Map

    All core collection interfaces are generic (public interface Collection...) and the actual type a "Collection" class instance is to store should be specified at time of declaration. Concrete implementations need not specify all the operations defined in the interface, if an operation is invoked which is not specified then a "UnsupportedOperationException" is thrown.

    Collection Interfaces are the root of collection hierarchy. They group of objects known as elements and are used to pass collections around and to manipulate them when maximum generality is desired. Collections have no direct implementation, implementations exist of more specific subinterfaces, e.g. ArrayList, HashSet etc. By convention all general-purpose collection implementations have a constructor that takes a Collection argument. This is known as a conversion constructor which initializes new a collection to contain all of elements in specified collection and allows you to convert the collection's type.

    List<String> list = new ArrayList<String>(c); 
    List<String> list = new ArrayList<>(c); (> JDK 7)
    

    Basic Methods: size(), isEmpty(), contains(), add(), remove(), Iterator<E> iterator()
    Collection Methods: containsAll(), addAll(), removeAll(), retainAll(), clear()
    Other Methods: toArray()
    Stream Methods: (JDK 8)
    stream(), parallelStream()

    From JDK 8 in order to traverse collections we use aggregate operations which obtain a stream and perform aggregate operations on it. These are often used in conjunction with lambda expressions to make programming more expressive (use less lines of code). Unlike lists collections have no set order, you have add to collections, remove from collection, iterate over collections and query the size of a collection, but you cannot reference a specific element of a collection because they are not defined.

    for (Object o : collection)
        System.out.println(o);
    
  • 6.2 Iterators

    An Iterator is an object that enables you to traverse through a collection and to remove elements from the collection selectively, if desired. A collection has an iterator() method which returns an Iterator object.

    public interface Iterator<E> {
       boolean hasNext();
       E next(); 
       void remove(); //optional
    

    The "remove()" method removes last element returned by "next()" and is the only safe way to modify a collection during iteration. Use Iterator instead of for construct when you need to remove the current element or iterate over multiple collections in parallel. We can use an Iterator to filter an arbitrary Collection, i.e. traverse collection removing specific elements. Note, code is polymorphic i.e. works for any Collection regardless of implementation.

    static void filter(Collection c) {
       for (Iterator it = c.iterator(); it.hasNext();) //iterate while we have next element
          if (!cond(it.next())) it.remove(); //if element does not match condition remove it
    } 
    

  • 6.3 Set Interface

    A Set object cannot contain duplicate elements, and two Set instances are equal if they contain the same elements. Sets have three general-purpose implementations:
    HashSet
    stores elements in a hash table
    best-performing implementation
    no guarantees concerning the order of iteration
    TreeSet
    stores elements in a red-black tree
    orders elements based on their values
    substantially slower than HashSet
    LinkedHashSet
    implemented as a hash table with a linked list running through it
    orders elements based on order in which were inserted
    spares clients from unspecified, generally chaotic ordering provided by HashSet at a cost that is only slightly higher
  • 6.4 List Interface

    A list is an ordered collection (sequence) which may allow duplicate elements.

    Adds other operations:
    Access
    based on numerical position
    get(), set(), add(), addAll(), remove()
    Search
    returns numerical position
    indexOf(), lastIndexOf()
    Iteration
    listIterator()
    Range-view
    sublist() method performs arbitrary range operations on the list

    List have two general-purpose implementations:
    ArrayList
    usually better-performing implementation
    LinkedList
    offers better performance under certain circumstances

    An array class has a static factory method "asList()" which allows an array to be viewed as a List. A List's iterator() method returns an Iterator object which returns list elements in proper sequence a ListIterator is richer as it allows traversal in either direction, can modify list during iteration, and can obtain the current position of the iterator. Methods: hasNext(), next(), hasPrevious(), previous(), remove()

    for(
       ListIterator<Type> it = list.listIterator(list.size()); 
       it.hasPrevious(); )
    {
       Type t = it.previous();
    ... 
    }
    

    List "add" allows to insert a new element into list immediately before current cursor position:

    public static <E> void replace
     (List<E> list, E val, List<? extends E> newVals) {
       for (ListIterator<E> it = list.listIterator();
                            it.hasNext(); ){
          if (val == null ? it.next() == null :
                     val.equals(it.next())) {
             it.remove();
             for (E e : newVals)
                it.add(e); 
          } 
       } 
    } 
    

    Most polymorphic algorithms in the Collections class apply specifically to List; sort, shuffle, reverse, rotate, swap, replaceAll, fillcopy, binarySearch.
  • 6.5 Queue, Deque, and Map Interfaces, and Object Ordering.

    A queue interface is a collection for holding elements prior to processing. Typically, but not necessarily, they are used to order elements in a FIFO (first-in-first-out) manner; exceptions to this include priority queues, which order elements according to their values. Queues specifies additional insertion, removal, and inspection operations

    Deque "double-ended-queue" interfaces (pronounced as deck) support insertion and removal of elements at both end points. Methods are use to access elements at both ends of the Deque instance and allow us to insert, remove, and examine the elements. ArrayDeque and LinkedList implement the Deque interface. The Deque interface can be used both as last-in-first-out stacks and first-in-first-out queues.

    Map interfaces are objects that map keys to values. Maps cannot contain duplicate keys: each key can map to at most one value. There are three general-purpose Map implementations: HashMap, TreeMap and LinkedHashMap (analogous to HashSet, TreeSet, and LinkedHashSet). We can sort collections

    We can sort collections in a variety of different natural orders, a List l may be sorted using "Collections.sort(1)". For example, String and Date both implement Comparable interfaces, String can be sorted in alphabetical order (‘ASCII’ value) and Dates in chronological order; ClassCastException thrown if elements of list do not support Comparable.
  • 6.6 Summary Of Interfaces & General-Purpose Implementations.

    Core collection interfaces are the foundation of the Java Collections Framework

    The hierarchy consists of two distinct interface trees:
    Collection interface
    basic functionality used by all collections e.g. add, remove
    sub-interfaces — Set, List, and Queue
    Map interface
    maps keys and values similar to a hashtable

    Collections can be manipulated independently of the details of their representation

    Set interface
    no duplicate elements
    subinterface - SortedSet
    List interface
    ordered collection - control over where each element is inserted
    can retrieve elements from a list by position
    Queue:
    elements typically ordered on a FIFO basis
    Deque:
    enables insertion, deletion, and inspection operations at both the ends
    can be used in both LIFO and FIFO

    Concrete implementations (you can call the methods of abstract class by overriding the methods of abstract class) of the collection interfaces

    Reusable data structures

    Different types of implementation
    general-purpose implementations are most commonly used
    one implementation of an interface is often the most suitable one to use
    HashSet, ArrayList and HashMap

    Interfaces Hash table Implementations Resizable array Implementations Tree Implementations Linked list Implementations Hash table + Linked list Implementations
    Set HashSet TreeSet LinkedMashSet
    List ArrayList LinkedList
    Queue
    Deque ArrayDeque LinkedList
    Map HashMap TreeMap LinkedHashMap

    Each general-purpose implementations provides all optional operations contained in its interface

    All permit null elements, keys, and values

    None are synchronized (thread-safe)

    All have fail-fast iterators, which detect illegal concurrent modification during iteration and Fail quickly and cleanly rather than risking arbitrary, nondeterministic behavior at an undetermined time in the future

    All are "Serializable" and all support a public "clone()" method

    General-Purpose Set Implementations

    "HashSet" is much faster than "TreeSet" but offers no ordering guarantees
    use "TreeSet":
    if you need to use the operations in "SortedSet" interface
    if value-ordered iteration is required
    "LinkedHashSet" is in some sense intermediate between "HashSet" and "TreeSet"

    General-Purpose List Implementations

    "ArrayList" is most popular and fast
    Use "LinkedList":
    if you frequently add elements to the beginning of the "List"
    iterate over "List" to delete elements
    big cost in performance

    General-Purpose Map Implementations

    Use "TreeMap":
    if you need "SortedMap" operations or key-ordered "Collection"-view iteration
    Use "HashMap":
    if you want maximum speed and don't care about iteration order
    Use "LinkedHashMap":
    if you want near "HashMap" performance and insertion-order iteration
  • 6.7 Algorithms

    Algorithms are methods that perform useful computations, such as searching and sorting, on objects that implement collection interfaces. Algorithms are polymorphic, i.e., the same method can be used on many different implementations of the appropriate collection interface (reusable functionality). They take the form of static methods whose first argument is the collection on which the operation is to be performed. The great majority provided by Java platform operate on List instances, but a few of them operate on arbitrary Collection instances:

    Sorting:
    reorders a "List" so its elements are in ascending order according to an ordering relationship
    two forms of the operation are provided:
    simple form takes a "List" and sorts it according to its elements' natural ordering
    second form takes a "Comparator"

    Shuffling:
    opposite of what sort does, destroying any trace of order that may have been present in a "List"
    Routine data manipulation on "List" objects:
    reverse, fill, copy, swap, addAll
    Searching:
    binarySearch searches for a specified element in a sorted List
    two forms:
    assumes List is sorted in ascending order according to natural ordering
    second form requires a Comparator and assumes that the List is sorted into ascending order according to the specified Comparator
    Composition (is-a, has-a):
    frequency (how many times it is-a/has-a), disjoint (do two collections contain these elements in common)
    Retrieve maximums and minimums.
  • 6.8 HashSet Example

    We can add elements to the "HashSet" using the "add()" method:

    Set<String> names = new HashSet<>();
    names.add("Iniesta");
    names.add("Messi");
    names.add("Busquets");
    names.add("Puyol");
    names.add("Pique");
    names.add("Puyol");
    

    toString
    The "Enum" type implements the "toString()" method:

    System.out.println(names);
    

    for-each
    We can use the new version of the for-each construct to traverse the collection:

    Iterator it = names.iterator();
    while (it.hasNext())
       System.out.println(it.next());
    

    iterator
    The "Collection" class defines an "iterator()" method which returns an "Iterator" object that can be used to traverse the collection:

    Iterator it = names.iterator();
    while (it.hasNext())
       System.out.println(it.next());
    

    if (names.contains(toBeRemovedElement)) {
       boolean result = names.remove(toBeRemovedElement);
       if (result)
          System.out.println("removed");
       else
          System.out.println("not present");
    }
    else
       System.out.println("not present");
    

    Recall from earlier that the "TreeSet" class is a concrete implementation of the "SortedSet" interface where elements are stored in their natural sorted order. We can change the definition of the names variable to a "TreeSet", note no other code requires changing:

    Set<String> names = new TreeSet<>();
    

    toString
    ========
    [Busquets, Iniesta, Messi, Pique, Puyol]
    for each
    ========
    name: Busquets
    name: Iniesta
    name: Messi
    name: Pique
    name: Puyol
    iterator
    ========
    Busquets
    Iniesta
    Messi
    Pique
    Puyol
    

    Now let's create a "HashSet" of objects:

    Set<Customer> customers = new HashSet<>();
    

    An add operation simply involves creating a new "Customer" object and invoking the "add()" method on the "customers" object:

    customers.add(
      new Customer("Iniesta", "Fuentealbilla","andy@barca.com",CustomerType.INTERNAL_ACCOUNT)
    ); 
    

    toString
    ========
    [customer id: 5, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT, customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT, customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT, customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT, customer id: 4, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT, customer id: 6, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT ]
    for each
    ========
    customer: customer id: 5, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer: customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer: customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer: customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer: customer id: 4, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer: customer id: 6, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    iterator
    ========
    customer id: 5, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 4, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 6, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    

    Note this time “Puyol” can be added twice as the constructor for the Customer class allocates a new value for the customerId (remember hashsets do not allow identical elements) therefore the two "Customer" objects are distinct. Ids and their allocation need careful thought when you’re developing business systems.

    What about removal? Well again we can use the "contains()" method to determine if an object is contained in the collection and then "remove()" to remove it.

    Customer toBeRemovedElement = new Customer("Puyol", "La Pobla de Segur","charlie@barca.com",CustomerType.EXTERNAL_ACCOUNT);
    if (customerSet.contains(toBeRemovedElement)) {
       boolean result = customerSet.remove(toBeRemovedElement);
    

    Remove Element
    --------------
    Element to be removed: customer id: 7, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    not present
    customer id: 5, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 4, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 6, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    

    If the removal fails, perhaps due to the fact that the element to be removed has "customerId" 7 while the element to be removed has" customerId" has 4 (or 6) let’s use the setter method for "customerId" to change the value of this field for the "toBeRemoved" object:

    tobeRemoved.setCustomerId(4);
    

    Remove Element
    --------------
    Element to be removed: customer id: 7, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    Element to be removed: customer id: 4, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    not present
    customer id: 5, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 4, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 6, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    

    The remove operation still fails as though the two objects contain the same values for fields they are different objects. Recall that all classes in Java ultimately derive from the Object class which defines an equals() method:

    🔗 Java Class Object

    We can override this method in the "Customer" class to change the way in which "Customer" objects are tested for equality:

    @Override
    public boolean equals(Object o) {
       if (o instanceof Customer) {
          Customer c = (Customer)o;
          return 
            c.getCustomerId() == getCustomerId() &&
            c.getCustomerName() == getCustomerName() &&
            c.getCustomerAddress().equals(getCustomerAddress())
            && c.getCustomerEmail().equals(getCustomerEmail());
       } else {
          return false;
       }
    }
    

    We also require a hashCode() override, 31 is an arbitrary number:

    @Override
    public int hashCode() {
       return 
         getCustomerId() * 31 +    
         getCustomerName().hashCode() * 31 +
         getCustomerAddress().hashCode() * 31 + 
         getCustomerEmail().hashCode() * 31;
    }
    

    Success!:

    Remove Element
    --------------
    Element to be removed: customer id: 7, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    Element to be removed: customer id: 4, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    removed
    customer id: 6, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 5, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    

    Note it only removes the “Puyol” object with the specified "customerId"; however, this is a business rules issue concerning the creation of "Customer" objects rather than a removal issue. "Customer" objects are equal if the "equals()" method returns true AND they have the same "hashCode()" value as objects with the same hash code may not be equal.

    What about a sorted set of objects? Strings have a natural order so we can simply change from a "HashSet" to a "TreeSet" without any other changes of code. However, there is no natural order associated with the "Customer" object. Changing to a "TreeSet" of "Customer" objects produces a runtime error:

    run:
    Exception in thread "main" java.lang.ClassCastException: collections.Customer cannot be cast to java.lang.Comparable
            at java.until.TreeMap.compare(TreeMap.java:1290)
            at java.until.TreeMap.put(TreeMap.java:538)
            at java.until.TreeMap.add(TreeMap.java:255)
            at collections.Collections_Customer.addElements(Collections_Customer.java:55)
            at collections.Collections_Customer.main(Collections_Customer.java:30)
    Java Result: 1
    BUILD SUCCESSFUL (total time: 0 seconds)
    

    Set<Customer> customers = new TreeSet<>();
    

  • 6.9 TreeSet of Objects

    An object class needs to implement the "Comparable" interface in order to add new elements of that class in the correct position. Let’s look at modifying the "Customer" class to handle this:

    public class Customer implements Comparable<Customer> {
    @Override
    public int compareTo(Customer compareCustomer) {
       int custId =
             ((Customer) compareCustomer).getCustomerId(); 
    		
       //ascending order
       return this.customerId - custId;
    		
    } 
    

    This gives us a sorted set based on the ‘key’ field "customerId":

    run:
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 4, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 5, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 6, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    

    What about sorting on different fields (or even combinations of fields)? In that case we need to create a "Comparator" to tell the "constructor/sort()" method how to sort the "Set/List"

    public static Comparator<Customer>
       CustomerNameComparator = new Comparator<Customer>() { 
     
       @Override
       public int compare(Customer cust1, Customer cust2)
       {
     
          String custName1 = cust1.getCustomerName();
          String custName2 = cust2.getCustomerName();
     
          //ascending order
          return custName1.compareTo(custName2);
     
          //descending order
          //return custName2.compareTo(custName1);
       }
     
    };
    

    Note this uses an anonymous class to implement / override the "compare()" method of the "Comparator" interface. Recall you cannot instantiate an Interface therefore a class must be instantiated and the interface method executed on that object. An anonymous class means that we do not have to name the class/object that is used to implement the interface and invoke the method. Lets create a new "TreeSet" object with elements from the original "TreeSet" using a "Comparator" for ordering:

    System.out.println("Sort By Name");
    System.out.println("------------");
    Set<Customer> newSet =
         new TreeSet(Customer.CustomerNameComparator);
    newSet.addAll(customers);        
    displayElements(newSet);
    

    run:
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 4, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 5, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 6, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    Sort By Name
    ------------
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 5, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 4, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 6, customer name: Puyol, customer address: la Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    

    An alternative Collection interface is the List interface with concrete implementations: "ArrayList" and "LinkedList". Let’s look at changing the "Customer" collection from a "Set" to a "List":

    List<Customer> customers = new ArrayList<>();
    

    Add Elements
    ------------
    toString
    ========
    [customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT, customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT, customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT, customer id: 4, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT, customer id: 5, customer name: Pique, customer address: Barcelona, customer email: Gerard@barca.com, customer type: EXTERNAL_NOACCOUNT,customer id: 6, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT]
    for each
    ========
    customer: customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer: customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer: customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer: customer id: 4, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer: customer id: 5, customer name: Pique, customer address: Barcelona, customer email: Gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer: customer id: 6, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    iterator
    ========
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 4, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 5, customer name: Pique, customer address: Barcelona, customer email: Gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 6, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    

    A "List" can hold duplicate elements and elements can also be inserted into a specific position. let’s create a "Customer" object and add it to the collection twice - at the start and the end of the collection

    customers.add(new Customer("Iniesta", … )); 
    … 
    customers.add(new Customer("Pique", … )); 
    Customer c = new Customer("Puyol", "La Pobla de Segur","charlie@barca.com",CustomerType.EXTERNAL_ACCOUNT);
    customers.add(c);
    customers.add(0, c); 
    

    We can use the get() method to retrieve the element at the specified index position:

    if (customers.get(0) == customers.get(5))
       System.out.println(
         "Objects at positions 1 & 6 are equal"   ); 
    displayElements(customers);
    

    Get
    ---
    Objects at position 1 & 6 are equal
    customer id: 5, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 4, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 5, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    

    The "indexOf()" method determines the index position of the first occurrence of the object and "lastIndexOf()" the last index position:

    int index = customers.indexOf(c);
    System.out.println("Index: " + index);
            
    index = customers.lastIndexOf(c);
    System.out.println("Index: " + index);
    

    customer id: 5, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 4, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 5, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    Index of
    --------
    customer id: 5, customer name: Puyole, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    Index: 0
    Last Index Of
    -------------
    Index: 5
    

    We can remove an element by locating its index as above and using the "remove()" method or more commonly using an iterator to traverse the collection and test each element in turn for a match and then removing the element.

    A "List" is already ordered in the order in which elements have been inserted. The "sort()" method uses the natural order of objects as defined by the "compareTo()" method: Collections.sort(customers)

    The "sort()" method used in conjunction with a Comparator will sort by specific fields: customers.sort(Customer.CustomerNameComparator)

    run:
    Add Elements
    ------------
    customer id: 5, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 4, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 5, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    Sort By Id
    ----------
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 4, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 5, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 5, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    Sort By Name
    ------------
    customer id: 3, customer name: Busquets, customer address: Sabadell, customer email: sergi@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 1, customer name: Iniesta, customer address: Fuentealbilla, customer email: andy@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 2, customer name: Messi, customer address: Rosario, customer email: lionel@barca.com, customer type: INTERNAL_ACCOUNT
    customer id: 4, customer name: Pique, customer address: Barcelona, customer email: gerard@barca.com, customer type: EXTERNAL_NOACCOUNT
    customer id: 5, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT
    customer id: 5, customer name: Puyol, customer address: La Pobla de Segur, customer email: charlie@barca.com, customer type: EXTERNAL_ACCOUNT