Java, Arrays and Strings
Tags: java, coding, beginners, tutorial
Published: 2024-06-29 04:02:59

### Arrays in Java

Arrays in Java are a fundamental data structure used to store multiple values of the same type in a single variable. They provide a fixed-size, ordered collection of elements and are an essential part of Java programming. This guide covers everything you need to know about arrays in Java.

#### 1. Declaring and Initializing Arrays

Arrays can be declared and initialized in various ways.

- **Declaration:**
  
  ```java
  int[] numbers;
  String[] names;
  ```

- **Initialization:**
  
  ```java
  numbers = new int[5]; // an array of 5 integers
  names = new String[3]; // an array of 3 strings
  ```

- **Combined Declaration and Initialization:**

  ```java
  int[] numbers = new int[5];
  String[] names = new String[3];
  ```

- **Static Initialization:**

  ```java
  int[] numbers = {1, 2, 3, 4, 5};
  String[] names = {"Alice", "Bob", "Charlie"};
  ```

#### 2. Accessing Array Elements

Array elements can be accessed using their index. The index is zero-based, meaning it starts from 0.

```java
int firstNumber = numbers[0];
String firstName = names[0];
```

#### 3. Modifying Array Elements

You can modify array elements by assigning new values to specific indices.

```java
numbers[0] = 10;
names[0] = "David";
```

#### 4. Array Length

The length of an array can be obtained using the `length` property.

```java
int arrayLength = numbers.length;
```

#### 5. Iterating Over Arrays

You can iterate over arrays using loops.

- **For Loop:**

  ```java
  for (int i = 0; i < numbers.length; i++) {
      System.out.println(numbers[i]);
  }
  ```

- **Enhanced For Loop:**

  ```java
  for (int number : numbers) {
      System.out.println(number);
  }
  ```

#### 6. Multidimensional Arrays

Java supports multidimensional arrays (arrays of arrays).

- **Declaration and Initialization:**

  ```java
  int[][] matrix = new int[3][3];
  int[][] predefinedMatrix = {
      {1, 2, 3},
      {4, 5, 6},
      {7, 8, 9}
  };
  ```

- **Accessing and Modifying Elements:**

  ```java
  int element = matrix[0][0];
  matrix[0][0] = 10;
  ```

#### 7. Array Methods

Java provides utility methods for arrays in the `java.util.Arrays` class.

- **Sorting:**

  ```java
  Arrays.sort(numbers);
  ```

- **Binary Search:**

  ```java
  int index = Arrays.binarySearch(numbers, 3);
  ```

- **Copying:**

  ```java
  int[] copiedArray = Arrays.copyOf(numbers, numbers.length);
  ```

- **Filling:**

  ```java
  Arrays.fill(numbers, 0);
  ```

- **Comparing:**

  ```java
  boolean areEqual = Arrays.equals(numbers, copiedArray);
  ```

- **String Representation:**

  ```java
  String arrayString = Arrays.toString(numbers);
  ```

#### 8. Common Pitfalls

- **Index Out of Bounds:** Accessing an index outside the array's bounds throws an `ArrayIndexOutOfBoundsException`.
  
  ```java
  int invalidElement = numbers[10]; // Throws ArrayIndexOutOfBoundsException
  ```

- **Null Elements:** An array of objects can contain null elements.

  ```java
  String[] strings = new String[5];
  strings[0] = null; // Valid
  ```

- **Fixed Size:** Arrays have a fixed size; once initialized, their size cannot be changed. Use `ArrayList` for a dynamic array.

### Strings in Java

Strings are an essential part of Java programming. The `String` class in Java is used to create and manipulate strings. This guide covers everything you need to know about strings in Java.

#### 1. Creating Strings

Strings can be created in several ways:

- **Using String Literals:**

  ```java
  String str1 = "Hello, World!";
  ```

- **Using the `new` Keyword:**

  ```java
  String str2 = new String("Hello, World!");
  ```

- **From a `char` Array:**

  ```java
  char[] chars = {'H', 'e', 'l', 'l', 'o'};
  String str3 = new String(chars);
  ```

#### 2. String Immutability

Strings in Java are immutable, meaning once a `String` object is created, it cannot be changed. Any modification results in the creation of a new `String` object.

```java
String original = "Hello";
String modified = original.concat(", World!");
System.out.println(original); // Outputs: Hello
System.out.println(modified); // Outputs: Hello, World!
```

