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...
Top comments (0)