Overview :
The Java Stream API facilitates processing sequences of elements, offering operations like filtering, mapping, and reducing. Streams can be used to perform operations in a declarative way, resembling SQL-like operations on data
Key Concepts :
Stream: A sequence of elements supporting sequential and parallel aggregate operations
Intermediate Operations: Operations that return another stream and are lazy (e.g., filter, map)
Terminal Operations: Operations that produce a result or a side-effect and are not lazy (e.g., collect, forEach)
Example Scenario :
Suppose we have a list of Person objects and we want to perform various operations on this list using the Stream API
public class Person { private String name; private int age; private String city; public Person(String name, int age, String city) { this.name = name; this.age = age; this.city = city; } public String getName() { return name; } public int getAge() { return age; } public String getCity() { return city; } @Override public String toString() { return "Person{name='" + name + "', age=" + age + ", city='" + city + "'}"; }
}
Use Cases :
- Filtering
- Mapping
- Collecting
- Reducing
- FlatMapping
- Sorting
- Finding and Matching
- Statistics
Filtering : Filtering allows you to select elements that match a given condition
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List<Person> people = Arrays.asList( new Person("Alice", 30, "New York"), new Person("Bob", 20, "Los Angeles"), new Person("Charlie", 25, "New York"), new Person("David", 40, "Chicago") ); // Filter people older than 25 List<Person> filteredPeople = people.stream().filter(person -> person.getAge() > 25) .collect(Collectors.toList()); filteredPeople.forEach(System.out::println); }
}
Mapping : Mapping transforms each element to another form using a function
public class Main { public static void main(String[] args) { List<Person> people = Arrays.asList( new Person("Alice", 30, "New York"), new Person("Bob", 20, "Los Angeles"), new Person("Charlie", 25, "New York"), new Person("David", 40, "Chicago") ); // Get list of names List<String> names = people.stream() .map(Person::getName) .collect(Collectors.toList()); names.forEach(System.out::println); }
}
Collecting : Collecting gathers the elements of a stream into a collection or other data structures
public class Main { public static void main(String[] args) { List<Person> people = Arrays.asList( new Person("Alice", 30, "New York"), new Person("Bob", 20, "Los Angeles"), new Person("Charlie", 25, "New York"), new Person("David", 40, "Chicago") ); // Collect names into a set Set<String> uniqueCities = people.stream() .map(Person::getCity).collect(Collectors.toSet()); uniqueCities.forEach(System.out::println); }
}
Reducing : Reducing performs a reduction on the elements of the stream using an associative accumulation function and returns an Optional
public class Main { public static void main(String[] args) { List<Person> people = Arrays.asList( new Person("Alice", 30, "New York"), new Person("Bob", 20, "Los Angeles"), new Person("Charlie", 25, "New York"), new Person("David", 40, "Chicago") ); // Sum of ages int totalAge = people.stream() .map(Person::getAge).reduce(0, Integer::sum); System.out.println("Total Age: " + totalAge); }
}
FlatMapping : FlatMapping flattens nested structures into a single stream.
public class Main { public static void main(String[] args) { List<List<String>> namesNested = Arrays.asList( Arrays.asList("John", "Doe"), Arrays.asList("Jane", "Smith"), Arrays.asList("Peter", "Parker") ); List<String> namesFlat = namesNested.stream() .flatMap(List::stream).collect(Collectors.toList()); namesFlat.forEach(System.out::println); }
}
Sorting : Sorting allows you to sort the elements of a stream
public class Main { public static void main(String[] args) { List<Person> people = Arrays.asList( new Person("Alice", 30, "New York"), new Person("Bob", 20, "Los Angeles"), new Person("Charlie", 25, "New York"), new Person("David", 40, "Chicago") ); // Sort by age List<Person> sortedPeople = people.stream() .sorted(Comparator.comparing(Person::getAge)) .collect(Collectors.toList()); sortedPeople.forEach(System.out::println); }
}
Finding and Matching :
Finding and matching operations check the elements of a stream to see if they match a given predicate
public class Main { public static void main(String[] args) { List<Person> people = Arrays.asList( new Person("Alice", 30, "New York"), new Person("Bob", 20, "Los Angeles"), new Person("Charlie", 25, "New York"), new Person("David", 40, "Chicago") ); // Find any person living in New York Optional<Person> personInNY = people.stream() .filter(person -> "NewYork".equals(person.getCity())).findAny(); personInNY.ifPresent(System.out::println); // Check if all people are older than 18 boolean allAdults = people.stream() .allMatch(person -> person.getAge() > 18); System.out.println("All adults: " + allAdults); }
}
Statistics : The Stream API can also be used to perform various statistical operations like counting, averaging, etc.
public class Main { public static void main(String[] args) { List<Person> people = Arrays.asList( new Person("Alice", 30, "New York"), new Person("Bob", 20, "Los Angeles"), new Person("Charlie", 25, "New York"), new Person("David", 40, "Chicago") ); // Count number of people long count = people.stream().count(); System.out.println("Number of people: " + count); // Calculate average age Double averageAge = people.stream() .collect(Collectors.averagingInt(Person::getAge)); System.out.println("Average Age: " + averageAge); }
}
Practical Example :
Here’s a comprehensive example that uses several of the features mentioned above:
import java.util.*;
import java.util.stream.*; public class Main { public static void main(String[] args) { List<Person> people = Arrays.asList( new Person("Alice", 30, "New York"), new Person("Bob", 20, "Los Angeles"), new Person("Charlie", 25, "New York"), new Person("David", 40, "Chicago") ); // Filter, map, sort, and collect List<String> names = people.stream() .filter(person -> person.getAge() > 20) .map(Person::getName) .sorted() .collect(Collectors.toList()); names.forEach(System.out::println); // Find the oldest person Optional<Person> oldestPerson = people.stream() .max(Comparator.comparing(Person::getAge)); oldestPerson.ifPresent(person -> System.out.println("Oldest Person: " + person)); // Group by city Map<String, List<Person>> peopleByCity = people.stream() .collect(Collectors.groupingBy(Person::getCity)); peopleByCity.forEach((city, peopleInCity) -> { System.out.println("People in " + city + ": " + peopleInCity); }); // Calculate total and average age IntSummaryStatistics ageStatistics = people.stream() .collect(Collectors.summarizingInt(Person::getAge)); System.out.println("Total Age: " + ageStatistics.getSum()); System.out.println("Average Age: " + ageStatistics.getAverage()); }
}
Summary :
The Java Stream API is a powerful tool for working with collections and data. It allows for:
Filtering: Select elements based on a condition
Mapping: Transform elements
Collecting: Gather elements into collections or other data structures
Reducing: Combine elements into a single result.
FlatMapping: Flatten nested structures.
Sorting: Order elements.
Finding and Matching: Check elements against a condition.
Statistics: Perform statistical operations.
Understanding these features will help you write cleaner, more concise, and more readable code.
Happy Coding…
Discover more from Coursity
Subscribe to get the latest posts sent to your email.