#### 3. Common String Methods

The `String` class provides many methods for string manipulation and analysis.

- **Length of a String:**

  ```java
  int length = str1.length();
  ```

- **Character at a Specific Index:**

  ```java
  char ch = str1.charAt(0);
  ```

- **Substring:**

  ```java
  String substr = str1.substring(7, 12); // Outputs: World
  ```

- **Concatenation:**

  ```java
  String str5 = str1.concat(" How are you?");
  ```

- **Index of a Character or Substring:**

  ```java
  int index = str1.indexOf('W'); // Outputs: 7
  ```

- **Comparison:**

  ```java
  boolean equals = str1.equals("Hello, World!"); // true
  boolean equalsIgnoreCase = str1.equalsIgnoreCase("hello, world!"); // true
  int compareTo = str1.compareTo("Hello, World!"); // 0 (lexicographically equal)
  ```

- **Replacing Characters or Substrings:**

  ```java
  String replaced = str1.replace('l', 'p'); // Heppo, Worpd!
  String replacedSubstring = str1.replace("World", "Java"); // Hello, Java!
  ```

- **Splitting a String:**

  ```java
  String[] parts = str1.split(", ");
  ```

- **Trimming Whitespace:**

  ```java
  String trimmed = str1.trim();
  ```

- **Converting to Upper/Lower Case:**

  ```java
  String upperCase = str1.toUpperCase();
  String lowerCase = str1.toLowerCase();
  ```

#### 4. String Pool

Java optimizes memory usage of `String` objects by storing them in a special area called the string pool. When you create a string literal, the JVM checks the string pool to see if it already exists. If it does, it returns the reference to the existing string. Otherwise, it creates a new string and adds it to the pool.

```java
String s1 = "Hello";
String s2 = "Hello";
boolean isSame = (s1 == s2); // true, both reference the same object in the string pool
```

### String Formatting in Java

String formatting is an essential aspect of Java programming, allowing you to create well-structured and readable text. Java provides several ways to format strings, including `String.format()`, `System.out.printf()`, and simple string concatenation.

#### 1. `String.format()` Method

The `String.format()` method allows you to create formatted strings using various format specifiers, similar to `printf` in C/C++.

**Basic Syntax:**

```java
String formattedString = String.format(format, arguments);
```

**Format Specifiers:**

- **%s**: String
- **%d**: Decimal integer
- **%f**: Floating-point number
- **%x**: Hexadecimal integer
- **%o**: Octal integer
- **%c**: Character
- **%%**: Percent sign

**Example:**

```java
int age = 25;
String name = "Alice";
double score = 95.5;

String formattedString = String.format("Name: %s, Age: %d, Score: %.2f", name, age, score);
System.out.println(formattedString); // Outputs: Name: Alice, Age: 25, Score: 95.50
```

**Flags and Width:**

- **-**: Left-justify
- **+**: Include a sign (+ or -)
- **0**: Pad with zeros
- **,**: Include grouping separators
- **(width)**: Minimum number of characters to be printed

**Example with Flags:**

```java
int number = 12345;
String formattedNumber = String.format("%,d", number);
System.out.println(formattedNumber); // Outputs: 12,345

double price = 123.456;
String formattedPrice = String.format("%010.2f", price);
System.out.println(formattedPrice); // Outputs: 0000123.46
```

#### 2. `System.out.printf()` Method

The `System.out.printf()` method prints formatted output directly to the console. It uses the same format specifiers as `String.format()`.

**Example:**

```java
String name = "Alice";
int age = 25;
double score = 95.5;

System.out.printf("Name: %s, Age: %d, Score: %.2f%n", name, age, score);
// Outputs: Name: Alice, Age: 25, Score: 95.50
```

#### 3. String Concatenation with `+`

For basic string formatting needs, simple string concatenation using the `+` operator can be used. Although it lacks the flexibility of `String.format()` and `System.out.printf()`, it is straightforward for simple tasks.

**Example:**

```java
String name = "Alice";
int age = 25;
double score = 95.5;

String concatenatedString = "Name: " + name + ", Age: " + age + ", Score: " + score;
System.out.println(concatenatedString); // Outputs: Name: Alice, Age: 25, Score: 95.5
```

### String Methods in Java

