3

I want to get one Object with List<> fields from list of objects with no List<> fields.

This is my code:

public class ObjectDTO {
    private List<String> ids;
    private List<Date> times;
    private List<String> names;
    private List<String> details;

public class ObjectDTOHelper {
        private String id;
        private Date time;
        private String name;
        private String detail;
}

I supply List<ObjectDTOHelper> as input and want to get output just an object of ObjectDTO.

Which I can solve with multiple independent stream functions:

List<ObjectDTOHelper> helpers; //about 1000 elements

List<String> ids= helpers.stream().map(h -> h.id).collect(toList());
List<Date> times= helpers.stream().map(h -> h.time).collect(toList());
List<String> names= helpers.stream().map(h -> h.name).collect(toList());
List<String> details= helpers.stream().map(h -> h.detail).collect(toList());

new ObjectDTO(ids, times, name, detail)

Which I think is not the best solution, because I need do that more 100_000 times)

Can you help solve this more optimally?

Arun
  • 2,360
  • 2
  • 17
  • 23
EraCat
  • 65
  • 1
  • 5
  • 1
    If `ObjectDTO` must be constructed with all 4 lists (as opposed to adding elements incrementally) then this is pretty much the best you can do. – Michael Feb 13 '19 at 13:18
  • What is unclear in the question is that from *about 1000 elements*, how do end up with *need do that more 100_000 times*? The complexity of the current solution is `O(n)` as well, an optimization over which could have been a single iteration altogether. In terms of the best solution, what's not efficient is storing those many `List fields` within an object. – Naman Feb 13 '19 at 14:05
  • I agree with you, but that decided "by design" and I'm just looking for better solution. maybe I can still change the structure) – EraCat Feb 13 '19 at 14:17

3 Answers3

3

One possible optimization to add to what you've done already is to preallocate the known exact amount of memory needed for all your lists:

List<ObjectDTOHelper> helpers = ...; //about 1000 elements
int size = helpers.size();

List<String> ids= helpers.stream().map(h -> h.id).collect(toCollection(()->new ArrayList<>(size)));
List<Date> times= helpers.stream().map(h -> h.time).collect(toCollection(()->new ArrayList<>(size)));
List<String> names= helpers.stream().map(h -> h.name).collect(toCollection(()->new ArrayList<>(size)));
List<String> details= helpers.stream().map(h -> h.detail).collect(toCollection(()->new ArrayList<>(size)));

new ObjectDTO(ids, times, name, detail)

If you have too many such fields, you can create your own Collector to pass to collect().

Klitos Kyriacou
  • 10,634
  • 2
  • 38
  • 70
  • IMHO, that just complicated the existing solution – Naman Feb 13 '19 at 14:00
  • @nullpointer I agree it's more complicated, but the OP was specifically asking for something more optimized that what they had. They can measure the performance difference (using e.g. JMH) and decide for themselves whether it's worth the reduced readability. – Klitos Kyriacou Feb 13 '19 at 14:02
  • Okay on that note, I am curious to know how is this solution efficient to that in the question? Any documents around that. – Naman Feb 13 '19 at 14:10
  • @nullpointer it just avoids resizing of the arrays used internally by the ArrayLists. But as I said, the overhead should be measured. Also see related question: https://stackoverflow.com/questions/41069206/an-elegant-way-to-specify-initial-capacity-of-collector-in-java-stream-api – Klitos Kyriacou Feb 13 '19 at 14:56
  • Thank you! it really improved the situation a little) according to my measurements: with size: `1000 avgt 5 3245.702 ± 120.881 ms/op` `10000 avgt 5 29253.296 ± 646.855 ms/op` without size `1000 avgt 5 3323.091 ± 451.716 ms/op` `10000 avgt 5 31517.425 ± 3185.305 ms/op` – EraCat Feb 13 '19 at 15:32
1

Assuming your fields from ObjectDTO is initialized like:

public class ObjectDTO {
    private List<String> ids = new LinkedList<>();
    ...
}

Update: See When to use LinkedList over ArrayList in Java?

You can fill it using IntStream

ObjectDTO objectDTO = new ObjectDTO();

IntStream.range(0, helpers.size())
    .forEach(i -> {
        objectDTO.getIds().add(helpers.get(i).getId());
        objectDTO.getTimes().add(helpers.get(i).getTime());
        objectDTO.getNames().add(helpers.get(i).getName());
        objectDTO.getDetails().add(helpers.get(i).getDetail());
     });
Ruslan
  • 6,090
  • 1
  • 21
  • 36
0

Assuming one CPU/thread your solution is as efficient as it gets in terms of complexity (O(n)) yet it probably has uncessary overhead due to the independent lambdas.

Would it make sense to have an operation to add a helper a a time to ObjectDTO:

class ObjectDTO {
  private List<String> ids;
  private List<Date> times;
  private List<String> names;
  private List<String> details;
  //...
  public void add(ObjectDTOHelper helper) {
      ids.add(helper.id)
      times.add(helper.time);
      names.add(helper.name);
      details.add(helper.detail);
  }
  //...
  public void addAll(Collection<? extends ObjectDTOHelper> helpers) {
     helpers.stream().forEach(this::add);
  }
  //...
}

If ObjectDTO is mean to be a constant class (no modifications after creation allowed), you could simply change those method to private and use addAll within a public constructor that accepts the helper collection.

Also you could take the opportunity to size the list based on the expected and known number of helpers bits that you are going to add to each.

class ObjectDTO {
  // ...
  public ObjectDTO(Collection<? extends ObjectDTOHelper> helpers) {
     int size = helpers.size();

     ids = new ArrayList<>(size);
     names = new ArrayList<>(size);
     // ...
     addAll(helpers);
  }

Also always use ArrayList and never LinkedList... I think the only case that linked perform better than array is if you are going to do repetitive insertions in the middle of a large list and that is not your case.

Valentin Ruano
  • 2,726
  • 19
  • 29