Skip to content

Consider replacing StringUtil concatenation with String.join() or Stream API #68

@sfloess

Description

@sfloess

Description

StringUtil.concatWithSeparator() implements custom string concatenation logic that could be simplified using Java's built-in String.join() or Stream API.

Current Implementation (StringUtil.java, lines 162-181)

public static StringBuilder concatWithSeparator(final StringBuilder stringBuilder, 
                                                final boolean isSeparatorAtEnd, 
                                                final String separator, 
                                                Object... objs) {
    Objects.requireNonNull(separator, "Separator cannot be null");
    ArrayUtil.ensureArray(objs, "Must have a list of objects to concat!");

    for (int index = 0; index < objs.length; index++) {
        stringBuilder.append(objs[index]);

        if (isSeparatorAppendable(separator, index, objs)) {
            stringBuilder.append(separator);
        }
    }

    if (isSeparatorAtEnd) {
        stringBuilder.append(separator);
    }

    return stringBuilder;
}

Simpler Alternative with String.join()

For Simple Cases (most common usage)

// Current
String result = StringUtil.concatWithSeparator(",", "foo", "bar", "baz");

// Could be
String result = String.join(",", "foo", "bar", "baz");

For StringBuilder Cases

public static StringBuilder concatWithSeparator(final StringBuilder stringBuilder, 
                                                final boolean isSeparatorAtEnd, 
                                                final String separator, 
                                                Object... objs) {
    Objects.requireNonNull(separator, "Separator cannot be null");
    ArrayUtil.ensureArray(objs, "Must have a list of objects to concat!");

    // Convert objects to strings and join
    String joined = Arrays.stream(objs)
        .map(Object::toString)
        .collect(Collectors.joining(separator));
    
    stringBuilder.append(joined);
    
    if (isSeparatorAtEnd) {
        stringBuilder.append(separator);
    }

    return stringBuilder;
}

Pros of Current Implementation ✅

  • Custom logic: Handles isSeparatorAtEnd flag
  • Checks for trailing separators: isSeparatorAppendable prevents double separators
  • StringBuilder reuse: Caller can provide existing StringBuilder
  • Works with any Object: Calls toString() implicitly

Cons of Current Implementation ❌

  • More complex: Custom loop logic vs built-in methods
  • More code to maintain: ~50 lines vs ~10 lines
  • Edge case handling: isSeparatorAppendable logic is subtle
  • Performance: Similar performance to built-ins, but custom code has overhead

Recommendation

Keep Current Implementation If:

  1. The isSeparatorAppendable logic (checking if objects end with separator) is actually used and necessary
  2. Performance profiling shows this is a hotspot

Consider Simplification If:

  1. The "avoid double separator" logic is never needed in practice
  2. Tests show equal or better performance with built-in methods
  3. Simplicity and maintainability are more important than edge case handling

Questions to Answer

  1. Is the double-separator prevention actually needed?

    • Check if any objects passed to this method ever end with the separator
    • If not, that logic is unnecessary complexity
  2. Is isSeparatorAtEnd commonly used?

    • If it's rarely true, we could just append the separator manually after calling String.join()
  3. Performance requirements?

    • Is this method called in hot loops?
    • Benchmark current vs. built-in implementations

Suggested Investigation

// Add telemetry to see if edge cases are hit
static boolean isSeparatorAppendable(final String separator, final int index, final Object... objs) {
    if (objs == null) {
        return false;
    }

    boolean isNotLastElement = index <= (objs.length - 2);
    boolean doesNotEndWithSeparator = !objs[index].toString().endsWith(separator);
    
    if (isNotLastElement && objs[index].toString().endsWith(separator)) {
        // Log when this edge case is hit
        getLogger().log(Level.FINE, "Prevented double separator for object: {0}", objs[index]);
    }
    
    return isNotLastElement && doesNotEndWithSeparator;
}

Files to Investigate

  • src/main/java/org/flossware/jcommons/util/StringUtil.java (lines 135-245)
  • src/test/java/org/flossware/jcommons/util/StringUtilTest.java (check test coverage)

Priority

Low - Performance/maintainability optimization

Related

This is not a bug, but a potential simplification opportunity worth investigating.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions