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.
String Fundamentals
Section titled “String Fundamentals”String Class Characteristics
Section titled “String Class Characteristics”- 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
String Creation
Section titled “String Creation”There are two primary ways to create Strings in Java:
// String literal - uses String poolString literalString = "Hello";
// String object - creates new object in heapString objectString = new String("Hello");String Pool
Section titled “String Pool”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 poolString s2 = "Java"; // Reuses the same "Java" from poolString 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)String Interning
Section titled “String Interning”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 referenceString s2 = "Hello"; // From poolSystem.out.println(s1 == s2); // trueString Operations and Performance
Section titled “String Operations and Performance”String Concatenation
Section titled “String Concatenation”Multiple ways to concatenate Strings in Java:
// Using + operatorString result = "Hello" + " " + "World"; // Compiler optimizes this to StringBuilder
// Using concat() methodString 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);Performance Considerations
Section titled “Performance Considerations”// BAD: Creates many intermediate String objectsString result = "";for (int i = 0; i < 10000; i++) { result += i; // Each iteration creates a new String}
// GOOD: Uses a single StringBuilderStringBuilder sb = new StringBuilder();for (int i = 0; i < 10000; i++) { sb.append(i); // Modifies the same StringBuilder}String result = sb.toString();String vs StringBuilder vs StringBuffer
Section titled “String vs StringBuilder vs StringBuffer”| Class | Mutability | Thread Safety | Performance |
|---|---|---|---|
| String | Immutable | Thread-safe | Slower for multiple modifications |
| StringBuilder | Mutable | Not thread-safe | Fastest for single-threaded scenarios |
| StringBuffer | Mutable | Thread-safe (synchronized) | Slower than StringBuilder due to synchronization |
String Methods and Operations
Section titled “String Methods and Operations”Common String Methods
Section titled “Common String Methods”String str = "Java Programming";
// Basic operationsint length = str.length(); // 16char charAtIndex = str.charAt(5); // 'P'boolean isEmpty = str.isEmpty(); // falseboolean isBlank = str.isBlank(); // false (Java 11+)
// Comparisonboolean equals = str.equals("java programming"); // false (case-sensitive)boolean equalsIgnoreCase = str.equalsIgnoreCase("java programming"); // trueint compareResult = str.compareTo("Java"); // Positive value
// Searchingint indexOfP = str.indexOf('P'); // 5int lastIndexOfm = str.lastIndexOf('m'); // 11boolean containsProgram = str.contains("Program"); // trueboolean startsWithJava = str.startsWith("Java"); // trueboolean endsWithing = str.endsWith("ing"); // true
// ExtractionString 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"
// Conversionchar[] charArray = str.toCharArray(); // {'J','a','v','a',' ','P',...}byte[] bytes = str.getBytes(); // byte representationbyte[] bytesUtf8 = str.getBytes(StandardCharsets.UTF_8); // UTF-8 bytesJava 8+ String Methods
Section titled “Java 8+ String Methods”// Java 8 String.joinString joined = String.join(", ", "Java", "C++", "Python"); // "Java, C++, Python"
// Java 11 strip methodsString stripped = " Java \u2000".strip(); // "Java" (removes Unicode whitespace)String stripLeading = " Java".stripLeading(); // "Java"String stripTrailing = "Java ".stripTrailing(); // "Java"
// Java 11 repeatString repeated = "Java".repeat(3); // "JavaJavaJava"
// Java 11 isBlankboolean isBlank = " \t \n ".isBlank(); // true
// Java 12 indentString indented = "Java".indent(4); // " Java\n"
// Java 12 transformString transformed = "Java".transform(s -> s + " Programming"); // "Java Programming"
// Java 13+ text blocksString textBlock = """ <html> <body> <p>Hello, World!</p> </body> </html> """; // Multi-line string with preserved formatting
// Java 15+ formattedString formatted = "%s has %d beans".formatted("Jack", 42); // "Jack has 42 beans"String Internals
Section titled “String Internals”Internal Representation
Section titled “Internal Representation”- 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
String Hashcode Implementation
Section titled “String Hashcode Implementation”// Equivalent to String's hashCode() implementationpublic 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;}Common String Interview Questions
Section titled “Common String Interview Questions”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.
3. Why are Strings immutable in Java?
Section titled “3. Why are Strings immutable in Java?”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 objectequals()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 StringBuilderpublic String reverseString(String str) { if (str == null) return null; return new StringBuilder(str).reverse().toString();}
// Manual implementationpublic 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
Comparatorfor specialized ordering (e.g., by length, by specific locale)
// Natural orderingList<String> names = Arrays.asList("Charlie", "Alice", "Bob");Collections.sort(names); // ["Alice", "Bob", "Charlie"]
// Case-insensitiveCollections.sort(names, String.CASE_INSENSITIVE_ORDER);
// Custom ordering (by length)Collections.sort(names, Comparator.comparingInt(String::length));
// Locale-specificCollections.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 expressionpublic 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
String Best Practices
Section titled “String Best Practices”-
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(); -
Preallocate StringBuilder capacity when size is known
StringBuilder sb = new StringBuilder(1000); // Preallocate for 1000 chars -
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); -
Use String.join() for joining collections
String joined = String.join(", ", stringList); -
Consider interning for frequently used Strings
// If these IDs are used frequently in comparisonsMap<String, User> userMap = new HashMap<>();for (User user : users) {userMap.put(user.getId().intern(), user);} -
Use text blocks for multi-line Strings (Java 15+)
String json = """{"name": "John","age": 30}"""; -
Use specialized methods for common operations
// Instead of str.length() == 0if (str.isEmpty()) { ... }// Instead of str.trim().length() == 0 (Java 11+)if (str.isBlank()) { ... } -
Be careful with String.split() performance
// For simple tokenizing, consider StringTokenizer or manual parsing// for performance-critical codeString[] tokens = str.split("\\s+"); //Splits by whitespace//Code with StringTokenizerStringTokenizer tokenizer = new StringTokenizer(str, " ");while (tokenizer.hasMoreTokens()) {String token = tokenizer.nextToken();// Process token} -
Cache hashCode for custom classes with String fields
public final class ImmutableKey {private final String value;private final int hashCode; // Cache hashCodepublic ImmutableKey(String value) {this.value = value;this.hashCode = value != null ? value.hashCode() : 0;}@Overridepublic int hashCode() {return hashCode;}} -
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);