Skip to content

Strings

Strings are one of the most fundamental and widely used data types in Java. As a senior Java engineer, understanding the nuances of String handling is crucial for writing efficient and correct code.

  • Immutable: Once created, String objects cannot be modified
  • Final class: Cannot be extended
  • Implements Serializable, Comparable, and CharSequence interfaces
  • Thread-safe: Due to immutability
  • Special status in Java: Literal pooling, compiler optimizations

There are two primary ways to create Strings in Java:

// String literal - uses String pool
String literalString = "Hello";
// String object - creates new object in heap
String objectString = new String("Hello");

The String pool (also called String constant pool or String intern pool) is a special memory area in the Java heap:

  • In Java 6 and earlier: Located in PermGen space
  • In Java 7 and later: Located in the main heap area
  • Purpose: Optimize memory usage by reusing String literals
String s1 = "Java"; // Creates "Java" in the pool
String s2 = "Java"; // Reuses the same "Java" from pool
String s3 = new String("Java"); // Creates new object in heap (not in pool)
System.out.println(s1 == s2); // true (same reference)
System.out.println(s1 == s3); // false (different references)
System.out.println(s1.equals(s3)); // true (same content)

Interning is the process of adding a String to the String pool:

String s1 = new String("Hello").intern(); // Explicitly adds to pool and returns pool reference
String s2 = "Hello"; // From pool
System.out.println(s1 == s2); // true

Multiple ways to concatenate Strings in Java:

// Using + operator
String result = "Hello" + " " + "World"; // Compiler optimizes this to StringBuilder
// Using concat() method
String result = "Hello".concat(" ").concat("World");
// Using StringBuilder (mutable, not thread-safe)
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(" ").append("World");
String result = sb.toString();
// Using StringBuffer (mutable, thread-safe)
StringBuffer sbuf = new StringBuffer();
sbuf.append("Hello").append(" ").append("World");
String result = sbuf.toString();
// Using String.join (Java 8+)
String result = String.join(" ", "Hello", "World");
List<String> words = Arrays.asList("Hello", "World");
String result = String.join(" ", words);
// BAD: Creates many intermediate String objects
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // Each iteration creates a new String
}
// GOOD: Uses a single StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i); // Modifies the same StringBuilder
}
String result = sb.toString();
ClassMutabilityThread SafetyPerformance
StringImmutableThread-safeSlower for multiple modifications
StringBuilderMutableNot thread-safeFastest for single-threaded scenarios
StringBufferMutableThread-safe (synchronized)Slower than StringBuilder due to synchronization
String str = "Java Programming";
// Basic operations
int length = str.length(); // 16
char charAtIndex = str.charAt(5); // 'P'
boolean isEmpty = str.isEmpty(); // false
boolean isBlank = str.isBlank(); // false (Java 11+)
// Comparison
boolean equals = str.equals("java programming"); // false (case-sensitive)
boolean equalsIgnoreCase = str.equalsIgnoreCase("java programming"); // true
int compareResult = str.compareTo("Java"); // Positive value
// Searching
int indexOfP = str.indexOf('P'); // 5
int lastIndexOfm = str.lastIndexOf('m'); // 11
boolean containsProgram = str.contains("Program"); // true
boolean startsWithJava = str.startsWith("Java"); // true
boolean endsWithing = str.endsWith("ing"); // true
// Extraction
String substring = str.substring(5, 16); // "Programming"
String[] parts = str.split(" "); // ["Java", "Programming"]
CharSequence subSequence = str.subSequence(0, 4); // "Java"
// Modification (returns new String)
String lower = str.toLowerCase(); // "java programming"
String upper = str.toUpperCase(); // "JAVA PROGRAMMING"
String trimmed = " Java ".trim(); // "Java"
String replaced = str.replace('a', 'o'); // "Jovo Progromming"
String replacedAll = str.replaceAll("a", "o"); // "Jovo Progromming"
String replacedFirst = str.replaceFirst("a", "o"); // "Jova Programming"
// Conversion
char[] charArray = str.toCharArray(); // {'J','a','v','a',' ','P',...}
byte[] bytes = str.getBytes(); // byte representation
byte[] bytesUtf8 = str.getBytes(StandardCharsets.UTF_8); // UTF-8 bytes
// Java 8 String.join
String joined = String.join(", ", "Java", "C++", "Python"); // "Java, C++, Python"
// Java 11 strip methods
String stripped = " Java \u2000".strip(); // "Java" (removes Unicode whitespace)
String stripLeading = " Java".stripLeading(); // "Java"
String stripTrailing = "Java ".stripTrailing(); // "Java"
// Java 11 repeat
String repeated = "Java".repeat(3); // "JavaJavaJava"
// Java 11 isBlank
boolean isBlank = " \t \n ".isBlank(); // true
// Java 12 indent
String indented = "Java".indent(4); // " Java\n"
// Java 12 transform
String transformed = "Java".transform(s -> s + " Programming"); // "Java Programming"
// Java 13+ text blocks
String textBlock = """
<html>
<body>
<p>Hello, World!</p>
</body>
</html>
"""; // Multi-line string with preserved formatting
// Java 15+ formatted
String formatted = "%s has %d beans".formatted("Jack", 42); // "Jack has 42 beans"
  • Java 8 and earlier: char[] array (UTF-16, 2 bytes per char)
  • Java 9 and later: byte[] + encoding flag (Latin-1 or UTF-16)
    • Latin-1 (ISO-8859-1) for characters ≤ 255 (1 byte per char)
    • UTF-16 for other characters (2 bytes per char)
    • Saves memory for strings with only ASCII characters