#### Character Retrieval

**`charAt(int index)`**
- Returns the character at the specified index.
- **Syntax:** `str.charAt(index)`

```java
String s = "hello";
System.out.println(s.charAt(1));  // Output: 'e'
```

#### Comparison

**`compareTo(String anotherString)`**
- Compares two strings lexicographically.
- **Syntax:** `str.compareTo(anotherString)`

```java
String s1 = "hello";
String s2 = "world";
System.out.println(s1.compareTo(s2));  // Output: -15
```

**`compareToIgnoreCase(String str)`**
- Compares two strings lexicographically, ignoring case differences.
- **Syntax:** `str.compareToIgnoreCase(str)`

```java
String s1 = "hello";
String s2 = "HELLO";
System.out.println(s1.compareToIgnoreCase(s2));  // Output: 0
```

#### Concatenation

**`concat(String str)`**
- Concatenates the specified string to the end of this string.
- **Syntax:** `str.concat(str)`

```java
String s1 = "hello";
String s2 = "world";
System.out.println(s1.concat(" ").concat(s2));  // Output: "hello world"
```

#### Search

**`contains(CharSequence s)`**
- Returns true if and only if this string contains the specified sequence of char values.
- **Syntax:** `str.contains(s)`

```java
String s = "hello world";
System.out.println(s.contains("world"));  // Output: true
```

**`endsWith(String suffix)`**
- Tests if this string ends with the specified suffix.
- **Syntax:** `str.endsWith(suffix)`

```java
String s = "hello";
System.out.println(s.endsWith("lo"));  // Output: true
```

**`indexOf(int ch)`**
- Returns the index within this string of the first occurrence of the specified character.
- **Syntax:** `str.indexOf(ch)`

```java
String s = "hello";
System.out.println(s.indexOf('e'));  // Output: 1
```

**`indexOf(String str)`**
- Returns the index within this string of the first occurrence of the specified substring.
- **Syntax:** `str.indexOf(str)`

```java
String s = "hello";
System.out.println(s.indexOf("ll"));  // Output: 2
```

**`indexOf(int ch, int fromIndex)`**
- Returns the index within this string of the first occurrence of the specified character, starting the search at the specified index.
- **Syntax:** `str.indexOf(ch, fromIndex)`

```java
String s = "hello";
System.out.println(s.indexOf('l', 3));  // Output: 3
```

**`indexOf(String str, int fromIndex)`**
- Returns the index within this string of the first occurrence of the specified substring, starting at the specified index.
- **Syntax:** `str.indexOf(str, fromIndex)`

```java
String s = "hello";
System.out.println(s.indexOf("l", 3));  // Output: 3
```

**`lastIndexOf(int ch)`**
- Returns the index within this string of the last occurrence of the specified character.
- **Syntax:** `str.lastIndexOf(ch)`

```java
String s = "hello";
System.out.println(s.lastIndexOf('l'));  // Output: 3
```

**`lastIndexOf(String str)`**
- Returns the index within this string of the last occurrence of the specified substring.
- **Syntax:** `str.lastIndexOf(str)`

```java
String s = "hello";
System.out.println(s.lastIndexOf("l"));  // Output: 3
```

#### Equality

**`equals(Object anObject)`**
- Compares this string to the specified object.
- **Syntax:** `str.equals(anObject)`

```java
String s1 = "hello";
String s2 = "hello";
System.out.println(s1.equals(s2));  // Output: true
```

**`equalsIgnoreCase(String anotherString)`**
- Compares this string to another string, ignoring case considerations.
- **Syntax:** `str.equalsIgnoreCase(anotherString)`

```java
String s1 = "hello";
String s2 = "HELLO";
System.out.println(s1.equalsIgnoreCase(s2));  // Output: true
```

#### Length

**`length()`**
- Returns the length of this string.
- **Syntax:** `str.length()`

```java
String s = "hello";
System.out.println(s.length());  // Output: 5
```

#### Replacement

**`replace(char oldChar, char newChar)`**
- Returns a string resulting from replacing all occurrences of `oldChar` in this string with `newChar`.
- **Syntax:** `str.replace(oldChar, newChar)`

```java
String s = "hello";
System.out.println(s.replace('l', 'p'));  // Output: "heppo"
```

#### Splitting and Joining

