1

I want to rewrite the following Java 8 code to be Java 7 compliant:

System.out.println("items: " + stringList.stream().collect(Collectors.joining(", ")));

A naive way is:

System.out.print("items: ");
String joiner = "";
for (String item : stringList) {
  System.out.print(joiner + item);
  joiner = ", ";
}
System.out.println();

What are some alternative patterns to accomplish this task? For example, could one use a command pattern somehow passing print() encapsulated as an object?

Ray Zhang
  • 1,411
  • 4
  • 18
  • 36
  • 1
    Or simply take a look at the source code of the JDK 8 [StringJoiner](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/util/StringJoiner.java) class and write a JDK 7 version of it – Edwin Dalorzo May 05 '17 at 18:57
  • 1
    Your code is nearly fine. The only change I would make is to `System.out.print` `joiner` and `item` separately, to avoid the unnecessary string creation. `StringBuilder` is less efficient here, since you would be simply buffering the chars, to create a string, to write to another buffer of chars; instead, just write to that final buffer directly (as you are already doing). – Andy Turner May 05 '17 at 19:07

4 Answers4

2

If you have guava on the classpath (and you really should IMO), than it has the same fluent way of joining, but compatible with jdk-7 (obviously you need a version of guava that is jdk-7 compatible):

 String joined = Joiner.on(",").join(stringList);
Eugene
  • 117,005
  • 15
  • 201
  • 306
0
StringBuilder sb = new StringBuilder("items: ");
for (String item : stringList) {
  sb.append(item).append(", ");
}
if (!stringList.isEmpty()) {
    sb.setLength(sb.length() - 2);
}
System.out.println(sb.toString());

Comments:

  1. The StringBuilder is initialized with the constructor argument
  2. append returns a StringBuilder so you can chain them
  3. The last two chars need to be deleted to avoid trailing ", "
  4. stringList may be empty so only delete the trailing comma if it isn't
  5. This is likely more efficient as you avoid a lot of temporary String creation and copying.
Manos Nikolaidis
  • 21,608
  • 12
  • 74
  • 82
  • 1
    Alternatively, one could append the colon seperator first, if `!sb.isEmpty()` and then always append the next item. – Edwin Dalorzo May 05 '17 at 19:01
  • Thanks. I edited my original post to remove the looping String creation as it was not the part I meant to draw attention to. My question is really how to accomplish the given task using an alternative design pattern. For example, a command pattern passing an encapsulated function as a variable. – Ray Zhang May 05 '17 at 19:02
0

Almost the same, just avoiding concatenation:

System.out.print("items: ");
boolean first = true;
for (String item : stringList) {
    if (! first)
        System.out.print(", ");
    System.out.print(item);
    first = false;
}
System.out.println();

Note: the meaning of the boolean could be inverted so we don't need to use negation, but I prefer it that way for readability.

user85421
  • 28,957
  • 10
  • 64
  • 87
0

Note that your original Java 8 code

System.out.println("items: " + stringList.stream().collect(Collectors.joining(", ")));

can be simplified to

System.out.println("items: " + String.join(", ", stringList));

In Java 7, you would have to use a StringBuilder to do the same efficiently, but you may encapsulate it in a utility class, similar to Java 8’s StringJoiner. Using a class with the same API allows to create Java 7 compatible code that is still easy to adapt to use the Java 8 counterpart.

However, it’s rather unlikely that this is the most performance critical part of your application, hence, you could use a pragmatic approach:

String s = stringList.toString();
System.out.append("items: ").append(s, 1, s.length()-1).println();

This simply doesn’t print the [ and ] of the string representation, resulting in the desired comma separated list. While this string representation is not mandated, it would be hard to find an example of a useful collection implementation that doesn’t adhere to this convention (and you would know if you used such a class).

Regarding your updated question referring to something like “command pattern”, well, that doesn’t have to do anything with the Stream code of your question, but there are already abstractions, like the one used in the code above.

You could create a method like

static <A extends Appendable> A join(A target, CharSequence sep, List<?> data) {
    try {
        CharSequence currSep = "";
        for(Object item: data) {
            target.append(currSep).append(item.toString());
            currSep = sep;
        }
        return target;
    } catch(IOException ex) {
        throw new IllegalStateException(ex);
    }
}

which offers some flexibility, e.g. you could use it to acquire a String:

String s = join(new StringBuilder(), ", ", stringList).toString();
System.out.println("items: " + s);

or just print:

join(System.out.append("items: "), ", ", stringList).println();
Holger
  • 285,553
  • 42
  • 434
  • 765