How to do it...

The previous recipe provided with the process for how to generate the Stream objects from a source data structure. It is time to scrutinize and study the operations involved in stream objects:

  1. Open again the class EmployeeStreamService in the package org.packt.functional.codes.service.impl and add this set of methods that initializes Employee, arrays and converts List<Employee> to Employee[] using some Stream methods:
public Stream<Employee> createEmptyArrayStream(){ 
      Stream<Employee> stream = Stream.empty(); 
      return stream; 
} 
 
public Stream<Employee> createUnlimitedStream(){ 
      Stream<Employee> stream = 
Stream.generate(() -> {return new Employee();}) 
.limit(10); 
      return stream; 
} 
    
public Stream<Employee> createArrayFromOf(Employee[]  
   empArr){ 
    Stream<Employee> empStream = Stream.of(empArr); 
    return empStream; 
} 
 
public Employee[] convertStreamToArray( 
Stream<Employee> arrStream){ 
      Employee[] newArrayEmps = 
      arrStream.toArray(Employee[]::new); 
      return newArrayEmps; 
 } 
    
public void createCustomArrayStream(){ 
    Employee emp1 = new Employee(); 
    Employee emp2 = new Employee(); 
    Stream<Employee> emptyRecs = Stream.of(emp1, emp2); 
    Consumer<Employee> showRecs = System.out::println; 
    emptyRecs.forEach(showRecs); 
  } 
    
  public Stream<Employee> createArrayStream(Employee[]  
     empArr){ 
      Stream<Employee> empStream = Arrays.stream(empArr); 
      return empStream; 
    } 
    
  public Employee[] arrayStream(){ 
      Stream<Employee> serial = 
      employeeDaoImpl.getEmployees().stream(); 
      IntFunction<Employee[]> sizeEmpArr = 
        (size) -> new Employee[size]; 
      Employee[] arrEmps = serial.toArray(sizeEmpArr); 
      return arrEmps; 
  } 

The preceding methods focus on ways to create streams of objects from the start. Creating an empty Stream<Employee> is shown by createEmptyArrayStream(), while createUnlimitedStream() generates either an unlimited or limited number of Employee streams. The createCustomArrayStream() method, on the other hand, creates Employee objects at runtime and stores them in a Stream object for rendering transactions using an internal iterator, foreach(). Other methods show how to extract streams from arrays or collections.

On the other hand, the convertStreamToArray() method accepts an existing stream of Employee objects and converts it to an array of Employees using a constructor reference. Likewise, the arrayStream()method executes the same conversion but uses the IntFunction functional interface to instantiate the Employee[].

  1. The Stream API has a list of intermediate aggregate methods that are lazily executed until the last aggregate operation is reached. Add the following method that shows how to use filter() in order to select Employee objects that meet the preceding age 25 limit:
public List<Employee> getEmployeesAge(){ 
      AgeLimitService ageLimit = ()->{   
        return employeeDaoImpl.getEmployees() 
              .stream() 
              .filter((Employee e)-> e.getAge() > 25) 
              .collect(Collectors.toList()); 
      };  
      return ageLimit.qualifiedEmps(); 
}

The AgeLimitService is a custom functional interface implemented in the org.packt.functional.code.service as:

@FunctionalInterface 
public interface AgeLimitService { 
    public List<Employee> qualifiedEmps(); 
} 
  1. Add another method that highlights the use of the sorted() operation with Comparator<Employee> for sorting Employee objects. Lambda expression is used to implement the Comparator interface:
public List<Employee> getSortedEmployees(){ 
    
  Comparator<Employee> compareEmp = 
  (e1, e2) -> e1.getLastName().compareTo(e2.getLastName()); 
  List<Employee> newListEmps =  
      employeeDaoImpl.getEmployees() 
         .stream() 
         .sorted(compareEmp ) 
         .collect(Collectors.toList()); 
  return newListEmps; 
} 
  1. The following method showcases one of the most widely-used aggregate methods, map(), which takes a Function functional interface that converts one object type to another:
public Set<String> getDistinctNames(){ 
    Function<Employee,String> allNames = 
(e) -> e.getFirstName(); 
    Set<String> setNames = employeeDaoImpl.getEmployees() 
       .stream() 
       .filter((a) -> a.getAge() > 25) 
       .map(allNames) 
       .collect(Collectors.toCollection(HashSet::new)); 
    return setNames; 
} 
  1. Some variations of the map() method accept the ToIntFunction, ToDoubleFunction, or ToLongFunction function interfaces in order to generate combined end results such as the average(), count(), or sum() of all the objects. The following is a method that uses the mapToInt() method that extracts the average age of all employees:
