In Java, strings are fundamental for data manipulation. This article delves into the core concepts of Java's String
and StringBuilder
classes, essential components of the Java Standard Library. It will explore how to create and manipulate String objects, understand the nuances of immutability, and leverage the string pool for efficiency. Additionally, the mutable nature of the StringBuilder class and its methods for dynamic string construction will be discussed here, providing a comprehensive understanding of string handling in Java.
Table of Contents
- Working with String Objects
- Manipulate Data Using the StringBuilder Class
- Understanding Equality: String vs. StringBuilder
String APIs refer to the set of methods and functionalities provided by the String
and StringBuilder
classes, which are part of the Java Standard Library.
1. Working with String
Objects
The String
class is fundamental in Java, representing an immutable sequence of characters. It implements the CharSequence
interface, a general way of representing character sequences, also implemented by classes like StringBuilder
. In Java, a String
object can be created as follows (the new
keyword is optional):
String name = "Fluffy";
String name = new String("Fluffy");
In both examples, a reference variable name
is created, pointing to a String
object with the value "Fluffy". However, the creation process differs subtly. The String
class is unique in that it doesn’t require instantiation with new
, it can be created directly from a literal.
Concatenation
Placing one String
object before another String
object and combining them (using the +
operator) is called string concatenation.
The most important rules to know are:
- If both operands are numeric,
+
means numeric addition. - If either operand is a
String
,+
means concatenation. - The expression is evaluated left to right.
System.out.println(1 + 2); // 3
System.out.println("a" + "b"); // "ab"
System.out.println("a" + "b" + 3); // "ab3"
System.out.println(1 + 2 + "c"); // "3c"
System.out.println("c" + 1 + 2); // "c12" => "c1" + 2 => "c12"
int three = 3;
String four = "4";
System.out.println(1 + 2 + three + four); // "64" => 3 + 3 + "4" => 6 + “4"
Immutability and the String Pool
In Java, the String
class is immutable. Once a String
object is created, its value cannot be changed. This design choice offers key benefits:
- Immutable
String
objects are inherently thread-safe, which prevents data corruption in parallel environments. - Immutability increases security. For example, class names or arguments passed to methods are often represented as strings. By making strings immutable, you prevent attackers from changing these values after they are passed.
- Since
String
objects are immutable, their hash codes can be cached without worrying about changes. This makesString
objects suitable for use as keys inHashMap
and other hash-based data structures, improving performance.
The immutability of String
objects is demonstrated in the following example:
String s1 = "1";
String s2 = s1.concat("2");
s2.concat("3"); // This call is effectively ignored
System.out.println(s2); // Output: "12"
In this example, s1.concat("2")
creates a new String object, "12"
, and assigns its reference to s2
. Even though s2.concat("3")
is called, it doesn't change the value of s2
. Because strings are immutable, concat()
always returns a new String object. Since the result of s2.concat("3")
isn't assigned to anything, it's discarded, and s2
remains "12"
.
Since strings are everywhere in Java, they can consume significant memory, especially in production applications. To mitigate this, Java reuses common strings through the string pool (also known as the intern pool), a special memory area in the Java Virtual Machine (JVM) that stores String
literals to optimize memory usage.
So, the string pool contains String
literals and constants. For example, x
or y
are String
literals and will be placed in the string pool. But, the myObject.toString()
is a string but not a literal, so it typically doesn’t go into the string pool directly. And String
objects created with the new
keyword are not automatically added to the string pool:
String x = "Hello World"; // x goes into the string pool
String y = "Hello World"; // y goes into the string pool
System.out.println(x == y); // true (x and y refer to the same object)
String z = "Hello World";
String myObject = new String("Hello World"); // this is object
System.out.println(z == myObject); // false
The String
object created as a result of runtime calculations (e.g., using String
methods), even if they have the same content, a new String
object will be created:
String x = "Hello World";
String z = " Hello World".trim(); // a new String object is created
System.out.println(x == z); // false (x and z are different objects)
Concatenation with non-literals also results in a new String
object:
String single = "hello world";
String concat = "hello ";
concat += "world";
System.out.println(single == concat); // false
When a String
literal is created, the JVM first checks if an identical String
already exists in the pool. If it does, the variable simply points to the existing String
object in the pool, avoiding the creation of a duplicate. This mechanism offers significant memory savings, especially in applications that use many identical string values.
Essential String
Methods and the Method Chaining
For all these methods, remember that a String
is a sequence of characters, and Java indexing starts at 0
.
-
length()
returns the length of theString
object (number of characters):
"animals".length(); // 7
"".length(); // 0
-
toLowerCase()
/toUpperCase()
return a newString
object with all characters converted to lowercase or uppercase, respectively:
"AniMaLs".toLowerCase(); // "animals"
"AniMaLs".toUpperCase(); // "ANIMALS"
-
equals()
/equalsIgnoreCase()
compare the content of twoString
objects. Theequals()
is case-sensitive, whileequalsIgnoreCase()
is not. Both methods return aboolean
:
"animals".equals("animals"); // true
"animals".equals("ANIMALS"); // false
"animals".equalsIgnoreCase("ANIMALS"); // true
-
contains()
checks if theString
object contains a specified sequence of characters. Returns aboolean
:
"animals".contains("ani"); // true
"animals".contains("als"); // true
"animals".contains("AlS"); // false (case-sensitive)
-
startsWith()
/endsWith()
check if theString
object starts or ends with a specified prefix or suffix. Returns aboolean
:
"animals".startsWith("ani"); // true
"animals".endsWith("als"); // true
"animals".startsWith("als"); // false
-
replace()
returns a newString
object in which all occurrences of a specified sequence of characters are replaced with another sequence:
"animals".replace("a", "o"); // "onimols"
"animals".replace("als", "zzz"); // "animzzz"
-
charAt()
returns thechar
at the specified index:
"animals".charAt(0); // a
"animals".charAt(6); // s
"animals".charAt(7); // throws exception
-
indexOf()
finds the first index that matches the specified character or substring. Returns-1
if no match is found and doesn’t throw an exception. Returns anint
:
"animals".indexOf('a'); // 0
"animals".indexOf("al"); // 4
"animals".indexOf('a', 4); // 4
"animals".indexOf("al", 5); // -1
-
substring()
returns parts of theString
object:
String string = "animals";
"animals".substring(3); // "mals"
"animals".substring(string.indexOf('m')); // "mals"
"animals".substring(3, 5); // "ma"
"animals".substring(3, 3); // empty string
"animals".substring(3, 2); // throws exception
"animals".substring(3, 8); // throws exception
-
trim()
/strip()
remove whitespace from the beginning and end of aString
object. Thetrim()
works only with ASCII-spaces, thestrip()
(new in Java 11) and supports Unicode:
String text = " abc\t ";
text.trim().length(); // 3
text.strip().length(); // 3
-
stripLeading()
/stripTrailing()
(new in Java 11). The first removes whitespace from the beginning and leaves it at the end. ThestripTrailing()
does the opposite:
String text = " abc\t ";
text.stripLeading().length(); // 5
text.stripTrailing().length(); // 4
-
intern()
uses an object in the string pool if one is present. If the literal is not yet in the string pool, Java will add it at this time:
String x = "Hello World";
String y = new String("Hello World").intern();
System.out.println(x == y); // true
String first = "rat" + 1;
String second = "r" + "a" + "t" + "1";
String third = "r" + "a" + "t" + new String("1");
System.out.println(first == second); // true
System.out.println(first == second.intern()); // true
System.out.println(first == third); // false
System.out.println(first == third.intern()); // true
So, String
methods can be used sequentially, with each method call assigning its resulting String
object to a new variable:
String start = "AniMaL ";
String trimmed = start.trim(); // "AniMaL"
String lowercase = trimmed.toLowerCase(); // "animal"
String result = lowercase.replace('a', 'A'); // "AnimAl"
However, this approach can become verbose and less readable. Instead, Java allows you to use a technique called method chaining, where multiple method calls are chained together in a single expression:
String result = "AniMaL "
.trim()
.toLowerCase()
.replace('a', 'A'); // "AnimAl"
2. Manipulate Data Using the StringBuilder
Class
The StringBuilder
class represents mutable sequences of characters. Most of its methods return a reference to the current object, enabling method chaining.
StringBuilder Methods
The StringBuilder
class has many methods: substring()
does not change the value of a StringBuilder
, whereas append()
, delete()
, and insert()
does change it.
StringBuilder sb = new StringBuilder("start");
sb.append("+middle"); // "start+middle"
StringBuilder same = sb.append("+end"); // "start+middle+end"
StringBuilder a = new StringBuilder("abc");
StringBuilder b = a.append("de");
b = b.append("f").append("g");
System.out.println(a); // Output: "abcdefg"
System.out.println(b); // Output: "abcdefg"
charAt()
, indexOf()
, length()
, and substring()
: these four methods work the same as in the String
class:
-
append()
adds values (e.g.,String
,char
,int
,Object
, etc.) to the end of the currentStringBuilder
object:
StringBuilder sb = new StringBuilder().append(1).append('c');
sb.append("-").append(true); // sb -> "1c-true"
-
insert()
adds characters at the requested index. Theoffset
is the index where we want to insert the requested parameter:
StringBuilder sb = new StringBuilder("animals");
sb.insert(7, "-"); // sb -> "animals-"
sb.insert(0, "-"); // sb -> "-animals-"
sb.insert(4, "-"); // sb -> "-ani-mals-"
As we add and remove characters, their indexes change. Also, if offset
is equal to length() + 1
a StringIndexOutOfBoundsException
will be thrown.
-
delete()
/deleteCharAt()
, the first removes characters from sequence, asdeleteCharAt()
uses to remove only one character at the specified index:
StringBuilder sb = new StringBuilder("abcdef");
sb.delete(1, 3); // sb -> "adef" (the length is 4)
sb.deleteCharAt(5); // throws an exception (the index is out of range)
Even if delete()
is called with an end index beyond the length of the StringBuilder
, it will not throw an exception but will simply delete up to the end of the StringBuilder
:
StringBuilder sb = new StringBuilder("abcdef");
sb.delete(1, 100); // sb -> "a"
-
replace()
deletes the characters starting withstartIndex
and ending beforeendIndex
:
StringBuilder builder = new StringBuilder("pigeon dirty");
builder.replace(3, 6, "sty"); // sb -> "pigsty dirty"
builder.replace(3, 100, "a"); // sb -> "piga"
builder.replace(2, 100, ""); // sb -> "pi"
-
reverse()
reverses the characters in theStringBuilder
object:
StringBuilder sb = new StringBuilder("ABC");
sb.reverse(); // sb -> "CBA"
3. Understanding Equality: String
vs. StringBuilder
Equality Comparison in Java: String
-
==
: Can returntrue
if bothString
literals point to the same object in the string pool. Fornew String()
, it typically returnsfalse
. -
String.equals()
: Compares the content (sequence of characters) of the strings. Returnstrue
if the contents are identical.
String a = "Fluffy";
String b = new String("Fluffy");
a == b; // false
a.equals(b); // true
String x = "Hello World";
String z = " Hello World".trim();
x == y; // false
x.equals(z); // true
Equality Comparison in Java: StringBuilder
-
==
: Always checks for reference equality, just like other non-pooled objects. It will returntrue
only when both references point to the same StringBuilder object. -
StringBuilder.equals()
: Compares the object references. Returns true only if both references point to the same StringBuilder object.
StringBuilder one = new StringBuilder();
StringBuilder two = new StringBuilder();
StringBuilder three = one.append("a");
one == two; // false
one.equals(two); // false
one == three; // true
one.equals(three); // true
❗Note: If the class does not have an equals()
method, Java uses the ==
operator to check equality. Remember that ==
is checking for object reference equality.
public class Cat {
String name;
public void main(String[] args) {
Cat t1 = new Cat();
Cat t2 = new Cat();
Cat t3 = t1;
t1 == t2; // false
t1 == t3; // true
t1.equals(t2); // false: equals() is absent -> t1 == t2
}
}
The compiler is smart enough to know that two references can’t possibly point to the same object when they are completely different types.
String string = "a";
StringBuilder builder = new StringBuilder("a");
string == builder; // DOES NOT COMPILE
builder.equals(string) // false
Top comments (0)