Monday, April 21, 2025

Streams in Java8

 In Java, the Stream API was introduced in Java 8 to provide a modern way of working with collections and other data sources. Streams allow you to perform operations like filtering, mapping, reducing, and collecting data from collections in a declarative way.

1. Creating a Stream

You can create a stream from a collection (e.g., List, Set) using the .stream() method.

import java.util.List;

import java.util.stream.Stream; 

public class StreamExample {

    public static void main(String[] args) {

        List<String> names = List.of("Alice", "Bob", "Charlie", "David"); 

        // Create a stream from the List

        Stream<String> nameStream = names.stream();         

        // Print the elements

        nameStream.forEach(System.out::println);

    }

}

2. Stream Operations

The Stream API supports a wide range of operations. These operations can be classified into intermediate and terminal operations. 

Intermediate Operations

Intermediate operations are lazy (they don’t process the data until a terminal operation is invoked). They return a new stream and can be chained.

  • filter(): Filters elements based on a condition.

  • map(): Transforms elements.

  • sorted(): Sorts the stream.

  • distinct(): Removes duplicates.

  • peek(): Allows you to perform a non-interfering action (like logging) without modifying the stream.

Example of filter() and map():

import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie", "David");
        // Filter names starting with 'A' and convert them to uppercase
        List<String> filteredNames = names.stream()
                                          .filter(name -> name.startsWith("A"))
                                          .map(String::toUpperCase)
                                          .collect(Collectors.toList());

        System.out.println(filteredNames);  // Output: [ALICE]
    }
}

Terminal Operations

Terminal operations trigger the processing of the stream and produce a result, such as a value or side-effect.

  • forEach(): Iterates over each element of the stream.

  • collect(): Collects the elements into a collection (like a List, Set).

  • reduce(): Performs a reduction on the stream (like summing values).

  • count(): Returns the number of elements in the stream.

  • anyMatch(), allMatch(), noneMatch(): Matches elements based on a condition.

    Example of collect() and reduce():

    import java.util.List;

    import java.util.Optional;

    import java.util.stream.Collectors; 

    public class StreamExample {

        public static void main(String[] args) {

            List<Integer> numbers = List.of(1, 2, 3, 4, 5); 

            // Collecting to a List

            List<Integer> evenNumbers = numbers.stream().filter(n -> n % 2 == 0)

                                               .collect(Collectors.toList());

            System.out.println(evenNumbers);  // Output: [2, 4] 

            // Using reduce() to sum the numbers

            Optional<Integer> sum = numbers.stream().reduce((a, b) -> a + b);

            sum.ifPresent(System.out::println);  // Output: 15

        }

    }

    3. Chaining Stream Operations

    One of the most powerful features of the Stream API is the ability to chain multiple operations together.

    import java.util.List;

    import java.util.stream.Collectors; 

    public class StreamExample {

        public static void main(String[] args) {

            List<String> names = List.of("Alice", "Bob", "Charlie", "David"); 

            // Chaining operations: filter, map, sorted, collect

            List<String> result = names.stream()

                                    .filter(name -> name.length() > 3)    // Filter names with length > 3

                                       .map(String::toUpperCase)              // Convert to uppercase

                                       .sorted()                              // Sort alphabetically

                                       .collect(Collectors.toList());         // Collect into a List

            System.out.println(result);  // Output: [ALICE, CHARLIE, DAVID]

        }

    }

4. Parallel Streams

The Stream API also supports parallel processing. Using parallelStream() allows you to process elements in parallel across multiple threads, which can improve performance for large collections.

import java.util.List;

public class StreamExample {

    public static void main(String[] args) {

        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 

        // Parallel stream to sum numbers

        int sum = numbers.parallelStream()

                         .mapToInt(Integer::intValue)

                         .sum(); 

        System.out.println("Sum: " + sum);  // Output: 55

    }

}

5. Short-Circuiting Operations

Some operations are short-circuiting, meaning they may not process all elements of the stream.

  • anyMatch(): Returns true if any element matches the condition.

  • allMatch(): Returns true if all elements match the condition.

  • noneMatch(): Returns true if no elements match the condition.

  • findFirst(): Returns the first element in the stream.

  • findAny(): Returns any element (could be parallel) in the stream.

import java.util.List; 
public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5); 
        // Short-circuiting example with anyMatch()
        boolean hasEven = numbers.stream()
                                 .anyMatch(n -> n % 2 == 0); 
        System.out.println("Has even number? " + hasEven);  // Output: true
    }
}

Summary of Key Methods

  • Intermediate Operations: filter(), map(), sorted(), distinct(), peek()

  • Terminal Operations: forEach(), collect(), reduce(), count(), anyMatch(), allMatch()

  • Parallel Streams: parallelStream()

  • Short-Circuiting Operations: anyMatch(), allMatch(), findFirst()

The Stream API is highly versatile, and by combining different operations, you can perform a wide range of data transformations and processing tasks on collections in Java.

Comparator and compare in java with example using streams Java 8 

In Java, Comparator is an interface that is used to compare two objects. It is often used to define custom sorting orders for collections, and it's especially useful when working with the Stream API. The compare() method is part of the Comparator interface and is used to compare two objects.

Here's how they work:

  • compare(T o1, T o2): This method compares two objects o1 and o2 of type T. It returns:

  • A negative integer if o1 is less than o2

  • Zero if o1 is equal to o2

  • A positive integer if o1 is greater than o2. 

    Using Comparator with Collections

    The Comparator interface can be used to sort or order elements in a collection. Here's a basic example of using Comparator with the Stream API.

    Example 1: Sorting a List of Strings using a Comparator