public double getAverageAge(){ 
    ToIntFunction<Employee> sizeEmpArr = 
      (e) -> e.getAge(); 
    return employeeDaoImpl.getEmployees() 
   .stream() 
   .mapToInt(sizeEmpArr).average().getAsDouble(); 
} 
  1. For manipulating existing data without changing the original data structure, the replaceAll() method through its lambda expression can return a new List, Set, or array of data objects, given some reflected changes. The following is a method that uses replaceAll() to write all firstNames and lastNames in all caps:
public List<Employee> updateNames(){ 
      List<Employee> newListEmps=  
          employeeDaoImpl.getEmployees(); 
      newListEmps.replaceAll((e) ->{ 
        e.setFirstName(e.getFirstName().toUpperCase()); 
        e.setLastName(e.getLastName().toUpperCase()); 
        return e; 
      });  
      return newListEmps; 
} 
  1. Other intermediate operations, such as limit(n) and distinct(), can be used as special types of filtering methods which will add more constraints on the retrieval of records. The distinct() operation returns a stream of unique elements based on the given Predicate, whereas limit(n) extracts the first n objects from the container:
public List<Employee> getOldestEmps(){ 
      Predicate<Employee> checkNotQualified = 
        (e) -> e.getAge() < 25; 
      Comparator<Employee> compareAge = 
        (e1, e2) -> e1.getAge().compareTo(e2.getAge()); 
      return employeeDaoImpl.getEmployees().stream() 
          .filter(checkNotQualified) 
          .sorted(compareAge) 
          .limit(3) 
          .distinct() 
          .collect(Collectors.toList()); 
}
  1. Aside from the intermediate aggregate operations, there are also terminal operations in Stream that trigger the execution of the whole sequence of operations, and reduce() is one of them. The combination of the map() and reduce() methods is one of the most robust pairs of intermediate methods in generating basic statistical and mathematical operations on some list or arrays of data objects. The following method shows how reduce() can combine the list of numeric values from a List<Employee>, given a summation formula, to give the sum of all Employee ages. The formula may vary from one requirement to another:
public double sumAge(){ 
      BinaryOperator<Integer> addAgeEmp = 
(age1, age2) -> age1 + age2; 
      Function<Employee,Integer> ageList =  
        (e) -> e.getAge(); 
      double sum = employeeDaoImpl.getEmployees() 
           .stream() 
           .map(ageList) 
           .reduce(0, addAgeEmp);  
      return sum; 
} 

The reduce() method needs an initial value and a formula in the form of the BinaryOperator functional interface. Moreover, its output is always a single-valued end result.

  1. Another terminal operation is the collect() method that groups together all data objects in a stream and returns them as a list, set, or array. It uses some mutable reduction operations found in the class java.util.stream.Collectors. The following is a service method that returns all employees with an age greater than 25 in the form of a List:
public List<Employee> getEmployeesFunc(){ 
       
    Predicate<Employee> qualifiedEmps =  
        (e) -> e.getAge() > 25; 
    List<Employee> newListEmps =  
        employeeDaoImpl.getEmployees() 
            .stream() 
            .filter(qualifiedEmps) 
            .collect(Collectors.toList()); 
    return newListEmps; 
}
  1. Aside from Collectors.toList(), Collectors.toSet() and Collectors.toMap(), a stream can also be stored in other mutable containers such as LinkedList or LinkedHashSet through the use of the Collectors.toCollection() static method:
public Set<String> getDistinctNames(){ 
   Function<Employee,String> allNames = 
        (e) -> e.getFirstName(); 
   Set<String> setNames = employeeDaoImpl.getEmployees() 
        .stream() 
        .filter((a) -> a.getAge() > 25) 
        .map(allNames) 
        .collect(Collectors 
        .toCollection(LinkedHashSet::new)); 
    return setNames;   
} 
  1. The last popular terminal operation is forEach(), which implements the java.lang.Iterable interface to iterate internally all objects passed on it. The following method shows the firstName, lastName, and age of all records:
public void showAllEmployees(){ 
      Consumer<Employee> showAll = (e) -> { 
        System.out.format("%s %s %d
", e.getFirstName(), 
        e.getLastName(), e.getAge()); 
      }; 
      employeeDaoImpl.getEmployees() 
          .stream() 
          .forEach(showAll); 
} 
  1. There are also terminal operations that return Boolean because their purpose is to validate a particular input requirement. The following service method searches and returns true whenever one Employee record is found to have an age of less than 25:
public boolean validateAgeNotQualified(){ 
   Predicate<Employee> checkNotQualified =  
     (e) ->  e.getAge() < 25; 
   return employeeDaoImpl.getEmployees() 
.stream() 
.anyMatch(checkNotQualified); 
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.36.38