Skip to content

VOL 01 / CH 09 / LESSON 02

9.2 String Operations and Construction ​

Prerequisites: String basics, arrays, and loops

Estimated time: 30 minutes

Runtime Environment: Java 17

You are ready to continue when: You can extract, split, clean, replace, and build text according to its API contract

An order line looks like just a piece of text ​

An order line contains a date, customer, item, and quantity. Splitting and rebuilding it correctly depends on the API contract: whether an endpoint is included, whether a delimiter is a regular expression, and whether trailing empty fields are preserved. Snippets using Arrays, Pattern, Matcher, and related types need imports; the complete example lists them. Run snippets with repeated variable names separately.

1. substring Includes the Start and Excludes the End ​

java
String order = "2026-07-15|a developer|Hammer|2";
String date = order.substring(0, 10);
String details = order.substring(11);

System.out.println(date);
System.out.println(details);

Output:

text
2026-07-15
a developer|Hammer|2

substring(begin, end) includes begin but not end, and the effective scope satisfies:

text
0 <= begin <= end <= text.length()

Out-of-range indexes or begin > end cause an IndexOutOfBoundsException under the API contract; common JDK implementations use its subclass StringIndexOutOfBoundsException. Indexes are counted in UTF-16 code units; if the split point falls in the middle of a surrogate pair, an incomplete Unicode character is obtained. When locating by code point, first use offsetByCodePoints to compute the index.

The substring contract specifies which text is returned, not how backing storage is allocated or shared. A full-range substring may even return the original object. For memory analysis, inspect the target JDK and retained objects rather than inferring allocation size from substring length alone.

2. The first parameter of split is a regular expression ​

By default, a regex dot matches any character except a line terminator, so you can't simply write:

java
String ip = "192.168.1.1";

System.out.println(Arrays.toString(ip.split(".")));
System.out.println(Arrays.toString(ip.split("\\.")));

Output:

text
[]
[192, 168, 1, 1]

When delimiters come from configuration or user input, don't guess which characters need escaping on your own:

java
String delimiter = ".";
String[] parts = ip.split(Pattern.quote(delimiter));

The second parameter determines the number of splits and trailing empty items. ​

java
String row = "Hammer,2,,";

System.out.println(Arrays.toString(row.split(",")));
System.out.println(Arrays.toString(row.split(",", -1)));
System.out.println(Arrays.toString(row.split(",", 3)));

Output:

text
[Hammer, 2]
[Hammer, 2, , ]
[Hammer, 2, ,]
  • Don't pass limit or pass 0: discard trailing empty strings;
  • Negative: retain all fields, including trailing empty fields;
  • Positive n: cut into at most n pieces, with the last piece retaining the uncut content.

CSV has quoting, escaping, and embedded newline rules that cannot be correctly parsed with just one split(","). Production code should use a library that conforms to the target CSV dialect.

3. trim vs. strip Have Different Blank Definitions ​

java
String padded = "  \u2003Hello\u2003  ";

System.out.println("'" + padded.trim() + "'");
System.out.println("'" + padded.strip() + "'");

Output:

text
' Hello '
'Hello'

trim() removes leading and trailing characters with code values no greater than U+0020. Added in Java 11, strip() uses Character.isWhitespace, which handles more multilingual whitespace than trim. Some space-like characters still do not match that definition: "\u00A0".strip().isEmpty() is false because the non-breaking space U+00A0 is excluded. When choosing a method, consider the input protocol rather than broadly stating that strip clears all Unicode whitespace.

Related methods also include:

java
text.stripLeading();
text.stripTrailing();
text.isBlank();

isBlank() accepts empty strings or strings composed entirely of Character.isWhitespace characters; isEmpty() only checks if the length is 0.

4. replace processes literal text, replaceAll processes regex ​

java
String notice = "Cats and cats";

System.out.println(notice.replace("cats", "dogs"));
System.out.println(notice.replaceAll("(?i)cats", "dogs"));

Output:

text
Cats and dogs
dogs and dogs

replace(CharSequence, CharSequence) treats the target as literal text. The first parameter of replaceAll(regex, replacement) is a regex, and the second parameter has its own replacement syntax: $1 references capture groups, and backslashes are used for escaping.

When replacing text from external input, use Matcher.quoteReplacement:

java
String price = "price=10";
String safe = price.replaceAll("\\d+", Matcher.quoteReplacement("$5"));
System.out.println(safe); // price=$5

If you only need to replace literal content, prefer replace, it's straightforward and bypasses two layers of regex rules.

5. Search and Case Conversion ​

MethodResultBoundary
indexOf(text)First appearance location, not found at -1Starting index can be specified
lastIndexOf(text)Last appearance positionNot found for -1
contains(text)Whether the string contains a literal substringParameter cannot be null
startsWith / endsWithMatches prefix or suffixCase sensitive
toUpperCase(locale)Convert based on specified regionWhen targeting users, do not rely on the default region

When a protocol permits this conversion, Locale.ROOT makes its result independent of the machine's default locale. The protocol still defines which characters are allowed and whether case matters:

java
String normalized = command.toUpperCase(Locale.ROOT);

Natural language search, sorting, and case folding are more complex and cannot assume that a single toLowerCase() resolves all Unicode equivalence issues.

6. Choosing the Right Construction Method ​

Small Fixed Fragments: Using + ​

java
String label = "Order #" + orderId + ": " + itemName;

The code is clear; the compiler handles runtime concatenation strategy. Don't manually write a Builder just to combine two fragments.

