Edit: Switched pseudo code to a runnable code example.
Edit2: Sonarlink thinks nested streams reduce cognitive complexity, adding the outcome at the end of my question. Thank you @Unmitigated.
I have some objects, lets say; countries, cities, districts. Countries have a list of cities, cities have a list of districts.
I am trying to find a district by name and put that data in a Map.
For example: district name is "Bastille". I try to find it by checking all countries' all cities' all districts. Here is an example code for my logic.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
List<Country> countries = new ArrayList<>();
Country co1 = new Country();
co1.setName("USA");
City cit1 = new City();
cit1.setName("New York");
District d1 = new District();
d1.setName("Manhattan");
District d2 = new District();
d2.setName("Queens");
cit1.setDistricts(Arrays.asList(d1, d2));
co1.setCities(Arrays.asList(cit1));
countries.add(co1);
Country co2 = new Country();
co2.setName("France");
City cit2 = new City();
cit2.setName("Paris");
District d3 = new District();
d3.setName("Bastille");
District d4 = new District();
d4.setName("Montmarte");
cit2.setDistricts(Arrays.asList(d3, d4));
co2.setCities(Arrays.asList(cit2));
countries.add(co1);
countries.add(co2);
HashMap<String, HashMap<String, HashMap<String, String>>> foundMap = new HashMap<>();
boolean isFound = method1(countries, foundMap, "Bastille");
System.out.println(foundMap);
}
public static boolean method1(List<Country> countries, HashMap<String, HashMap<String, HashMap<String, String>>> foundMap, String search) {
for(Country country : countries){
for(City city : country.getCities()){
for(District district : city.getDistricts()){
if(district.getName().equals(search)){
if(!foundMap.containsKey(country.getName())){
foundMap.put(country.getName(), new HashMap<>());
}
if(!foundMap.get(country.getName()).containsKey(city.getName())){
foundMap.get(country.getName()).put(city.getName(), new HashMap<>());
}
foundMap.get(country.getName()).get(city.getName()).put(district.getName(), "Hello from " + district.getName());
return true;
}
}
}
}
return false;
}
}
Here are the sample country, city, district classes;
import java.util.List;
public class Country {
String name;
List<City> cities;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<City> getCities() {
return cities;
}
public void setCities(List<City> cities) {
this.cities = cities;
}
}
import java.util.List;
public class City {
String name;
List<District> districts;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<District> getDistricts() {
return districts;
}
public void setDistricts(List<District> districts) {
this.districts = districts;
}
}
public class District {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
This example creates me the needed HashMap;
{France={Paris={Bastille=Hello from Bastille}}}
In my situation, I need to check method1's return and throw an exception if this district is not found! (System should contain all the districts so "not found" is a failure)
I also need all these country name, city name, district name (which these nested for loops provide me) to create this HashMap entry.
So is there a more elegant way to implement this logic with streams or other tool? (I am ok with this code but sonarlint is unhappy about "cognitive complexity" and I have to find a way to reduce that).
Trying to switch to nested streams but I couldn't figure out how to use stream here. I can use some nested streams but I am not able to return true.
countries.getCities().forEach(city->{
...
...
//if district name is Bastille
return true; //Unexpected Return value because this is inside nested streams, not returning true from the method. If only "return" is used, I only returns from iterable to the outer stream.
}
So is there a better way to make both "check if district exists" and "put all data in a hashmap" logic run? Or only way to solve this is altering the algorithm?
Conclusion: I am switching my code to the Unmitigated's answer.
In my real situation, I had to create 4 "for" loops then an "if" to check if condition is met, then some business logic with hashmap. Sonarlink thinks method's cognitive complexity was 30.
After switching to nested streams with anyMatch, an inside boolean variable to check if condition is met then some business logic with hashmap and return; Sonarlink now says cognitive complexity dropped to 20. I think this is Sonarlink's misjudging because it ignores "stream().anyMatch(..." but counts "for(Obj o: List)".
The problem was: my company did not allow higher level of complexity in methods, after this refactoring Sonarlink is happy.
You can use nested Stream#anyMatch
(which is a short-circuiting operation), but it doesn't really reduce the cognitive complexity.
return Countries.stream().anyMatch(country ->
country.getCities().stream().anyMatch(city ->
city.getDistricts().stream().anyMatch(district -> {
boolean ret = district.getName().equals(search);
if (ret) {
// do something
}
return ret;
})
)
);