// Equivalent to String's hashCode() implementation
public int computeHashCode(String s) {
int h = 0;
for (int i = 0; i < s.length(); i++) {
h = 31 * h + s.charAt(i); //31 is a prime number
}
return h;
}

1. What is the difference between String, StringBuilder, and StringBuffer?

Section titled “1. What is the difference between String, StringBuilder, and StringBuffer?”

Answer:

  • String is immutable, thread-safe, and each modification creates a new object
  • StringBuilder is mutable, not thread-safe, and faster for concatenation in single-threaded scenarios
  • StringBuffer is mutable, thread-safe (synchronized methods), and suitable for multi-threaded environments

2. What is String interning and how does the String pool work?

Section titled “2. What is String interning and how does the String pool work?”

Answer: String interning is Java’s way of storing only one copy of each distinct String value in the String pool. When a String literal is created, Java checks if an identical String already exists in the pool. If it does, the reference to the pooled instance is returned. If not, the new String is added to the pool. The intern() method can be used to explicitly add a String to the pool.

Answer: Strings are immutable in Java for several reasons:

  • Security: Strings are used in class loading, network connections, and database operations where immutability prevents malicious code from changing values
  • Thread safety: Immutable objects are inherently thread-safe
  • String pool optimization: Allows safe sharing of String literals
  • Hashcode caching: Immutability allows String’s hashcode to be cached, improving performance in collections
  • Predictable behavior: Ensures String behavior is consistent across the application

4. How does String concatenation work internally?

Section titled “4. How does String concatenation work internally?”

Answer: When you use the + operator with Strings:

  • For simple concatenations at compile time, the compiler optimizes to a single String
  • For concatenations involving variables or in loops, the compiler typically converts to StringBuilder operations
  • Each concatenation with + in a loop creates a new StringBuilder instance, which is inefficient

5. What changed in String implementation in Java 9?

Section titled “5. What changed in String implementation in Java 9?”

Answer: In Java 9, the internal representation of String changed from a char[] array (always using 2 bytes per character in UTF-16) to a byte[] array with an encoding flag. This allows ASCII-only strings to use just 1 byte per character (Latin-1 encoding), reducing memory footprint by up to 50% for English text.

6. How would you check if a String is a palindrome?

Section titled “6. How would you check if a String is a palindrome?”

Answer:

public boolean isPalindrome(String str) {
if (str == null) return false;
// Remove non-alphanumeric characters and convert to lowercase
String cleaned = str.replaceAll("[^a-zA-Z0-9]", "").toLowerCase();
int left = 0;
int right = cleaned.length() - 1;
while (left < right) {
if (cleaned.charAt(left) != cleaned.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}

7. What is the time complexity of String.indexOf()?

Section titled “7. What is the time complexity of String.indexOf()?”

Answer: The time complexity of String.indexOf() is O(n*m) where n is the length of the string being searched and m is the length of the substring being searched for. Java uses a variation of the Knuth-Morris-Pratt (KMP) algorithm for this operation.

8. How would you efficiently count occurrences of a character in a String?

Section titled “8. How would you efficiently count occurrences of a character in a String?”

Answer:

public int countOccurrences(String str, char ch) {
if (str == null || str.isEmpty()) return 0;
int count = 0;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == ch) {
count++;
}
}
return count;
// Alternative using Java 8 streams
// return (int) str.chars().filter(c -> c == ch).count();
}

9. What is the difference between == and equals() when comparing Strings?

Section titled “9. What is the difference between == and equals() when comparing Strings?”

Answer:

  • == compares object references (memory addresses) - checks if two references point to the same object
  • equals() compares the content of the Strings - checks if two Strings have the same sequence of characters
String s1 = "Hello";
String s2 = "Hello";
String s3 = new String("Hello");
System.out.println(s1 == s2); // true (same reference from string pool)
System.out.println(s1 == s3); // false (different objects)
System.out.println(s1.equals(s3)); // true (same content)

10. How would you reverse a String in Java?

Section titled “10. How would you reverse a String in Java?”

Answer:

