Understanding Java Streams

2 minute read

What is a Stream?

  • A stream in Java is a sequence of data that takes input from Collections or IO Channels.
  • Streams don’t change the original data structure.
  • A Stream Pipeline is the operation (STREAM OPERATIONS) that run on a stream to produce a result.
  • Each intermediate operation is lazily executed and returns a stream as a result.
  • Terminal operations mark the end of the stream and return the result.
  • Finite Streams have a limit.
  • Infinite Streams are like sunrise/sunset cycles.

Important Notes

  • Re-stream a List each time: Don’t re-use a stream because “creating” a Stream just points at the existing data structure behind the scenes; it does not copy the data.
  • Do not support indexed access: findFirst() can give the top element, but not the second, third, or last element.
  • Simple syntax to build a List or array from a Stream.

Three Common Ways to Create a Stream

  1. From ArrayList
    List<Student> students = new ArrayList<>();
    Stream<Student> studentStream = students.stream();
    
  2. From Array of Objects (not array of primitives)
    Student[] students = {....};
    Stream<Student> studentStream = Stream.of(students).map().filter().other(); // No Terminal Operator
    
  3. From Individual Elements
    Student s1 = ;
    Student s2 = ;
       
    Stream<Student> studentStream = Stream.of(s1, s2, ...).map().filter().other(); // No Terminal Operator
    

Outputting Streams

Getting a List out of a Stream

List<SomeClass> list = someStream.map().collect(Collectors.toList());

Getting an Array out of a Stream

// Fill elements into an Array from a Stream
Student[] studentArray = someStream.map(someLambda).toArray(Student[]::new);
String[] strArray = stringStream.filter(...).map(...).toArray(String[]::new);

What Cannot be Done with Streams forEach

  1. Multiple Loops are not possible as forEach is a Terminal operation consuming a Stream.
  2. Local variable modification:
    list.stream().forEach(e -> total += e);
    
    • Do this with map and reduce.
    • Or use the built-in sum method of DoubleStream or IntStream.
  3. Cannot use break or return within forEach loop.

Stream Operations

Source

  • SOURCE: Where the stream comes from.

Intermediate Operations

  • INTERMEDIATE OPERATIONS: Transforms the stream into another stream. STREAMS USE LAZY EVALUATION.
    • map: Returns a stream consisting of the results of applying the given function to the elements of this stream.
    • filter: Selects elements as per the Predicate passed as argument.
    • sorted: Sorts the stream.
  • filter()
  • map()
  • flatMap()
  • distinct()
  • limit()
  • peek()

Terminal Operations

  • TERMINAL OPERATION: Actually produces a result. Stream becomes invalid after terminal operation.
    • collect: Returns the result of the intermediate operations performed on the stream.
    • forEach: Iterates through every element of the stream.
    • reduce: Reduces the elements of a stream to a single value. Takes a BinaryOperator as a parameter.
  • anyMatch()
  • allMatch()
  • noneMatch()
  • collect()
  • count()
  • findAny()
  • findFirst() - returns Optional
  • forEach()
  • min()
  • max()
  • reduce()
  • toArray()

Map vs FlatMap

Employee with Address Object

@Data
class Address {
    private String city;
}

Using map

Scenario: Transforming a list of Employee objects to a list of their names.

class Employee {
    private Address address;//One Address Object
}
public void testMain(List<Employee> employees) {

    //Extract the address of every employhee
    List<Address> addreses = employees.stream()
        .map(Employee::address)
        .collect(Collectors.toList());
}

Using flatMap

Scenario: Given a list of Employee objects, each with a list of Address objects within it, return a single list of all addresses.

class Employee {
    private List<Address> addresses;//Each employee has multiple addresses, including empty
}

public void testFlatMap(List<Employee> employees) {
    List<Address> allAddresses = employees.stream()
        .flatMap(employee -> employee.getAddresses().stream())//Flatmap needs a Stream that it can stitch together.
        .collect(Collectors.toList());
}

Tags:

Categories:

Updated: