StringBuilder: building a text piece by piece
Programs often build a text from many pieces: a receipt from its lines, or a list of names with commas between them. Module 24's bookshelf built its toString with text = text + "\n" + book in a loop. Here's the same idea, smaller. A seaside town rents out six beach huts, B1 to B6, which stand in a row, and its booking page draws them as a row of boxes:
String row = "";
for (int hut = 1; hut <= 6; hut++) {
row += "|B" + hut;
}
row += "|";
System.out.println(row);
|B1|B2|B3|B4|B5|B6|Why += in a loop is slow
A String never changes after it's made. row += "|B" + hut doesn't add to the old String: it makes a new one, copies every character of the old one into it, then the new piece, and the old String is thrown away. Each hut's piece is 3 characters, so the first round copies 3 characters, the second 6, the third 9, and so on. Every round copies everything built so far, once more.
For six huts, that's nothing. For 1,000 pieces of 3 characters each, the rounds copy 3 + 6 + 9 + ... + 3,000 characters: 1,501,500 in all, to make a text of 3,000 characters. Twice as many pieces means about four times as much copying, so a loop that builds a long text this way gets slower and slower as the text grows.
StringBuilder
Java's StringBuilder is a text that can change. It keeps its characters with room to spare, and append writes a new piece at the end, without copying what's already there. (When the room runs out, it makes a bigger one, again with room to spare.)
StringBuilder row = new StringBuilder();
for (int hut = 1; hut <= 6; hut++) {
row.append("|B");
row.append(hut);
}
row.append("|");
String text = row.toString();
System.out.println(text);
|B1|B2|B3|B4|B5|B6|new StringBuilder()makes an empty one, andnew StringBuilder("Total")one that starts with a text.new StringBuilder(5)doesn't hold 5: a number in the parentheses only sets how much room it starts with, and the text is empty. Usenew StringBuilder().append(5).appendadds anything to the end: a String, an int, a double, a char or a boolean.toString()returns the finished text as a String. Printing a StringBuilder, or joining it to a String with+, calls it for you, as for any object. A method that returns aStringneeds it:return row.toString();- StringBuilder is in the package
java.lang, like String, so it needs no import.
append returns the StringBuilder itself, so the calls can be chained: row.append("|B").append(hut); does the two appends in the loop in one statement.
A receipt
Here's a receipt, built line by line. Step 1's index loop goes through the two lists side by side, and \n is a line break, as in module 24:
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> items = new ArrayList<>();
items.add("Hammer");
items.add("Nails");
items.add("Tape");
ArrayList<Integer> prices = new ArrayList<>();
prices.add(12);
prices.add(4);
prices.add(3);
System.out.println(receipt(items, prices));
}
public static String receipt(ArrayList<String> items, ArrayList<Integer> prices) {
StringBuilder text = new StringBuilder();
int total = 0;
for (int i = 0; i < items.size(); i++) {
text.append(items.get(i)).append(": ").append(prices.get(i)).append(" dollars\n");
total += prices.get(i);
}
text.append("Total: ").append(total).append(" dollars");
return text.toString();
}
}
Hammer: 12 dollars
Nails: 4 dollars
Tape: 3 dollars
Total: 19 dollarsinsert, reverse and length
insert(index, text)puts a text at a position, and moves everything after it to the right.insert(0, ...)adds to the front.reverse()turns the characters around.length()andcharAt(index)work as they do for a String.
StringBuilder code = new StringBuilder("4821");
code.insert(0, "#");
code.insert(3, "-");
System.out.println(code);
code.reverse();
System.out.println(code + " has " + code.length() + " characters");
#48-21
12-84# has 6 charactersAfter the first insert, the text is #4821, and insert(3, "-") puts the dash in front of index 3, the 2.
Comparing the text
A StringBuilder has no equals of its own. It has Object's, which asks whether two variables refer to the same object (modules 23 and 34), so a StringBuilder is never equal to a String. To compare the text, turn it into a String first:
String word = "level";
StringBuilder backwards = new StringBuilder(word).reverse();
System.out.println(backwards.equals(word));
System.out.println(backwards.toString().equals(word));
false
true"level" reads the same backwards, but only the second line says so.
+ is still the clearest way to join a few pieces in one expression, as in return this.name + " (" + this.age + ")";. StringBuilder is for a text that grows in a loop.