Step-by-step appending in loops or branches: using StringBuilder ​

java
StringBuilder report = new StringBuilder();
for (OrderLine line : lines) {
    report.append(line.item())
            .append(" x ")
            .append(line.quantity())
            .append(System.lineSeparator());
}
String text = report.toString();

Capacity grows automatically. Only provide an initial capacity when you have a reliable estimate and measurement shows that avoiding growth would help; arbitrarily setting a large capacity may waste memory.

Existing collection: use join ​

java
String summary = String.join(" | ", "Hammer", "Nail", "Plank");
String commaSeparated = names.stream().collect(Collectors.joining(","));

This only joins names with commas; it does not escape commas, quotes, or newlines inside fields, so it is not a complete CSV encoder. When you need both a prefix and a suffix, Collectors.joining(", ", "[", "]") is more reliable than manually handling the last delimiter.

Template text: Format or text block ​

java
String receipt = "Order %s, Total %d item".formatted(orderId, quantity);

String template = """
        Customer:%s
        Item:%s
        """.formatted(customer, item);

Format strings also have type contracts; external input should not be used directly as a trusted template.

7. StringBuilder and StringBuffer ​

TypeConcurrency ContractCommon Uses
StringBuilderNo thread-safety guaranteemutable buffer owned by method or single-threaded context
StringBufferSynchronizes where needed for individual thread-safe operationsMust comply with compatibility requirements for older APIs of this type

StringBuffer synchronizes where needed, but a sequence such as "check then append" is not one atomic operation. Appending another mutable CharSequence also does not automatically protect that source object just because the target buffer is synchronized. Sharing a mutable text buffer across multiple threads typically entangles ordering, locking, and error handling. A more common design is for each task to locally construct its result, and then a clear consolidation phase to merge them.

Don't state fixed conclusions like "2 to 3 times slower" or "99% of the time." Performance differences depend on the JDK, contention, and workload; use tools like JMH to measure target code when performance requirements exist.

8. Complete Working Example ​

First, prepare a temporary directory:

bash
chapter9_operations_root=$(mktemp -d)
readonly chapter9_operations_root
mkdir -p "$chapter9_operations_root/out"
cd "$chapter9_operations_root"

Save as StringOperationsDemo.java:

java
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class StringOperationsDemo {
    public static void main(String[] args) {
        String order = "2026-07-15|a developer|Hammer|2";
        checkEquals("2026-07-15", order.substring(0, 10));
        checkEquals("a developer|Hammer|2", order.substring(11));

        String ip = "192.168.1.1";
        checkEquals(List.of("192", "168", "1", "1"),
                Arrays.asList(ip.split(Pattern.quote("."))));

        String row = "Hammer,2,,";
        checkEquals(2, row.split(",").length);
        checkEquals(4, row.split(",", -1).length);
        checkEquals("", row.split(",", -1)[3]);

        String padded = "  \u2003Hello\u2003  ";
        checkEquals("\u2003Hello\u2003", padded.trim());
        checkEquals("Hello", padded.strip());

        String safePrice = "price=10".replaceAll(
                "\\d+", Matcher.quoteReplacement("$5"));
        checkEquals("price=$5", safePrice);

        List<String> items = List.of("Hammer", "Nail", "Plank");
        String joined = items.stream().collect(Collectors.joining(" | "));
        checkEquals("Hammer | Nail | Plank", joined);

        StringBuilder report = new StringBuilder();
        for (int index = 0; index < items.size(); index++) {
            report.append(index + 1).append(". ").append(items.get(index));
            if (index + 1 < items.size()) report.append(System.lineSeparator());
        }

        System.out.println(order.substring(0, 10));
        System.out.println(joined);
        System.out.println("REPORT".toLowerCase(Locale.ROOT) + ":");
        System.out.println(report);
        System.out.println("String operation check passed");
    }

    static void checkEquals(Object expected, Object actual) {
        if (!Objects.equals(expected, actual)) {
            throw new AssertionError("expected=" + expected + ", actual=" + actual);
        }
    }
}

Run:

bash
javac --release 17 -encoding UTF-8 -Xlint:all -d out StringOperationsDemo.java
java -cp out StringOperationsDemo

Expected output:

text
2026-07-15
Hammer | Nail | Plank
report:
1. Hammer
2. Nail
3. Plank
String operation check passed

Clean up after saving the necessary experiment records:

bash
cd
ls -ld -- "$chapter9_operations_root"
rm -r -- "$chapter9_operations_root"

Professional Lookup Entry ​

  • String API: substring, split, strip, formatted, lines;
  • The different uses of Pattern.quote and Matcher.quoteReplacement;
  • The capacity, length, and setLength agreement of StringBuilder;
  • JMH: Use the benchmarking framework to compare concatenation strategies, avoid drawing conclusions based on a single nanoTime.

Before leaving this chapter ​

Write the inclusive-start, exclusive-end bounds for substring. Test a regex dot, trailing empty fields, and a positive limit with split; pass external replacement values through Matcher.quoteReplacement. Then, based on scenario, select a few +, incremental StringBuilder, or collection joins, and explicitly state that StringBuffer's single-method synchronization does not equal full process atomicity.

Keep the fixed inputs and expected fields. When the order format changes, use them to identify which parsing rules need to change. The next chapter introduces collections for data whose size can grow.

→ Next step: Core collections

API Entry: Java 17 Pattern, Java 17 Matcher, Java 17 StringBuilder.

Built with VitePress | Software Systems Atlas