• 7.0 Employees Application

    A business system has a collection of employee objects; employees are allocated a unique id on joining the company. Other information held includes the employee name, role, and length of service (in complete years).

    public class Employee implements Comparable<Employee>,Serializable { 
       private final int employeeId;
       private String employeeName;
       private String employeeRole;
       private int lengthOfService;
        
       private static int numberOfEmployees=0;
    public Employee()
    { 
       this.employeeId = ++numberOfEmployees;
       this.employeeName = null;
       this.employeeRole = null;
       this.lengthOfService = 0;
    }
    public Employee(String employeeName, String employeeRole) { … } 
    public Employee(int employeeId,
                    String employeeName,
                    String employeeRole,
                    int lengthOfService) { … } 
    @Override
    public int hashCode() {
       return getId()*31 + getEmployeeName().hashCode()*31 + getEmployeeRole().hashCode()*31 + getLengthOfService()*31;
    }
     
    @Override
    public boolean equals(Object o) {
       if (o instanceof Employee) {
          Employee e = (Employee)o;
          return e.getId() == getId() &&
                 e.getEmployeeName() == getEmployeeName() &&
                 e.getLengthOfService() == getLengthOfService() &&
                 e.getEmployeeRole().equals(getEmployeeRole());
       } else {
          return false;
       }
    }
    @Override
    public int compareTo(Employee compareEmployee) {
    	
       int empId = ((Employee) compareEmployee).getId(); 
    		
       //ascending order
       return this.employeeId - empId;
    		
       //descending order
       //return employeeId - this.empId;
    		
    }
    public static Comparator<Employee>
       EmployeeNameComparator = new Comparator<Employee>() {
     
       @Override
       public int compare(Employee emp1, Employee emp2) {
     
          String empName1 = emp1.getEmployeeName();
          String empName2 = emp2.getEmployeeName();
     
          //ascending order
          return empName1.compareTo(empName2);
     
          //descending order
          //return empName2.compareTo(empName1);
       } 
    };
    

    Model Class Checklist:

    1. fields (types)
    2. constructor(s) (new, load)
    3. getters and setters
    4. toString()
    5. additional operations
    e.g. add/remove from collection field
    6. hashCode() and equals()
    7. Comparable and compareTo()
    8. Comparators

    public class EmployeesController {
       private final Repository<Employee> repository;
        
       public EmployeesController() {
          … 
          char c =
                inputHelper.readCharacter("Load File (Y/N)?");
          if (c == 'Y' || c == 'y') {
              String fileName =
                 inputHelper.readString("Enter filename");               
              this.repository = new Repository<>(fileName);
          } 
          else {
              this.repository = new Repository<>();
          } 
       } 
    private char displayEmployeesMenu() {
       InputHelper inputHelper = new InputHelper();
       System.out.print("\nA. List Employees (Iterator)");
       System.out.print("\tB. List Employees In Reverse (Iterator)");
       System.out.print("\tC. List Employees With Role");
       System.out.print("\nD. List Employees With Role And Service");
       System.out.print("\tE. List Employees With Search Criteria");
       System.out.print("\tF. List Employees Using Lambda Expressions"); 
       System.out.print("\nG. List Employees Using Predicate");
       System.out.print("\tH. Process Employees");
       System.out.print("\tI. Process Employees With Function");
       System.out.print("\nJ. Process Elements With Function");
       System.out.print("\tK. Process Elements With Aggregate Operations");        
       System.out.print("\tQ. Quit\n");         
       return inputHelper.readCharacter("Enter choice", "ABCDEFGHIJKQ");
    } 
    public void run() {
       boolean finished = false;
       String role="Developer";
       int service = 3;
            
       do {
          char choice = displayEmployeesMenu();
          switch (choice) {
             case 'A': 
                listEmployees();
                break;
             case 'B': 
                listEmployeesInReverse();
                break;
             …
    
  • 7.1 Collections and Retrieval

    This application requires different retrieval functionality as want to be able to collect and retrieve based on fields required, conditions to be met, and sort order. The simplest solution is a different method for each type of retrieval. However this leads to large number of methods and code changes for new forms of retrieval. Consequently our goal is to create generic method which will handle multiple types of query.

    List Employees

    private void listEmployees() {
       System.out.format("\033[31m%s\033[0m%n", "Employees");
       Iterator it = repository.getItems().listIterator();
       while (it.hasNext()) {
          Employee e = (Employee) it.next();
          System.out.println(e);
       }
    }
    

    Employees
    =========
    employee id: 1, employee name: Sergi Busquets, employee role: Project Leader, length of service: 5
    employee id: 2, employee name: Sergi Roberts, employee role: Developer, length of service: 2
    employee id: 3, employee name: Edgar Davids, employee role: Tester, length of service: 0
    employee id: 4, employee name: Dani Alves, employee role: Developer, length of service: 9
    employee id: 5, employee name: Jordi Alba, employee role: Developer, length of service: 4
    employee id: 6, employee name: Ivan Rakitic, employee role: Developer, length of service: 1
    employee id: 7, employee name: Luis Suarez, employee role: Tester, length of service: 2
    

    🔗 Java Interface ListIterator

    List Employees In Reverse

    private void listEmployeesInReverse() {        
       ListIterator it = repository.getItems().listIterator(repository.getItems().size()); 
       while (it.hasPrevious()) {
          Employee e = (Employee) it.previous();            
          System.out.println(e);
       }    	
    }
    

    Employees
    =========
    employee id: 7, employee name: Luis Suarez, employee role: Tester, length of service: 2
    employee id: 6, employee name: Ivan Rakitic, employee role: Developer, length of service: 1
    employee id: 5, employee name: Jordi Alba, employee role: Developer, length of service: 4
    employee id: 4, employee name: Dani Alves, employee role: Developer, length of service: 9
    employee id: 3, employee name: Edgar Davids, employee role: Tester, length of service: 0
    employee id: 2, employee name: Sergi Roberts, employee role: Developer, length of service: 2
    employee id: 1, employee name: Sergi Busquets, employee role: Project Leader, length of service: 5
    

    List Employees With Role

    private void listEmployeesWithRole(String role) {        
       Iterator it = repository.getItems().listIterator();
       while (it.hasNext()) {
          Employee e = (Employee) it.next();
          if (e.getEmployeeRole().equals(role))
             System.out.println(e);
       }    	
    } 
    

    Employees : Developer
    =========
    employee id: 2, employee name: Sergi Roberts, employee role: Developer, length of service: 2
    employee id: 4, employee name: Dani Alves, employee role: Developer, length of service: 9
    employee id: 5, employee name: Jordi Alba, employee role: Developer, length of service: 4
    employee id: 6, employee name: Ivan Rakitic, employee role: Developer, length of service: 1
    

    List Employees With Role & Service

    private void listEmployeesWithRoleAndService(String role, int service) {        
       Iterator it = repository.getItems().listIterator();
       while (it.hasNext()) {
          Employee e = (Employee) it.next();
          if (e.getEmployeeRole().equals(role)
           && e.getLengthOfService() >= service)
             System.out.println(e);
       }    	
    } 
    

    Employees : Developer, Service >=: 3
    =========
    employee id: 4, employee name: Dani Alves, employee role: Developer, length of service: 9
    employee id: 5, employee name: Jordi Alba, employee role: Developer, length of service: 4
    

  • 7.2 Using a Functional Interface

    Recall from previous lectures that an interface specifies a set of abstract methods. A class implements an interface and supplies concrete methods for each abstract method defined in the interface. A functional interface has only one abstract method and to ‘instantiate’ a functional interface we must create an object and invoke the method on that object. A local class can be used to both provide a concrete method implementation of the abstract method and permit instantiation of an object to invoke the method on.

    A local class is defined where it is needed and we would require a local class for each type of query. However this make our program vulnerable to new demands and changes in class structure. Let’s consider the use of a functional interface to check an employee against some specified criteria. The interface requires a "test()" method. A local class can be used to implement the concrete "test()" code for a specific query and invoke that method on the Employee object itself returning true or false

    interface CheckEmployee { boolean test(Employee e); }
    
    class CheckEmployeeForRoleAndService implements CheckEmployee {
       String role; int service;
            
       CheckEmployeeForRoleAndService(String role, int service) {
          this.role = role;
          this.service = service;
       }
            
       @Override
       public boolean test(Employee e) {
          return e.getEmployeeRole().equals(this.role) && e.getLengthOfService() >= this.service;
       }
    }
    public boolean 
       matchRoleAndService(String role,int service) {
       CheckEmployeeForRoleAndService tester = new CheckEmployeeForRoleAndService(role, service); 
       return tester.test(this);
    } 
    private void listEmployeesWithSearchCriteria(String role, int service) { 
      Iterator it = repository.getItems().listIterator();
      while (it.hasNext()) {
         Employee e = (Employee) it.next();
         if (e.matchRoleAndService(role, service)) 
             System.out.println(e);
      }       
    } 
    

    Using An Anonymous Class

    Recall that a local class is only being used to instantiate an object that can invoke the overridden interface method. There is no need for a named class here and we can use an anonymous class. This reduces the amount of code however the syntax is still fairly unwieldy. Let’s revisit the "matchRoleAndService()" method and replace the local class with an anonymous class implementation.

    public boolean matchRoleAndService(String role, int service) {
       return
          new CheckEmployee() {
                @Override
                public boolean test(Employee e) {
                   return
                     e.getEmployeeRole().equals(role) && e.getLengthOfService() >= service;
                } 
              }.test(this);
    } 
    

    Using Lambda Expressions

    Recall that a functional interface has only one method and therefore we shouldn’t need to specify that method. A lambda expression allows specification of functionality which can be passed into a method and used to ‘instantiate’ the functional interface, i.e. treat code as data. This would allow us to pass different types of functionality, queries, into a method, i.e. a more general retrieval method. Let’s implement a general match() method within the employee class with a CheckEmployee interface argument which can be instantiated with a lambda expression

    Employee class:
    public boolean match(CheckEmployee tester) {
       return tester.test(this);
    }
    Controller class: 
    private void listEmployeesUsingLambdaExpression(String role, int service) {
       while (it.hasNext()) {
          Employee e = (Employee) it.next();
          if (e.match(
                   emp -> emp.getEmployeeRole().equals(role)
                && emp.getLengthOfService() >= service
              )) 
             System.out.println(e);
       }       
    } 
    

    The syntax of a lambda expression is:
    comma-separated list of formal parameters
    Enclosed within parentheses unless there is only one
    arrow token
    ->
    body
    single expression or block
    return value or void
    if void braces not needed

    Could be considered as an anonymous method

    Facilitates general methods where specific functionality is passed as an argument

    Lambda expressions are very popular in modern Java programming. They facilitate functional programming and can be used to defer execution as long as possible by passing functionality to place where it is actually needed. They can also be used to represent a functional interface as an expression which reduces code and provides flexibility. They provide better support for the DRY (Don’t Repeat Yourself) principle.

    🔗 When to Use Nested Classes, Local Classes, Anonymous Classes, and Lambda Expressions
  • 7.3 Standard Functional Interfaces

    Java 8 has recognized the commonality between functional interfaces and has created a number of standard functional interfaces which make the JDK work and are open to user code.

    🔗 Package java.util.function

    The complier is responsible for determining the type for a lambda expression. It determines the type for the expression based on the context in which the expression appear. The types for lambda expressions are known as target types. Each type has a single abstract method called the functional method. Given that functional interfaces can only have a single abstract method lambda expressions can only be used if the type of lambda body and return type match. Lambda expression have four uses; assignment, invocation, return, and cast context. General-purpose (Generic) interfaces specify one or more parameter types, void or return value for abstract methods; used for common requirements:

    Supplier<T>
    get() method
    Supplies a value of type T
    Consumer<T>
    accept() method
    Consumes a value of type T
    Function<T, R>
    apply() method
    A function with argument of type T and return value of type R
    Predicate<T>
    test() method
    Boolean-valued function
  • 7.4 Using a Predicate & Other Standard Functional Interfaces

    Using a Predicate

    Recall the CheckEmployee interface has only one "test()" method which we ‘instantiated’ using: local class, anonymous class, and a lambda expression. The Predicate<T> general purpose interface already supplies this specification so we can use it:

    public boolean match(Predicate<Employee> tester)
    {
       return tester.test(this);
    }
    

    The Predicate<T> interface:

    interface Predicate<T> {
       boolean test(T t);
    }
    

    is instantiated as:

    interface Predicate<Employee> {
       boolean test(Employee t);
    }
    

    Using Other Standard Functional Interfaces

    private void processEmployees(String role, int service) {
    
       while (it.hasNext()) {
          Employee e = (Employee) it.next();
          if (e.match(emp -> emp.getEmployeeRole().equals(role)
                   && emp.getLengthOfService() >= service))
             processEmployee(e, emp -> System.out.println(emp));
       }       
    }
    

    Recall the "Consumer<T>" interface contains an "accept()" method which consumes an object of type T (recap: T is a generic type, i.e. somewhere in our code we will create a type that we substitute in for T and consumer will then be of and expect that type). Once the "Predicate<T>" has been used to select employees that match the criteria detailed in the lambda expression.

    We can ‘instantiate’ the Consumer<T> interface to process those employees:

    private void processEmployee(Employee employee, Consumer<Employee> process) {
       process.accept(employee);
    }
    

    The concrete implementation of the accept() method is supplied as argument: "emp -> System.out.println(emp)". The Function<T, R> interface can be instantiated with a function which takes an argument of type T and returns an object of type R. Again we can use a lambda expression to ‘instantiate’ the interface: "emp -> emp.getEmployeeName()". We can extend the previous example to use a predicate for matching, a function for selecting fields and a consumer for processing.

    private void processEmployeesWithFunction(String role, int service) {
       while (it.hasNext()) {
          Employee e = (Employee) it.next();
          if (e.match(emp -> emp.getEmployeeRole().equals(role) && emp.getLengthOfService() >= service))
          {
             processEmployeeWithFunction(e, emp -> emp.getEmployeeName(), str -> System.out.println(str)); 
          } 
       }       
    } 
    private void processEmployeeWithFunction(Employee employee, Function<Employee, String> mapper, Consumer<String> process) {
       String data = mapper.apply(employee);
       process.accept(data);
    }
    
  • 7.5 Generics

    Generics enable types (classes & interface) to be parameterized, i.e. written in a generic manner with no specific types and types are supplied at time of instantiation. This facilitates reuse of code and extensibility giving us many advantages including stronger type checking at compile time. Common parameter names (enclosed by a diamond <>) are:

    E – Element (collections framework)
    K – Key
    N – Number
    T – Type
    V – Value

    private <X, Y> void
        processElementsWithFunction(
          Iterable<X> source,
          Predicate<X> tester,
          Function<X, Y> mapper,
          Consumer<Y> process
        )
    {
       for (X x : source) {
          if (tester.test(x)) {
              Y data = mapper.apply(x);
              process.accept(data);
          } 
       }       
    }
    

    In order to use a generic function we need to obtain a "source" of objects and filter objects that match "Predicate" object "tester". Then map each filtered object to a value as specified by the "Function" object "mapper". We perform actions on each mapped object using the "Consumer" "object" block.

    processElementsWithFunction(
      repository.getItems(),
      (Employee e) -> 
         e.getEmployeeRole().equals(role) &&
         e.getLengthOfService() >= service,
       e -> e.getEmployeeName(),
       str -> System.out.println(str)
    );
    

    Generic Function
    ================
    Dani Alves
    Jordi Alba
    

    processElementsWithFunction(
       projects,
       (Project p) ->
          p.getNumberOfAllocatedDays() >= 10
          && p.getEmployees().size() >= 2,
       p -> Integer.toString(p.getProjectId())
            + ": " + p.getProjectTitle(),
       str -> System.out.println(str)
    ); 
    
  • 7.6 Streams

    A stream is a sequence of elements. Processing elements in a collection can be achieved by streaming the collection and then applying a pipeline of aggregate operations (operations that perform a function), typically: stream, filter, map and process. Streams are made up of a sequence of aggregate operations which are known as pipelines. Pipelines have a source, typically a collection and are divided into intermediate, e.g. filtering a collection by some criteria, and terminal operations, typically processing the matched (filtered) elements of the collection to produce output or a new value.

    🔗 Package java.util.stream

    Aggregate Operations Overview
    Operations that perform a function
    Process elements from a stream
    Not the collection
    Pipeline is a sequence of aggregate operations
    Typically accept lambda expressions as parameters

    processElements Action Aggregate Operation
    Obtain a source of objects Stream<E> stream()
    Filter objects that match a Predicate object Stream<T> filter(Predicate<? super T> predicate)
    Map objects to another value as specified by a Function object <R> Stream<R> map(function<? super T, ? extend R> mapper)
    Perform an action as specified by a Consumer object void forEach(consumer<? super T> action)

    repository.getItems()
      .stream()
      .filter(e ->
         e.getEmployeeRole().equals(role) &&
         e.getLengthOfService() >= service)
      .map(e -> e.getEmployeeName())
      .forEach(str ->System.out.println(str)); 
    

    projects
      .stream()
      .filter(p ->
              p.getNumberOfAllocatedDays() >= 10
              && p.getEmployees().size() >= 2)
      .map(p -> Integer.toString(p.getProjectId())
                + ": " + p.getProjectTitle())
      .forEach(str -> System.out.println(str)); 
    
    

    run:
    Aggregate Operations
    ====================
    Dani Alves
    Jordi Alba
    1: Order Processing
    2: Stock Control
    4: CRM
    5: Web
    

    🔗 Aggregate Operations
  • 7.7 Generic Repository Class

    Note we previously tried to minimize reference to the class of object stored in the Repository collection. However, we still had some reference to Customer, in this case Customer would be replaced by Employee :

    public class Repository {
        private List<Customer> items; 
    

    We are aiming to produce a generic Repository class where the collection can hold any suitable class of object. Therefore, we replace the references to the class of object with a generic class T. This allows us to specify the class of object to be stored in the repository at run time rather than development time.

    public class
       Repository<T extends RepositoryObject>
         implements RepositoryInterface<T>,
                    Serializable {
       private List<T> items; 
       …
       @Override
       public List<T> getItems() {        
          return this.items;
       }
       … 
    

    Note T (and whatever replaces it at instantiation time) must implement a "getId()" method

    public T getItem(int id) {
       for (T item:this.items) {
           if (item.getId() == id)
              return item;
       }
       return null;
    }
    

    Therefore we need to ensure that any class that replaces T has a "getId()" method. To do this we create a RepositoryObject abstract class which specifies an abstract getId() method. Any class which desires to hold objects in our generic Repository class must extend this abstract class and provide a concrete implementation of the getId() method.

    public class
       Repository<T extends RepositoryObject>
    
    public abstract class RepositoryObject {
        public abstract int getId();
    }
    

    Note we could specify other methods here that we might need in implementing additional methods in the generic Repository class. Similarly we could provide a default implementation of getId() which uses Java facilities to generate a unique id.

    public class Employee
            extends RepositoryObject 
            implements Comparable<Employee>,
                       Serializable {
       private final int id;
       @Override
       public int getId()
       { 
          return this.id;
       } 
    

    The controller class defines the repository object and so has to specify the class of object to store in the repository:

    private final Repository<Employee> repository;
    

    The DAOInterface has no reference to the class of object stored in the repository so need not change:

    public Repository load(String filename);
    
    public void store(String filename, Repository repository);
    

    The DAOObjImpl class contains no reference to the class of object and will work provide the class implements the Serializable interface:

    @Override
    public Repository load(String filename) {
       Repository repository = new Repository();
       try {
          …
          repository = (Repository) ois.readObject();
       } catch(ClassNotFoundException ex){ } 
       return repository;
    }
    

    Note the text implementation still needs to know about the class of object as it has to create objects from text strings read from the text file