import java.util.*;
import java.util.stream.*;

public class ComparatorExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); 
        // Sort names by length using Comparator and Streams
        List<String> sortedByLength = names.stream()
                                           .sorted(Comparator.comparingInt(String::length))  // Using compare() via comparingInt
                                           .collect(Collectors.toList());

        System.out.println("Sorted by length: " + sortedByLength);
    }
}

Sorted by length: [Bob, Alice, David, Charlie]

In this example:

  • We use Comparator.comparingInt(String::length) to create a comparator that compares the strings based on their length.

  • The sorted() method of the stream uses this comparator to order the strings by length.

2. Using Comparator with compare()

You can also directly create a Comparator by implementing the compare() method. Here's an example of comparing Person objects based on their age field.

Example 2: Custom Comparator with compare() Method

import java.util.*;
import java.util.stream.*;

class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

public class ComparatorExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35),
            new Person("David", 28)
        );

        // Custom Comparator to compare by age
        Comparator<Person> byAge = new Comparator<Person>() {
            @Override
            public int compare(Person p1, Person p2) {
                return Integer.compare(p1.getAge(), p2.getAge()); // Using compare() for integers
            }
        };

        // Sort people by age using the comparator and streams
        List<Person> sortedByAge = people.stream()
                                         .sorted(byAge)
                                         .collect(Collectors.toList());

        System.out.println("Sorted by age: " + sortedByAge);
    }
}

Output: 
Sorted by age: [Bob (25), David (28), Alice (30), Charlie (35)] 

In this example:

  • We define a custom Comparator<Person> using the compare() method to compare Person objects based on their age.

  • The Integer.compare() method is used in the compare() method to compare the ages, which is a clean and safe way to compare integer values.

3. Using Comparator with reversed() and Other Methods

You can also use Comparator's built-in methods like reversed(), thenComparing(), and others to modify how comparisons are done.

 Example 3: Reversing Order and Chaining Comparators

import java.util.*;

import java.util.stream.*;

class Person {

    String name;

    int age; 

    public Person(String name, int age) {

        this.name = name;

        this.age = age;

    } 

    public int getAge() {

        return age;

    } 

    public String getName() {

        return name;

    } 

    @Override

    public String toString() {

        return name + " (" + age + ")";

    }

}


public class ComparatorExample {

    public static void main(String[] args) {

        List<Person> people = Arrays.asList(

            new Person("Alice", 30),

            new Person("Bob", 25),

            new Person("Charlie", 35),

            new Person("David", 28)

        );

        // Sort first by age, then by name in reversed order

        List<Person> sortedByAgeAndName = people.stream()

                                                 .sorted(Comparator.comparingInt(Person::getAge)

                                                                   .thenComparing(Person::getName)

                                                                   .reversed())  // Reversing the entire order

                                                 .collect(Collectors.toList()); 

        System.out.println("Sorted by age and reversed name: " + sortedByAgeAndName);

    }

}

Sorted by age and reversed name: [Charlie (35), Alice (30), David (28), Bob (25)]

In this example:

  • We first sort by age using Comparator.comparingInt(Person::getAge).

  • We then chain another comparator using thenComparing(Person::getName) to sort by name.

  • Finally, we reverse the order of the entire comparator using .reversed().

4. Using Comparator with comparing() for Complex Fields

You can use the Comparator.comparing() method to simplify comparisons, especially when dealing with more complex types like String or Date.

Example: 
import java.util.*;
import java.util.stream.*;


class Person {

    String name;

    int age;

    String city; 

    public Person(String name, int age, String city) {

        this.name = name;

        this.age = age;

        this.city = city;

    }

     public int getAge() {

        return age;

    }

    public String getName() {

        return name;

    } 

    public String getCity() {

        return city;

    }

    @Override

    public String toString() {

        return name + " (" + age + ", " + city + ")";

    }

}


public class ComparatorExample {

    public static void main(String[] args) {

        List<Person> people = Arrays.asList(

            new Person("Alice", 30, "New York"),

            new Person("Bob", 25, "Los Angeles"),

            new Person("Charlie", 35, "Chicago"),

            new Person("David", 28, "San Francisco")

        );

        // Sort by city name then by age

        List<Person> sortedByCityAndAge = people.stream()

                                                 .sorted(Comparator.comparing(Person::getCity)

                                                                   .thenComparingInt(Person::getAge))

                                                 .collect(Collectors.toList());

        System.out.println("Sorted by city and age: " + sortedByCityAndAge);

    }

}

Sorted by city and age: [Bob (25, Los Angeles), Charlie (35, Chicago), David (28, San Francisco), Alice (30, New York)]

In this example:

  • We first sort by city using Comparator.comparing(Person::getCity).

  • We then chain a second comparator to sort by age using thenComparingInt(Person::getAge).

Summary of Key Methods:

  • compare(T o1, T o2): Compares two objects of type T.

    • Comparator.comparing(): A static method to create a comparator based on a specific field or property.

    • thenComparing(): Allows chaining comparators to compare by multiple criteria.

    • reversed(): Reverses the order of a comparator.

    • comparingInt(), comparingDouble(), comparingLong(): Specialized methods for comparing primitive types.

    By using these methods, you can easily sort or compare complex data structures using Java's Stream API and Comparator interface.




No comments:

Post a Comment

Java 9 and Java11 and Java17, Java 21 Features

 Java 9 and Java11 and Java17 features along with explanation and examples in realtime scenarios Here's a detailed breakdown of Java 9, ...