7 Tips for Mastering Java Streams

4 min read
May 28, 2024

Streams were introduced many years ago; however, as Java developers, many of us still haven’t fully harnessed the power of this versatile tool. In this article, you will find some valuable tricks that can be saved as a reference for incorporating into your next project.

In the examples that follow, I will be using these classes:

@Getter
class Company {
private String name;
private Address address;
private List<Person> personList;
}

@Getter
class Person {
private Long id;
private String name;
}

@Getter
class Address {
private String street;
private City city;
}

@Getter
class City {
private String name;
private State state;
}

@Getter
class State{
private String name;
}

1. Simplify map by using method reference

Here is the code that retrieves the city names from the companies' addresses:

public List<String> getCityNames(List<Company> companyList){
return companyList.stream()
.map(company -> company.getAddress().getCity().getName())
.toList();
}

Can be replaced by this to be more readable:

public List<String> getCityNames(List<Company> companyList){
return companyList.stream()
.map(Company::getAddress)
.map(Address::getCity)
.map(City::getName)
.toList();
}

2. Null checks

The code above with null checks:

public List<String> getCityNames(List<Company> companyList){
return companyList.stream()
.map(Company::getAddress)
.filter(Objects::nonNull)
.map(Address::getCity)
.filter(Objects::nonNull)
.map(City::getName)
.filter(Objects::nonNull)
.toList();
}

3. Stream of stream to stream

The code below retrieves the list of individuals from every company.

public List<Person> getAllPerson(List<Company> companyList){
// Make a list of list of Person
List<List<Person>> partialResult = companyList.stream()
.map(Company::getPersonList)
.toList();

// For each list of Person, add to the result
List<Person> result = new ArrayList<>();
partialResult.forEach(result::addAll);

return result;
}

The same can be done as follow:

public List<Person> getAllPerson(List<Company> companyList){
return companyList.stream()
.map(Company::getPersonList) // It returns a Stream<List<Person>>
.flatMap(List::stream) // It returns a Stream<Person>
.toList();
}

4. Grouping by an attribute

The code below generates a Map that associates each city with a List of Company.

public Map<City,List<Company>> getCompaniesByCity(List<Company> companyList){
return companyList.stream()
.collect(Collectors.groupingBy(company -> company.getAddress().getCity()));
}

5. Verify the presence of an item in the stream.

The code below verifies the presence of a company in a specific city:

public boolean hasCompanyInCity(List<Company> companyList, String cityName){
return companyList.stream()
.map(Company::getAddress)
.map(Address::getName)
.anyMatch(cityName::equals);
}

The same principle applies to noneMatch when you need to verify that no companies exist in a particular city:

public boolean hasNoCompanyInCity(List<Company> companyList, String cityName){
return companyList.stream()
.map(Company::getAddress)
.map(Address::getName)
.noneMatch(cityName::equals);
}

6. Logging

Utilize the peek method to generate a log for each city name that is returned:

public List<String> getCityNames(List<Company> companyList){
return companyList.stream()
.map(Company::getAddress)
.map(Address::getCity)
.map(City::getName)
.peek(cityName -> log.info(cityName))
.toList();
}

7. Get unique city names

Employ distinct to eliminate any repeated city names from a stream:

public List<String> getUniqueCityNames(List<Company> companyList){
return companyList.stream()
.map(Company::getAddress)
.map(Address::getCity)
.map(City::getName)
.distinct()
.toList();
}

Read more in Tech