0

I try to convert some for each loops to streams.

I have following object relations:

  • the main object is a collection of sensors
  • each sensor has commands and attributes objects
  • each command object has members objects
  • each attribute object has fields objects

5 Loops

List<SensorDTO> sensorDTOS = new ArrayList();

// Loop 1
for (Sensor sensor: sensors) {
    
    Set<CommandDTO> commandSet = new HashSet<>();
    // Loop 2
    for (Command c : sensor.getCommands()) {
        Set<MemberDTO> memberSet = new HashSet<>();
        // Loop 3
        for (Member m : c.getMembers()) {
            memberSet.add(new MemberDTO(m.getName()));
        }
        commandSet.add(new CommandDTO(c.getName(),memberSet));
    }
    
    Set<AttributeDTO> attributeSet = new HashSet<>();
    // Loop 4
    for (Attribute a : sensor.getAttributes()) {
        Set<FieldDTO> fieldSet = new HashSet<>();
        // Loop 5
        for (Field f : a.getFields()) {
            fieldSet.add(new FieldDTO(f.getName()));
        }
        attributeSet.add(new AttributeDTO( a.getName(), fieldSet));
    }

    SensorDTO sensorDTO = new SensorDTO(attributeSet, commandSet);
    sensorDTOS.add(sensorDTO);
}

Attempt using stream().forEach

The only I have accomplished is to print the inner object.

sensors.stream()
    .forEach(s-> { 
        s.getCommands().stream().forEach( c -> {
            c.getMembers().stream().forEach( m -> {
                 System.out.println(m);
            });
        });
    });

