Previous: Lambda Expressions and Functional Interfaces Next: Optional Class for Null Handling

Module 3: Stream API and Collections

Learning Objectives

3.1 Introduction to Streams

What are Streams?

Streams are a sequence of elements supporting sequential and parallel aggregate operations. They provide a high-level abstraction for processing collections of data in a functional style.

Stream Characteristics

Creating Streams

// From collections
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();

// From arrays
String[] array = {"a", "b", "c"};
Stream<String> streamFromArray = Arrays.stream(array);

// Using Stream.of()
Stream<String> streamOf = Stream.of("a", "b", "c");

// Infinite streams
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2);
Stream<Double> randomNumbers = Stream.generate(Math::random);

3.2 Intermediate Operations

Filter Operation

Filter elements based on a predicate condition.

@Test
public void testFilterOperation() {
    List<User> users = getUserList();
    
    List<User> activeUsers = users.stream()
        .filter(User::isActive)
        .filter(user -> user.getAge() > 18)
        .collect(Collectors.toList());
    
    assertTrue("Should have active adult users", !activeUsers.isEmpty());
}

Map Operation

Transform elements from one type to another.

@Test
public void testMapOperation() {
    List<User> users = getUserList();
    
    List<String> userEmails = users.stream()
        .map(User::getEmail)
        .collect(Collectors.toList());
    
    List<String> upperCaseNames = users.stream()
        .map(User::getName)
        .map(String::toUpperCase)
        .collect(Collectors.toList());
    
    assertEquals(users.size(), userEmails.size());
}

FlatMap Operation

Flatten nested structures into a single stream.

@Test
public void testFlatMapOperation() {
    List<Department> departments = getDepartments();
    
    List<User> allUsers = departments.stream()
        .flatMap(dept -> dept.getUsers().stream())
        .collect(Collectors.toList());
    
    List<String> allSkills = departments.stream()
        .flatMap(dept -> dept.getUsers().stream())
        .flatMap(user -> user.getSkills().stream())
        .distinct()
        .collect(Collectors.toList());
    
    assertTrue("Should have users from all departments", !allUsers.isEmpty());
}

3.3 Terminal Operations

Collect Operation

Collect stream elements into various data structures.

@Test
public void testCollectOperations() {
    List<User> users = getUserList();
    
    // Collect to List
    List<String> names = users.stream()
        .map(User::getName)
        .collect(Collectors.toList());
    
    // Collect to Set
    Set<String> uniqueRoles = users.stream()
        .map(User::getRole)
        .collect(Collectors.toSet());
    
    // Collect to Map
    Map<String, User> userMap = users.stream()
        .collect(Collectors.toMap(User::getEmail, user -> user));
    
    // Group by role
    Map<String, List<User>> usersByRole = users.stream()
        .collect(Collectors.groupingBy(User::getRole));
    
    assertFalse("Should have user names", names.isEmpty());
    assertFalse("Should have unique roles", uniqueRoles.isEmpty());
}

Reduce Operation

Combine stream elements into a single result.

@Test
public void testReduceOperations() {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    
    // Sum using reduce
    Optional<Integer> sum = numbers.stream()
        .reduce((a, b) -> a + b);
    
    // Sum with identity
    Integer sumWithIdentity = numbers.stream()
        .reduce(0, (a, b) -> a + b);
    
    // Find maximum
    Optional<Integer> max = numbers.stream()
        .reduce(Integer::max);
    
    assertEquals(15, sumWithIdentity.intValue());
    assertTrue("Sum should be present", sum.isPresent());
    assertEquals(15, sum.get().intValue());
}

3.4 Parallel Streams

When to Use Parallel Streams

Parallel streams can improve performance for CPU-intensive operations on large datasets.

@Test
public void testParallelStreams() {
    List<Integer> largeList = IntStream.rangeClosed(1, 1000000)
        .boxed()
        .collect(Collectors.toList());
    
    // Sequential processing
    long startTime = System.currentTimeMillis();
    long sequentialSum = largeList.stream()
        .mapToLong(Integer::longValue)
        .sum();
    long sequentialTime = System.currentTimeMillis() - startTime;
    
    // Parallel processing
    startTime = System.currentTimeMillis();
    long parallelSum = largeList.parallelStream()
        .mapToLong(Integer::longValue)
        .sum();
    long parallelTime = System.currentTimeMillis() - startTime;
    
    assertEquals(sequentialSum, parallelSum);
    System.out.println("Sequential time: " + sequentialTime + "ms");
    System.out.println("Parallel time: " + parallelTime + "ms");
}

3.5 Practical Test Automation Examples

Test Data Processing

@Test
public void testDataProcessing() {
    List<TestResult> testResults = getTestResults();
    
    // Calculate pass rate
    double passRate = testResults.stream()
        .mapToDouble(result -> result.isPassed() ? 1.0 : 0.0)
        .average()
        .orElse(0.0);
    
    // Find failed tests
    List<String> failedTests = testResults.stream()
        .filter(result -> !result.isPassed())
        .map(TestResult::getTestName)
        .collect(Collectors.toList());
    
    // Group by test suite
    Map<String, List<TestResult>> resultsBySuite = testResults.stream()
        .collect(Collectors.groupingBy(TestResult::getSuiteName));
    
    assertTrue("Pass rate should be above 80%", passRate > 0.8);
    System.out.println("Failed tests: " + failedTests);
}

Web Element Processing

@Test
public void testWebElementProcessing() {
    List<WebElement> elements = driver.findElements(By.className("test-item"));
    
    // Find visible elements
    List<WebElement> visibleElements = elements.stream()
        .filter(WebElement::isDisplayed)
        .collect(Collectors.toList());
    
    // Get all text content
    List<String> textContents = elements.stream()
        .filter(WebElement::isDisplayed)
        .map(WebElement::getText)
        .filter(text -> !text.isEmpty())
        .collect(Collectors.toList());
    
    // Check if any element contains specific text
    boolean hasExpectedText = elements.stream()
        .anyMatch(element -> element.getText().contains("Expected Text"));
    
    assertFalse("Should have visible elements", visibleElements.isEmpty());
    assertTrue("Should contain expected text", hasExpectedText);
}
Previous: Lambda Expressions and Functional Interfaces Next: Optional Class for Null Handling