// Using StringBuilder
public String reverseString(String str) {
if (str == null) return null;
return new StringBuilder(str).reverse().toString();
}
// Manual implementation
public String reverseStringManually(String str) {
if (str == null) return null;
char[] chars = str.toCharArray();
int left = 0;
int right = chars.length - 1;
while (left < right) {
char temp = chars[left];
chars[left] = chars[right];
chars[right] = temp;
left++;
right--;
}
return new String(chars);
}

11. What is the difference between trim() and strip() methods?

Section titled “11. What is the difference between trim() and strip() methods?”

Answer:

  • trim() removes whitespace characters with codepoint ≤ 32 (traditional ASCII whitespace)
  • strip() (Java 11+) removes all Unicode whitespace characters, including non-breaking spaces and other special whitespace

12. How do you handle String comparison for sorting?

Section titled “12. How do you handle String comparison for sorting?”

Answer: For String sorting, you can use:

  • String.compareTo() for natural ordering (lexicographical)
  • String.compareToIgnoreCase() for case-insensitive ordering
  • Custom Comparator for specialized ordering (e.g., by length, by specific locale)
// Natural ordering
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
Collections.sort(names); // ["Alice", "Bob", "Charlie"]
// Case-insensitive
Collections.sort(names, String.CASE_INSENSITIVE_ORDER);
// Custom ordering (by length)
Collections.sort(names, Comparator.comparingInt(String::length));
// Locale-specific
Collections.sort(names, Collator.getInstance(Locale.FRENCH));

13. How would you efficiently check if a String contains only digits?

Section titled “13. How would you efficiently check if a String contains only digits?”

Answer:

// Using regular expression
public boolean containsOnlyDigits(String str) {
return str != null && str.matches("\\d+");
}
// Using Character methods (more efficient)
public boolean containsOnlyDigitsEfficient(String str) {
if (str == null || str.isEmpty()) return false;
for (int i = 0; i < str.length(); i++) {
if (!Character.isDigit(str.charAt(i))) {
return false;
}
}
return true;
}

14. What are the memory implications of String concatenation in loops?

Section titled “14. What are the memory implications of String concatenation in loops?”

Answer: Using the + operator for String concatenation in loops creates many intermediate String objects, leading to excessive memory usage and garbage collection. Each iteration creates a new StringBuilder, appends to it, and converts it back to a String. For efficient concatenation in loops, create a single StringBuilder outside the loop and append to it.

15. How does the intern() method affect memory and performance?

Section titled “15. How does the intern() method affect memory and performance?”

Answer: The intern() method adds a String to the String pool if it’s not already there and returns the canonical pool reference. This can save memory when many duplicate Strings exist, but excessive interning can fill the String pool, potentially causing performance issues. Interning is most beneficial when:

  • You have many duplicate String values
  • The Strings have a long lifecycle
  • String equality comparisons (==) would be beneficial for performance
  1. Use StringBuilder for concatenation in loops

    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < items.length; i++) {
    sb.append(items[i]);
    }
    String result = sb.toString();
  2. Preallocate StringBuilder capacity when size is known

    StringBuilder sb = new StringBuilder(1000); // Preallocate for 1000 chars
  3. Use String.format() or Java 15+ formatted() for complex formatting

    String formatted = String.format("Name: %s, Age: %d", name, age);
    // Or in Java 15+
    String formatted = "Name: %s, Age: %d".formatted(name, age);
  4. Use String.join() for joining collections

    String joined = String.join(", ", stringList);
  5. Consider interning for frequently used Strings

    // If these IDs are used frequently in comparisons
    Map<String, User> userMap = new HashMap<>();
    for (User user : users) {
    userMap.put(user.getId().intern(), user);
    }
  6. Use text blocks for multi-line Strings (Java 15+)

    String json = """
    {
    "name": "John",
    "age": 30
    }
    """;
  7. Use specialized methods for common operations

    // Instead of str.length() == 0
    if (str.isEmpty()) { ... }
    // Instead of str.trim().length() == 0 (Java 11+)
    if (str.isBlank()) { ... }
  8. Be careful with String.split() performance

    // For simple tokenizing, consider StringTokenizer or manual parsing
    // for performance-critical code
    String[] tokens = str.split("\\s+"); //Splits by whitespace
    //Code with StringTokenizer
    StringTokenizer tokenizer = new StringTokenizer(str, " ");
    while (tokenizer.hasMoreTokens()) {
    String token = tokenizer.nextToken();
    // Process token
    }
  9. Cache hashCode for custom classes with String fields

    public final class ImmutableKey {
    private final String value;
    private final int hashCode; // Cache hashCode
    public ImmutableKey(String value) {
    this.value = value;
    this.hashCode = value != null ? value.hashCode() : 0;
    }
    @Override
    public int hashCode() {
    return hashCode;
    }
    }
  10. Use appropriate character encoding when converting between Strings and bytes

    byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
    String fromBytes = new String(bytes, StandardCharsets.UTF_8);