**`split(String regex)`**
- Splits this string around matches of the given regular expression.
- **Syntax:** `str.split(regex)`

```java
String s = "hello world";
String[] parts = s.split(" ");
for (String part : parts) {
    System.out.println(part);  // Outputs: "hello" and "world"
}
```

**`join(CharSequence delimiter, CharSequence... elements)`**
- Returns a new string composed of copies of the `CharSequence` elements joined together with a copy of the specified delimiter.
- **Syntax:** `String.join(delimiter, elements)`

```java
String joinedString = String.join(", ", "one", "two", "three");
System.out.println(joinedString);  // Output: "one, two, three"
```

#### Substring

**`substring(int beginIndex)`**
- Returns a new string that is a substring of this string.
- **Syntax:** `str.substring(beginIndex)`

```java
String s = "hello";
System.out.println(s.substring(2));  // Output: "llo"
```

**`substring(int beginIndex, int endIndex)`**
- Returns a new string that is a substring of this string, starting at `beginIndex` and ending at `endIndex - 1`.
- **Syntax:** `str.substring(beginIndex, endIndex)`

```java
String s = "hello";
System.out.println(s.substring(1, 4));  // Output: "ell"
```

#### Case Conversion

**`toLowerCase()`**
- Converts all of the characters in this string to lowercase.
- **Syntax:** `str.toLowerCase()`

```java
String s = "HELLO";
System.out.println(s.toLowerCase());  // Output: "hello"
```

**`toUpperCase()`**
- Converts all of the characters in this string to uppercase.
- **Syntax:** `str.toUpperCase()`

```java
String s = "hello";
System.out.println(s.toUpperCase());  // Output: "HELLO"
```

#### Trimming

**`trim()`**
- Returns a copy of the string, with leading and trailing whitespace omitted.
- **Syntax:** `str.trim()`

```java
String s = "  hello  ";
System.out.println(s.trim());  // Output: "hello"
```

#### Conversion

**`toCharArray()`**
- Converts this string to a new character array.
- **Syntax:** `str.toCharArray()`

```java
String s = "hello";
char[] charArray = s.toCharArray();
for (char c : charArray) {
    System.out.print(c);  // Output: h e l l o
}
```

### Static Methods

**`join(CharSequence delimiter, CharSequence... elements)`**
- Returns a new string composed of copies of the `CharSequence` elements joined together with a copy of the specified delimiter.
- **Syntax:** `String.join(delimiter, elements)`

```java
String joinedString = String.join(", ", "one", "two", "three");
System.out.println(joinedString);  // Output: "one, two, three"
```

**`valueOf(char c)`**
- Returns the string representation of the passed character.
- **Syntax:** `String.valueOf(c)`

```java
char ch = 'A';
String str = String.valueOf(ch);
System.out.println(str);  // Output: "A"
```

**`valueOf(char[] data)`**
- Returns the string representation of the character array.
- **Syntax:** `String.valueOf(data)`

```java
char[] data = {'h', 'e', 'l', 'l', 'o'};
String str = String.valueOf(data);
System.out.println(str);  // Output: "hello"
```

### Methods from Object Class

**`equals(Object anObject)`**
- Compares this string to the specified object.
- **Syntax:** `str.equals(anObject)`

```java
String s1 = "hello";
String s2 = "hello";
System.out.println(s1.equals(s2));  // Output: true
```

**`toString()`**
- Returns a string representation of the object.
- **Syntax:** `str.toString()`

```java
String s = "hello";
System.out.println(s.toString());  // Output: "hello"
```

### Methods Due to Comparable Interface

**`compareTo(String anotherString)`**
- Compares two strings lexicographically.
- **Syntax:** `str.compareTo(anotherString)`

```java
String s1 = "hello";
String s2 = "world";
System.out.println(s1.compareTo(s2));  // Output: -15
```

**`compareToIgnoreCase(String str)`**
- Compares two strings lexicographically, ignoring case differences.
- **Syntax:** `str.compareToIgnoreCase(str)`

```java
String s1 = "hello";
String s2 = "HELLO";
System.out.println(s1.compareToIgnoreCase(s2));  // Output: 0
```

### StringBuilder in Java

#### Overview

`StringBuilder` in Java is a mutable sequence of characters. It provides an efficient way to construct strings when frequent modifications, such as concatenations, are needed. Unlike `String`, which is immutable and creates a new object for every operation that modifies it, `StringBuilder` allows for in-place modifications, making it more efficient for tasks involving dynamic string manipulation.

