Of course! The String class in Java is one of the most fundamental and widely used classes. Let's break it down comprehensively, from the basics to more advanced topics.
What is a String in Java?
In Java, a String is a sequence of characters. For example, "Hello, World!" is a string that contains 12 characters.
Java provides two ways to create a string object:
a. Using String Literals
This is the most common way. Java creates a special area of memory in the heap called the "String Pool" or "String Constant Pool".
String str1 = "Hello"; String str2 = "Hello";
When you create a string using a literal, Java first checks the String Pool. If the string "Hello" already exists, it reuses that object. If not, it creates a new one in the pool. In this case, str1 and str2 will point to the exact same object in memory.
// This will print 'true' because they refer to the same object in the String Pool. System.out.println(str1 == str2); // Output: true
b. Using the new Keyword
This method explicitly creates a new String object in the regular heap memory, outside the String Pool.
String str3 = new String("Hello");
String str4 = new String("Hello");
Even though the content is the same, new always creates a new object. So, str3 and str4 are two different objects.
// This will print 'false' because they are two distinct objects in the heap. System.out.println(str3 == str4); // Output: false // This will also print 'false' because str1 is in the pool and str3 is in the heap. System.out.println(str1 == str3); // Output: false
Key Takeaway: Use to check if two references point to the same object. For comparing the content of two strings, always use the .equals() method.
Immutability of Strings
This is the most important characteristic of Java Strings. Once a String object is created, its content cannot be changed.
When you perform an operation that seems to modify a string (like concatenation), you are actually creating a new String object.
String original = "Java"; original = original + " is awesome!"; // Let's see what's happening: // 1. A new string "Java is awesome!" is created. // 2. The reference variable 'original' is updated to point to this new string. // 3. The old string "Java" is now "unreferenced" and will be cleaned up by the Garbage Collector. System.out.println(original); // Output: Java is awesome!
Why is immutability important?
- Security: Strings are used to store sensitive data (like passwords, usernames). Since they can't be changed, they are secure from accidental or malicious modification.
- Thread Safety: Because they can't be changed, they are inherently thread-safe. No two threads can modify the same string object simultaneously, which prevents race conditions.
- Performance: The immutability allows Java to optimize string storage by interning strings in the pool.
Common String Methods
Here are some of the most frequently used methods:
| Method | Description | Example |
|---|---|---|
length() |
Returns the length of the string. | "hello".length() -> 5 |
charAt(int index) |
Returns the character at the specified index. | "hello".charAt(1) -> 'e' |
substring(int beginIndex) |
Returns a substring from the specified index to the end. | "hello".substring(1) -> "ello" |
substring(int begin, int end) |
Returns a substring from beginIndex to endIndex - 1. |
"hello".substring(1, 3) -> "el" |
indexOf(String str) |
Returns the index of the first occurrence of the substring. | "hello world".indexOf("world") -> 6 |
toUpperCase() / toLowerCase() |
Returns a new string with all letters converted to upper/lower case. | "Java".toUpperCase() -> "JAVA" |
trim() |
Removes leading and trailing whitespace. | " Java ".trim() -> "Java" |
replace(char old, char new) |
Replaces all occurrences of a character. | "hello".replace('l', 'p') -> "heppo" |
split(String regex) |
Splits the string into an array of substrings based on a regular expression. | "a,b,c".split(",") -> ["a", "b", "c"] |
contains(String str) |
Checks if the string contains the specified sequence of characters. | "hello".contains("ell") -> true |
equals(Object obj) |
Compares the content of two strings. | "hello".equals("hello") -> true |
equalsIgnoreCase(String str) |
Compares two strings, ignoring case differences. | "Java".equalsIgnoreCase("java") -> true |
String Concatenation ( Operator)
You can join strings using the operator. This is very convenient but can be inefficient in loops.
String firstName = "John"; String lastName = "Doe"; String fullName = firstName + " " + lastName; // "John Doe"
Performance Note: Inside a loop, repeatedly using to build a string creates many intermediate String objects, which is slow. For performance-critical string building, use StringBuilder or StringBuffer.
StringBuilder and StringBuffer
These are classes used to create and modify mutable sequences of characters.
StringBuilder
- Mutable: Its content can be changed after creation.
- Not Thread-Safe: It's faster because it doesn't have the overhead of synchronization. Use it when you are sure that only one thread will be modifying the string (e.g., inside a method).
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
String result = sb.toString(); // "Hello World"
// More efficient way:
StringBuilder sb2 = new StringBuilder("Hello");
sb2.append(" ").append("World"); // Method chaining is common
String result2 = sb2.toString(); // "Hello World"
StringBuffer
- Mutable: Its content can also be changed.
- Thread-Safe: All its methods are synchronized, making it safe for use in multi-threaded environments. Because of this synchronization, it's slightly slower than
StringBuilder.
| Feature | String |
StringBuilder |
StringBuffer |
|---|---|---|---|
| Mutability | Immutable | Mutable | Mutable |
| Thread Safety | Thread-safe (immutable) | Not Thread-safe | Thread-safe |
| Performance | N/A (immutability has cost) | Fastest | Slower (due to sync) |
| Use Case | Fixed text, keys, identifiers | General-purpose string building in single-threaded apps | Building strings in multi-threaded apps |
String vs char[]
String: A high-level, object-oriented class. It's easy to use and comes with many built-in methods. However, it's immutable, which can be a drawback for performance.char[](Character Array): A low-level, mutable data structure. It's more memory-efficient and faster for operations that modify character sequences. However, you have to write the logic for operations like searching, concatenating, etc., yourself.
When to use which?
- Use
Stringfor almost everything unless you have a very specific performance reason to use achar[]. The convenience and features ofStringare worth the small overhead in most cases.
Summary
| Concept | Key Point |
|---|---|
| Creation | Use literals () for reuse in the String Pool. Use new to force a new object in the heap. |
| Immutability | String objects cannot be changed. Operations create new objects. |
| Comparison | Use .equals() to compare content. Use to compare if two references point to the same object. |
| Modification | Use StringBuilder (fast, single-threaded) or StringBuffer (thread-safe, slower) for building strings. |
| Best Practice | Prefer String literals and .equals() for everyday use. Use StringBuilder for performance-sensitive string building. |
