Lambdas and streams

Introduction
  • Introduction to session by speaker before transitioning to Anna.

  • Quick inquiries regarding the students' progress on the assignment.

  • Introduction: Anna, former tutor, currently a software engineer at Google.

Anna's Background
  • Anna works in Payments Compliance Identity Verification at Google.

    • Importance explained: validating significant monetary transactions (e.g., 5,000,0005,000,000).

    • Examples given: selling on the Play Store, YouTube monetization.

    • Regulatory complexity illustrated by cross-border transactions (e.g., Australia to the US, EU regulations).

  • Personal background and academic qualifications:

    • Bachelor of Science majoring in Computer Science and Accounting.

    • Tutored multiple computer science courses (e.g., 1001, 2002).

    • Completed internships in Brisbane before joining Google.

Structure of the Lecture
  • Lecture focuses on:

    • Functional interfaces

    • Anonymous classes

    • Lambdas

    • Streams

  • Importance of understanding functional interfaces to grasp subsequent concepts clearly.

Coding Example: The Red Room Class
  • Problem statement: Create a class called Red Room that allows only patrons of drinking age.

  • Breakdown of tasks identified:

    • Create a method called enter that receives a list of patrons.

  • Patrons Class:

    • Contains properties like name and age.

    • Breath alcohol test check included.

Defining the Enter Method
  • Declaration of the enter method:

public void enter(List<Patron> prospectivePatrons) {
  • Age Check Logic:

    • Iterate through the list of patrons and check if age 18\ge 18.

    • Occupants tracking through use of a Set (to avoid duplicates).

if (patron.getAge() >= 18) {
    occupants.add(patron);
  }
  • Testing Enter Method:

    • Create instances of patrons and test if the method properly allows entries based on age constraints.

Problem Identification and Solution
  • Acknowledgement that the age checking logic is hard-coded.

  • Discusses potential complexities like different regulations on different days (if patrons could be let in if accompanied).

  • Introduces the need to refactor by implementing the Bouncer interface.

  • Dependency Injection introduced to avoid hard-coding logic directly within the method.

public void enter(List<Patron> prospectivePatrons, Bouncer bouncer) {
  • Bouncer Interface:

    • Declares behavior through a method checkEntry(Patron p) to provide the logic for accepting patrons.

  • Implementation of Age Bouncer:

    • Create a class that implements Bouncer and defines the logic for checking age.

Conclusion on Refactoring Logic
  • Anonymous Classes:

    • Explanation given for use cases of anonymous classes. Define, instantiate, and use them in one go without naming.

  • Coding example: transforming the logic into an anonymous class for ease of implementation.

    ```java

    Bouncer bouncer = new Bouncer() {

    public boolean checkEntry(Patron p) {

    return p.getAge() >= 18;

    }

    };```

- **Lambdas**:
  - Introduced as a streamlined way of defining functionality for functional interfaces with syntax reduction.
  - Refactor Age Bouncer implementation to use lambda expressions instead of anonymous classes:
Predicate<Patron> ageBouncer = (p) -> p.getAge() >= 18;
  • Discussed preference for lambdas over anonymous classes for clearer syntax.

Functional Interfaces and Their Power
  • Definition: a functional interface contains a single abstract method.

  • Various examples of common functional interfaces:

    • Function: takes one argument and returns a result.

      Function<String, Integer> stringLength = s -> s.length();
      System.out.println(stringLength.apply("hello")); // Output: 5
    
    • Predicate: takes one argument and returns a boolean.

      Predicate<Integer> isEven = n -> n % 2 == 0;
      System.out.println(isEven.test(4)); // Output: true
    
    • Consumer: takes one argument and returns nothing.

      Consumer<String> greeter = s -> System.out.println("Hello, " + s);
      greeter.accept("World"); // Output: Hello, World
    
    • Supplier: takes no arguments and returns a result.

      Supplier<Double> randomValue = () -> Math.random();
      System.out.println(randomValue.get()); // Output: a random double value
    
  • Preference to use standard functional interfaces over creating custom ones, enhancing code reusability and clarity.

Streams
  • Transition from discussing Lambdas to Streams.

  • Explanation of streams in the context of Java:

    • Streams allow performing aggregate operations over sequences of elements (like collections, arrays).

  • Demonstration on counting items in a list versus using streaming:

    1. Manipulating the list directly through looping.

    2. Using stream(), filter(), map(), and other methods to perform operations declaratively.

  • Streams are lazy: operations are not executed until a terminal operation is invoked.

Stream Operations
  • Intermediary vs Terminal Operations:

    • Intermediaries (like filter, map) return a new stream.

    • Terminals (like collect, forEach) consume the stream.

  • Performance consideration: Filter as early as possible in the stream to reduce dataset size before processing heavy operations.

Collectors and Statistics
  • Discussed methods like Collectors.toList() for gathering elements back into a list, ensuring that the original data source remains unchanged.

  • Returning collections instead of streams where applicable is best practice.

  • More functions and variations: reduce, distinct, etc., that can be implemented on streams.

Infinite Streams
  • Example given on generating infinite streams using Stream.iterate().

  • Explanation on practical implementation to approximate Pi with a specified formula.

Input and Output in Java
  • Introduction to file handling and reading contents.

  • Importance of good practices in error handling while reading files, maintaining state clarity.

  • Recommendation against Java serialization due to security flaws; promoting the use of JSON or XML formats for data representation.

  • Brief instructions on implementing BufferedReader, error handling and formatting for reading files.

  • Importance of readability and useful error messaging when parsing file content.

Conclusion
  • Validated understanding of functional programming principles in Java through applied examples.

  • Encouraged the integration of advanced concepts like streams and lambdas in assignments to enhance programming skills.