How to make a list of sensors to return as a list of sensorDTOS?

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
vag
  • 3
  • 1
  • 1
    It would be match better if you would share your domain classes `Sensor`, `CommandDTO`, `MemberDTO` etc. (only fields used in this code), it would be easier to follow the logic. Please **update** the Question, use *edit* button or this [link](https://stackoverflow.com/posts/74842266/edit). – Alexander Ivanchenko Dec 18 '22 at 15:30
  • 1
    Thanks hc_dev you make the question more clearly. I see how need to be a question in the future – vag Dec 18 '22 at 16:37

3 Answers3

0
List<SensorDTO> sensorDTOS = sensors.stream()
.map(sensor -> {
    Set<CommandDTO> commandSet = sensor.getCommands().stream()
        .map(command -> {
            Set<MemberDTO> memberSet = command.getMembers().stream()
                .map(member -> new MemberDTO(member.getName()))
                .collect(Collectors.toSet());
            return new CommandDTO(command.getName(), memberSet);
        })
        .collect(Collectors.toSet());
    Set<AttributeDTO> attributeSet = sensor.getAttributes().stream()
        .map(attribute -> {
            Set<FieldDTO> fieldSet = attribute.getFields().stream()
                .map(field -> new FieldDTO(field.getName()))
                .collect(Collectors.toSet());
            return new AttributeDTO(attribute.getName(), fieldSet);
        })
        .collect(Collectors.toSet());
    return new SensorDTO(attributeSet, commandSet);
})
.collect(Collectors.toList());
jawath
  • 439
  • 7
  • 22
0

Use a mapper class

Then split 5 responsibilities into map-methods:

  1. map Sensor to SensorDTO (former Loop 1)
  2. map Sensor#commands to Set<CommandDTO> (former Loop 2)
  3. map Command#members to Set<MemberDTO> (former Loop 3)
  4. map Sensor#attributes to Set<AttributeDTO> (former Loop 4)
  5. map Attribute#fields to Set<FieldDTO> (former Loop 5)
class Mapper {

    // former Loop 1
    static SensorDTO toDTO(Sensor sensor) {
        return new SensorDTO(
            Mapper.toDTO(sensor.getAttributes()),
            Mapper.toDTO(sensor.getCommands())
        );
    }

    // former Loop 2
    static Set<CommandDTO> toDTO(Collection<Command> commands) {
        return commands.stream()
            .map(Mapper::toCommandDTO)
            .collect(Collectors.toSet());
    }

    static CommandDTO toCommandDTO(Command c) {
        return new CommandDTO(c.getName(), Mapper.toDTO(c.getMembers()));
    }

    // former Loop 3        
    static Set<MemberDTO> toDTO(Collection<Member> members) {
        return members.stream()
            .map(m -> new MemberDTO(m.getName()))
            .collect(Collectors.toSet());
    }

    // former Loop 4
    static Set<AttributeDTO> toDTO(Collection<Attribute> attributes) {
        return attributes.stream()
            .map(Mapper::toAttributeDTO)
            .collect(Collectors.toSet());
    }

    static AttributeDTO toAttributeDTO(Attribute a) {
        return new AttributeDTO(a.getName(), Mapper.toDTO(a.getFields()));
    }

    // former Loop 5
    static Set<FieldDTO> toDTO(Collection<Field> fields) {
        return fields.stream()
            .map(f -> new FieldDTO(f.getName()))
            .collect(Collectors.toSet());
    }
    
}

Note: For illustrative brevity I omitted any access modifiers like public.

Then you can use the mapper simply like this:

List<SensorDTO> dtos = sensors.stream()
    .map(Mapper::toDTO)
    .collect(Collectors.toList());

Why the mapper class?

Instead of using one huge stream we broke it into small parts, each with its own responsibility to map. Benefits are readability and maintainability. Then it is also easier to test.

Usually you would use an object-relational mapping (ORM) framework to convert from DTO to domain model and back (like MapStruct, Orika, ModelMapper, Dozer).

This Mapper class with its decomposed mapping methods is a good preparation to ease migration to a real ORM framework in the future.

See also:

hc_dev
  • 8,389
  • 1
  • 26
  • 38
0

The very first important thing to point out the imperative code with lots of nested loops presented in the Question as well attempts to re-implement it with Streams that can be observed in some other Answers are glaring examples of the violation of the first GRASP principle the Information expert principle.

Using the principle of information expert, a general approach to assigning responsibilities is to look at a given responsibility, determine the information needed to fulfill it, and then determine where that information is stored.

This will lead to placing the responsibility on the class with the most information required to fulfill it.

In other words, the class that hold the information should be responsible for processing it.

The advantage of this approach is better maintainability of code because data is being processed in one place (in the owning class) in isolation (i.e. without interfering with other code). Such logic is easy to extend, reuse and to test.

It worth to point out that a good alternative to would make use of the frameworks like MapStruct to manage the logic of transformation between domain classes and DTOs.

Here's how the method for transforming a List<Sensor> into a List<SensorDTO> might look like:

public static List<SensorDTO> toSensorDTO(List<Sensor> sensors) {
    
    return sensors.stream().map(Sensor::toDTO).toList();
}

Each class does its potion of work, transforming the data it owns via method toDto().

public static class Sensor {
    private Set<Command> commands;
    private Set<Attribute> attributes;

    public SensorDTO toDTO() {
    
        return Stream.of(this)
            .collect(Collectors.teeing(
                Collectors.flatMapping(s -> s.getCommands().stream()
                    .map(Command::toDTO), Collectors.toSet()),
                Collectors.flatMapping(s -> s.getAttributes().stream()
                    .map(Attribute::toDTO), Collectors.toSet()),
                SensorDTO::new
            ));
    }
}

public static class Command {
    private String name;
    private Set<Member> members;
    
    public CommandDTO toDTO() {
        Set<MemberDTO> memberDTOSet = members.stream()
            .map(Member::toDTO)
            .collect(Collectors.toSet());
        
        return new CommandDTO(name, memberDTOSet);
    }
}

public static class Member {
    private String name;

    public MemberDTO toDTO() {
        return new MemberDTO(name);
    }
}

public static class Attribute {
    private String name;
    private Set<Field> members;

    public AttributeDTO toDTO() {
        Set<FieldDTO> fieldDTOSet = members.stream()
            .map(Field::toDTO)
            .collect(Collectors.toSet());
    
        return new AttributeDTO(name, fieldDTOSet);
    }
}

public static class Field {
    private String name;
    
    public FieldDTO toDTO() {
        return new FieldDTO(name);
    }
}
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46