String, StringBuffer, and StringBuilder - 2020
Handling characters in supported through three final classes: String, StringBuilder and StringBuffer.
StringBuilder class was added to the API, to provide faster, nonsynchronized StringBuffer capability. The StringBuilder class has exactly the same methods as the old StringBuffer class, but StringBuilder is faster because its methods aren't synchronized. Both classes give us String-like object that handle some of the String class's shortcomings such as immutability.
The String class implement immutable character strings, which are read-only once the string has been created and initialized, whereas StringBuilder class implements dynamic character strings. The StringBuffer class is a thread-safe version of the StringBuilder class.
A String object can never be changed. But what is happening when a String object seems to be changing?
Each character in a string is a 16-bit Unicode character. Since Unicode characters are 16-bit, international set of characters is easily represented in Unicode.
Because strings are object, we can create an instance of a String with new keyword.
String s = new String();
This creates a new object of class String, and assigns it to the reference variable s. Let's give it to some value:
s = "Java";
Actually, because String class has lots of constructors, we can do the same in one line:
String s = "Java";
Another String object:
String s2 = s;
So, s and s2 refer the same object.
How about the following line?
s = s.concat(" String");
Aren't Strings immutable?
Let's look at the following example.
public class StringTest { public static void main(String[] args) { String s = "Java"; // (1) String s2 = s; // (2) s = s.concat(" String"); // (3) System.out.println("s = " + s); System.out.println("s2 = " + s2); } }
Output is:
s = Java String s2 = Java
On the heap, we have two String objects: "Java" and "Java String". Also we have two reference variables: s refers to "Java String" and s2 refers to "Java" starting from line (2). So, at the line (3), the old String "Java" did not changed to "Java String" instead a new String was created and initialize with "Java String".
How about the output from the following code?
public class StringTest { public static void main(String[] args) { String s = "Java"; String s2 = s; s = s.concat(" String"); System.out.println("s = " + s); System.out.println("s2 = " + s2); s.concat(" is Immutable!"); // What's happening? System.out.println("s = " + s); } }
Output is:
s = Java String s2 = Java s = Java String
A new String is created but it has not reference variable which can point to the newly create string.
s.concat(" is Immutable!"); // What's happening?
So, we do have a String object, "Java String is Immutable!". But we do not have the reference which can refer to the object. New String object has been created but we lost it. It's somewhere on the heap. But we do not know how we can get it.
How about this?
String s = "Android"; s.toUpperCase(); System.out.println("s = " + s);
The output is still:
s = Android
Same result from the following code:
s = "Android"; s.replace('d','D'); System.out.println("s = " + s);
The output is still:
s = Android
How about this?
s = "Android"; s = s.concat(" is getting better"); System.out.println("s = " + s);
Output is:
s = Android is getting better
Finally we get a new string!
But we lost our old String object, "Android".
What's the output from the following code?
String s1 = "North "; String s2 = s1 + "East "; s1.concat("South "); s2.concat(s1); s1 += "West "; System.out.println(s1 + " " + s2);
Output is
North West North East
We have two reference variables, s1 and s2. We had a total of eight String objects created as follows in order: "North", "East" (lost), "North East", "South" (lost), "North South" (lost), "North East North" (lost), "West" (lost), "North West" (at this point, the first object "North" is lost). So, only two of the eight String objects are not lost in this process.
As application grows, it's very common for String literals to occupy large amounts of memory, and there are often a lot of redundant String literals on the heap. To make Java more memory efficient, the JVM sets aside a special area of memory called String constant pool.
When the compiler encounters a String literal, it checks the pool to see if an identical String already exists. If there is a match, the reference to the new literal is directed to the existing String, and as a result, no new String literal object is created.
If several reference variables refer to the same String without even knowing it, it would be very bad if any of them could change the String's value.
The reason the String class is marked final is to prevent somebody from overriding the behavior of any of the String method. In that way, we can rest assured that the String objects we are counting on to be immutable, will remain immutable.
When we do
String s = "Java";
we are create one String object and one reference variable and the String object "Java" will go in the pool and s will refer to it.
But when we do
String s = new String("Java");
Java will create a new String object in normal (which is not pool) memory because we are using new keyword. The reference variable s will refer to it. The literal "Java" will be placed in the pool.
- char charAt(int index)
This method returns the character located at the String's specified index.
String s = "Michelangelo"; System.out.println(s.charAt(4)); // output is 'e'
The following code checks if a string consists of unique string:
(1) using 256 integer array, (2) using an integer as a flag utilizing the bit pattern in it (assuming a string is has lower case only from a to z).package com.bogotobogo; public class UniqueString { public static void main(String[] args){ String[] str = {"Garbage", "Collection", "String"}; for(String s: str) { IsUnique(s); IsUniqueBits(s); } } public static void IsUnique(String str) { boolean flag[] = new boolean[256]; int index = 0; for (int i = 0; i < str.length(); i++) { index = str.charAt(i); if(flag[index] == true) { System.out.println(str + " is not unique"); return; } flag[index] = true; } System.out.println(str + " is unique"); } public static void IsUniqueBits(String str) { int flag = 0; str.toLowerCase(); for(int i = 0; i < str.length(); i++) { int bitPosition = str.charAt(i)-'0'; if( (flag & (1 << bitPosition)) > 0 ) { System.out.println(str + " is not unique"); return; } flag |= 1 << bitPosition; } System.out.println(str + " is unique"); } }
Output:
Garbage is not unique Garbage is not unique Collection is not unique Collection is not unique String is unique String is unique
- String concat(String s)
This method returns a String with the value of the String passed in to the method appended to the end of the String used to invoke the method.
String s = "Dali: "; System.out.println(s.concat("Persistence of Memory")); // output is "Dali: Persistence of Memory"
- boolean equalsIgnoreCase(String s)
This method returns a boolean value depending on whether the value of the String in the argument is the same as the value of the String used to invoke the method. This method will return true even when characters in the String objects being compared have different cases.
String s = "Klimt"; System.out.println(s.equalsIgnoreCase("klimt")); // "true" System.out.println(s.equalsIgnoreCase("klit")); // "false"
- int length()
This method returns the length of the String used to invoke the method.
String s = "Van Gogh"; System.out.println(s.length()); // output is "8"
- String replace(char old, char new)
This method returns a String whose value is that of the String used to invoke the method, updated so that any occurence of the char in the first argument is replaced by the char in the second argument.
String s = "Rembrandt"; System.out.println(s.replace('r','R')); // output is "RembRandt"
- String substring(int begin)
String substring(int begin, int end)
This method is used to return a part of the String used to invoke the method.
String s = "Velasquez"; System.out.println(s.substring(1)); // output is "elasquez" System.out.println(s.substring(1,4)); // output is "ela"
- String toLowerCase()
This method returns a String whose value is the String used to invoke the method, but with any uppercase characters converted to lowercase.
String s = "Rousseau"; System.out.println(s.toLowerCase()); // output is "rousseau"
- String toString()
This method returns the value of the String used to invoke the method. Why do we need such method? All objects in Java must have a toString() method, which typically returns a String that in some meaningful way of describing the object in question. In the case of a String object, what is more meaningful way than the String's value?
String s = "Renoir"; System.out.println(s.toString()); // output is "Renoir"
- String toUpperCase()
This method returns a String whose value is the String used to invoke the method, but with lowercase characters converted to uppercase.
String s = "goya"; System.out.println(s.toUpperCase()); // output is "GOYA"
- String trim()
This method returns a String whose value is the String used to invoke the method, but with any leading or trailing blank spaces removed.
String s = " Rubens "; System.out.println(s.trim() + ": Debarquement de Marie de Medicis"); // output is "Rubens: Debarquement de Marie de Medicis"
- String is immutable while StringBuffer and StringBuilder is mutable object.
- StringBuffer is synchronized while StringBuilder is not which makes StringBuilder faster than StringBuffer.
- Concatenation operator "+" is internal implemented using either StringBuffer or StringBuilder.
- Use String if immutability is required, use Stringbuffer if mutable and thread-safety are rerquired and use StringBuilder if mutable is reuired but not thread-safety.
- It's very rare for a String manimuplating requires thread-safety. So, in most cases, we should use StringBuilder rather than using StringBuffer.
Main difference between String and StringBuffer is String is immutable while StringBuffer is mutable. This means we can modify a StringBuffer object once you created it without creating any new object. This mutable property makes StringBuffer an ideal choice for dealing with Strings in Java. We can convert a StringBuffer into String by its toString() method.
StringBuffer is very good with mutable String but it has one disadvantage: All its public methods are synchronized which makes it thread-safe but same time slow. StringBuilder in Java is a copy of StringBuffer but without synchronization. Try to use StringBuilder whenever possible it performs better in most of cases than StringBuffer class. We can also use "+" for concatenating two string because "+" operation is internal implemented using either StringBuffer or StringBuilder in Java.
The code below is an example of concatinating strings using the three methods of String handling.
package com.bogotobogo; public class Concat { public static void main(String[] args) { String [] sIn = {"An ", "Array ", "of ", "Java ", "strings"}; System.out.println("String: " + concatString(sIn)); System.out.println("StringBuffer: " + concatStringBuffer(sIn)); System.out.println("StringBuilder: " + concatStringBuilder(sIn)); } public static String concatString(String[] in) { String sentence = ""; for(String s: in) sentence = sentence.concat(s); return sentence; } public static String concatStringBuffer(String[] in) { StringBuffer sentence = new StringBuffer(); for(String s: in) sentence.append(s); return sentence.toString(); } public static String concatStringBuilder(String[] in) { StringBuilder sentence = new StringBuilder(); for(String s: in) sentence.append(s); return sentence.toString(); } }
All of them are giving us the same output: "An Array of Java strings". However, there are some differences among them. Next section will discuss those differences.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization