How to effectively concatenate multiple String variables each from the list of objects?

Viewed 623

I would like to go through a list of objects and concatenate several Strings for each variable:

final StringBuilder xBuilder = new StringBuilder();
final StringBuilder yBuilder = new StringBuilder();
final StringBuilder zBuilder = new StringBuilder();

someObjects.forEach(obj -> {
  A a = obj.getA();
  xBuilder.append(obj.getX() + ",");
  yBuilder.append(obj.getY() + ",");
  zBuilder.append(a.getZ() + ",");
});

Is there any more effective way than creating several StringBuilders for each variable?

4 Answers

Well, you do want 3 separate Strings here, so looks just fine to create 3 StringBuilder(s).. You could alternatively use a StringJoiner though:

StringJoiner xJoiner = new StringJoiner(",");
someObjects.forEach(obj -> {
    xJoiner.join(obj.getX();
});

Is there any more effective way than creating several StringBuilders for each variable?

There is no more effective way than creating a something for each of the variables.

Don't use forEach here: there is no advantage over a plain old loop (see here for a question about the differences):

final StringBuilder xBuilder = new StringBuilder();
final StringBuilder yBuilder = new StringBuilder();
final StringBuilder zBuilder = new StringBuilder();

for (SomeObject obj : someObjects) {
  A a = obj.getA();

  // Use two appends, rather than concatentating then appending.
  xBuilder.append(obj.getX()).append(",");

  yBuilder.append(obj.getY()).append(",");
  zBuilder.append(a.getZ()).append(",");

}

Collectors.joining() method will use StringJoiner internally to append Strings with passed delimiter like here ','.

someObjects.stream().collect(Collectors.joining(","));

As you're concatenating 3 different properties of the object, you need to use 3 different StringBuilder instances (or StringJoiner). You could iterate the list of objects 3 times, one for each property, or if you only want to iterate once (despite traversing a list is extremely cheap), you could use either your own approach or a traditional foreach.

Or you could also use the code I'm showing below, which is semantically equivalent to the other answers, except that it allows to decouple the concatenation process from the iteration (if you need this at all):

StringBuilder xBuilder = new StringBuilder(); // no need to use final since
StringBuilder yBuilder = new StringBuilder(); // these instances are 
StringBuilder zBuilder = new StringBuilder(); // effectively final

Consumer<SomeObject> action = ((Consumer<SomeObject>) obj -> 
    xBuilder.append(obj.getX() + ","))
        .andThen(obj -> yBuilder.append(obj.getY() + ","))
        .andThen(obj -> zBuilder.append(obj.getA().getZ() + ","));

Then, you can pass around action (i.e. to some method that doesn't have visibility over the builders) and simply do:

someObjects.forEach(action);
Related