#### Creation

**`StringBuilder()`**
- Constructs a string builder with no characters in it and an initial capacity of 16 characters.
- **Syntax:** `StringBuilder sb = new StringBuilder();`

```java
StringBuilder sb = new StringBuilder();  // Empty StringBuilder with initial capacity of 16
```

**`StringBuilder(int capacity)`**
- Constructs a string builder with the specified initial capacity.
- **Syntax:** `StringBuilder sb = new StringBuilder(capacity);`

```java
StringBuilder sb = new StringBuilder(20);  // StringBuilder with initial capacity of 20
```

**`StringBuilder(String str)`**
- Constructs a string builder initialized to the contents of the specified string.
- **Syntax:** `StringBuilder sb = new StringBuilder(str);`

```java
String str = "hello";
StringBuilder sb = new StringBuilder(str);  // StringBuilder initialized with "hello"
```

#### Methods

**Appending**

**`append(String str)`**
- Appends the specified string to this character sequence.
- **Syntax:** `sb.append(str)`

```java
StringBuilder sb = new StringBuilder("hello");
sb.append(" world");
System.out.println(sb.toString());  // Output: "hello world"
```

**`append(int i)`**
- Converts the integer to a string and appends it to this character sequence.
- **Syntax:** `sb.append(i)`

```java
StringBuilder sb = new StringBuilder("value is ");
sb.append(100);
System.out.println(sb.toString());  // Output: "value is 100"
```

**`append(char c)`**
- Appends the specified character to this character sequence.
- **Syntax:** `sb.append(c)`

```java
StringBuilder sb = new StringBuilder("hello");
sb.append('!');
System.out.println(sb.toString());  // Output: "hello!"
```

**Insertion**

**`insert(int offset, String str)`**
- Inserts the specified string into this character sequence at the specified position.
- **Syntax:** `sb.insert(offset, str)`

```java
StringBuilder sb = new StringBuilder("hello");
sb.insert(3, "world");
System.out.println(sb.toString());  // Output: "helworldlo"
```

**`insert(int offset, char c)`**
- Inserts the specified character into this character sequence at the specified position.
- **Syntax:** `sb.insert(offset, c)`

```java
StringBuilder sb = new StringBuilder("hello");
sb.insert(2, 'X');
System.out.println(sb.toString());  // Output: "heXllo"
```

**Deletion**

**`delete(int start, int end)`**
- Removes the characters in a substring of this sequence.
- **Syntax:** `sb.delete(start, end)`

```java
StringBuilder sb = new StringBuilder("hello world");
sb.delete(5, 10);
System.out.println(sb.toString());  // Output: "hello"
```

**Replacement**

**`replace(int start, int end, String str)`**
- Replaces the characters in a substring of this sequence with characters in the specified string.
- **Syntax:** `sb.replace(start, end, str)`

```java
StringBuilder sb = new StringBuilder("hello");
sb.replace(2, 5, "123");
System.out.println(sb.toString());  // Output: "he123"
```

**Conversion to String**

**`toString()`**
- Converts the contents of the StringBuilder to a String.
- **Syntax:** `sb.toString()`

```java
StringBuilder sb = new StringBuilder("hello");
String str = sb.toString();
System.out.println(str);  // Output: "hello"
```

**Other Methods**

**`length()`**
- Returns the length (number of characters) of the StringBuilder.
- **Syntax:** `sb.length()`

```java
StringBuilder sb = new StringBuilder("hello");
System.out.println(sb.length());  // Output: 5
```

**`capacity()`**
- Returns the current capacity of the StringBuilder.
- **Syntax:** `sb.capacity()`

```java
StringBuilder sb = new StringBuilder(10);
System.out.println(sb.capacity());  // Output: 10
```

**`reverse()`**
- Reverses the characters in the StringBuilder.
- **Syntax:** `sb.reverse()`

```java
StringBuilder sb = new StringBuilder("hello");
System.out.println(sb.reverse().toString());  // Output: "olleh"
```

### StringBuffer in Java

#### Overview

`StringBuffer` in Java is similar to `StringBuilder` but provides synchronized methods, making it thread-safe for concurrent access. It is used when multiple threads are working on the same string concurrently and need synchronization. Like `StringBuilder`, `StringBuffer` also provides methods to manipulate mutable sequences of characters efficiently.

#### Creation

**`StringBuffer()`**
- Constructs a string buffer with no characters in it and an initial capacity of 16 characters.
- **Syntax:** `StringBuffer sb = new StringBuffer();`

```java
StringBuffer sb = new StringBuffer();  // Empty StringBuffer with initial capacity of 16
```

**`StringBuffer(int capacity)`**
- Constructs a string buffer with the specified initial capacity.
- **Syntax:** `StringBuffer sb = new StringBuffer(capacity);`

```java
StringBuffer sb = new StringBuffer(20);  // StringBuffer with initial capacity of 20
```

**`StringBuffer(String str)`**
- Constructs a string buffer initialized to the contents of the specified string.
- **Syntax:** `StringBuffer sb = new StringBuffer(str);`

```java
String str = "hello";
StringBuffer sb = new StringBuffer(str);  // StringBuffer initialized with "hello"
```

#### Methods

**Appending**

**`append(String str)`**
- Appends the specified string to this character sequence.
- **Syntax:** `sb.append(str)`

```java
StringBuffer sb = new StringBuffer("hello");
sb.append(" world");
System.out.println(sb.toString());  // Output: "hello world"
```

**`append(int i)`**
- Converts the integer to a string and appends it to this character sequence.
- **Syntax:** `sb.append(i)`

```java
StringBuffer sb = new StringBuffer("value is ");
sb.append(100);
System.out.println(sb.toString());  // Output: "value is 100"
```

**`append(char c)`**
- Appends the specified character to this character sequence.
- **Syntax:** `sb.append(c)`

```java
StringBuffer sb = new StringBuffer("hello");
sb.append('!');
System.out.println(sb.toString());  // Output: "hello!"
```

**Insertion**

**`insert(int offset, String str)`**
- Inserts the specified string into this character sequence at the specified position.
- **Syntax:** `sb.insert(offset, str)`

```java
StringBuffer sb = new StringBuffer("hello");
sb.insert(3, "world");
System.out.println(sb.toString());  // Output: "helworldlo"
```

**`insert(int offset, char c)`**
- Inserts the specified character into this character sequence at the specified position.
- **Syntax:** `sb.insert(offset, c)`

```java
StringBuffer sb = new StringBuffer("hello");
sb.insert(2, 'X');
System.out.println(sb.toString());  // Output: "heXllo"
```

**Deletion**

**`delete(int start, int end)`**
- Removes the characters in a substring of this sequence.
- **Syntax:** `sb.delete(start, end)`

```java
StringBuffer sb = new StringBuffer("hello world");
sb.delete(5, 10);
System.out.println(sb.toString());  // Output: "hello"
```

**Replacement**

**`replace(int start, int end, String str)`**
- Replaces the characters in a substring of this sequence with characters in the specified string.
- **Syntax:** `sb.replace(start, end, str)`

```java
StringBuffer sb = new StringBuffer("hello");
sb.replace(2, 5, "123");
System.out.println(sb.toString());  // Output: "he123"
```

**Conversion to String**

**`toString()`**
- Converts the contents of the StringBuffer to a String.
- **Syntax:** `sb.toString()`

```java
StringBuffer sb = new StringBuffer("hello");
String str = sb.toString();
System.out.println(str);  // Output: "hello"
```

**Other Methods**

**`length()`**
- Returns the length (number of characters) of the StringBuffer.
- **Syntax:** `sb.length()`

```java
StringBuffer sb = new StringBuffer("hello");
System.out.println(sb.length());  // Output: 5
```

**`capacity()`**
- Returns the current capacity of the StringBuffer.
- **Syntax:** `sb.capacity()`

```java
StringBuffer sb = new StringBuffer(10);
System.out.println(sb.capacity());  // Output: 10
```

**`reverse()`**
- Reverses the characters in the StringBuffer.
- **Syntax:** `sb.reverse()`

```java
StringBuffer sb = new StringBuffer("hello");
System.out.println(sb.reverse().toString());  // Output: "olleh"
```

#### Performance Considerations

- `StringBuffer` is synchronized, making it thread-safe for concurrent access.
- It is slightly slower than `StringBuilder` due to synchronization overhead.
- The initial capacity grows automatically as needed.