Text up close
Problem
The report that drags
You already know that title.toUpperCase(); does nothing on its own: a string is immutable, and the method returns a new string. This has a cost, and you can see it in a loop.
The online store's program builds the daily report line by line:
String report = "";for (int i = 1; i <= 3; i++) { report += "Order " + i + ": €12.50\n";}System.out.print(report);Order 1: €12.50Order 2: €12.50Order 3: €12.50With three orders it is instant. On Saturday there are 50,000 orders, and the same loop takes about 2 seconds. We measured it on the machine where we check the code of the lessons.
Why? Each report += ... does not add to the old string. It makes a new string, copies the whole report so far into it, and only then glues on the new line. Near the end, every iteration copies almost a million characters.
The idea
StringBuilder: text that changes
To build text from many pieces, there is StringBuilder. It is an object that keeps the text in its own buffer and adds in place, without copying everything again:
StringBuilder report = new StringBuilder();for (int i = 1; i <= 3; i++) { report.append("Order ").append(i).append(": €12.50\n");}System.out.print(report.toString());Order 1: €12.50Order 2: €12.50Order 3: €12.50new StringBuilder()creates an empty "builder".append(...)adds to the end. It takes text, a number or a character, and returns the same object, so you can chain the calls one after another.toString()makes a plainStringfrom it, once, at the end.
With 50,000 orders this version takes about 7 milliseconds instead of 2 seconds.
Here is what happens in memory with each approach. To show that the buffer is not copied, here it is created with room for 64 characters.
String report = "";for (int i = 1; i <= 3; i++) { report += "Order " + i + ": €12.50\n";}StringBuilder builder = new StringBuilder(64);for (int i = 1; i <= 3; i++) { builder.append("Order ").append(i).append(": €12.50\n");}System.out.println(report.length() + " " + builder.length());System.out.println(report.equals(builder.toString()));Step by step, in words
- report points to an empty string.
- The first += makes a new String with one line: 16 characters.
- The second += makes one more String. It copies the old line into it (16 characters) and only then glues on the new one. Nobody needs the old string anymore.
- The third copies 32 characters. Every line adds to the copying: with 50,000 lines it is almost a million characters per iteration.
- StringBuilder is one object with a buffer. Here the buffer holds 64 characters.
- append writes into the buffer in place. Nothing is copied, and the object stays the same.
- The text is the same, 48 characters. With += another 48 were copied, and with StringBuilder not a single one.
More precisely, if you are curious
- The buffer grows on its own: when it fills up,
StringBuilderallocates one about twice as big and copies once. That is why copying is rare. - The compiler turns a single expression like
"Order " + i + ": " + priceinto efficient code by itself. The problem is only+=in a loop, where every iteration is a separate new string. - There is also
StringBufferwith the same methods. It is safe when several threads write to one object at the same time, and that makes it a bit slower. Threads come in the module "Real-world programs". Until then, useStringBuilder.
See it
Changing in place
StringBuilder can do more than add. Here is a card number for printing on a receipt:
StringBuilder card = new StringBuilder("4532015112830366");card.insert(12, ' ').insert(8, ' ').insert(4, ' ');System.out.println(card);card.replace(0, 14, "**** **** ****");System.out.println(card);
StringBuilder phone = new StringBuilder("0888-123-456");phone.deleteCharAt(8).deleteCharAt(4);System.out.println(phone);System.out.println(new StringBuilder("stressed").reverse());4532 0151 1283 0366**** **** **** 03660888123456desserts| Method | What it does |
|---|---|
insert(i, x) |
inserts x before the character at index i |
replace(a, b, x) |
replaces the characters from a to b (without b) with x |
deleteCharAt(i) |
removes the character at index i |
delete(a, b) |
removes the characters from a to b (without b) |
reverse() |
reverses the text, back to front |
length(), charAt(i) |
the same as for String |
The spaces in the card number go in from the back: first at 12, then at 8, then at 4. If you start at 4, the inserted space shifts everything after it, and 8 is no longer the place you want. The dashes in the phone number are removed from the back first for the same reason.
Try it
Two names, one object
What will the program print?
StringBuilder note = new StringBuilder("Table 4");StringBuilder copy = note;copy.append(", 2 coffees");System.out.println(note);
String text = "Table 4";String other = text;other += ", 2 coffees";System.out.println(text);StringBuilder is an ordinary object. copy = note copies the arrow, both variables point to the same object, and append changes it in place. A String is different: other += ... makes a new string and moves only other to it. text still points to the old one.The idea
In alphabetical order: compareTo
equals only tells you "are they the same". To sort, you need more: which string comes before the other. That is what compareTo tells you:
System.out.println("Andreev".compareTo("Borisov"));System.out.println("Borisov".compareTo("Borisov"));System.out.println("ivanova".compareTo("Ivanova"));System.out.println("ivanova".compareToIgnoreCase("Ivanova"));-10320- a negative number: the first one comes before the second;
- 0: they are the same;
- a positive number: the first one comes after the second.
The sign matters, not the number itself. With it you sort a list of names, for example with the bubble sort from the lesson on arrays:
String[] names = { "Petrova", "Andreev", "Ivanova", "Borisov" };for (int i = 0; i < names.length - 1; i++) { for (int j = 0; j < names.length - 1 - i; j++) { if (names[j].compareTo(names[j + 1]) > 0) { String tmp = names[j]; names[j] = names[j + 1]; names[j + 1] = tmp; } }}System.out.println(String.join(", ", names));Andreev, Borisov, Ivanova, PetrovaString.join(", ", names) glues the elements of the array with a separator between them.
More precisely, if you are curious
compareTocompares character by character, by their codes in the character table. Capital letters come before lowercase ones, so"ivanova".compareTo("Ivanova")is positive.compareToIgnoreCaseignores upper and lower case.- The number is the difference between the codes of the first characters that differ (here "i" and "I" differ by 32), or between the lengths, if one string is the start of the other.
- To sort by the rules of a language, for example with accented letters like "é", there is
java.text.Collator.getInstance(Locale.of("en")). java.util.Arrays.sort(names)sorts an array of strings by the same rule, without writing the bubble sort.
Try it
Invoice 10 or 9?
The invoice numbers are text. Which one is earlier?
What will the program print?
String a = "INV-10";String b = "INV-9";System.out.println(a.compareTo(b) < 0);System.out.println(Integer.parseInt(a.substring(4)) < Integer.parseInt(b.substring(4)));Text is compared character by character. The first four characters are the same, then come "1" and "9", and "1" comes before "9". So "INV-10" comes before "INV-9", just as "ba" comes before "c" in a dictionary. The numbers 10 and 9 are compared as numbers only after parseInt. If you sort numbers as text, pad them with zeros: INV-009, INV-010.
The idea
Search, cut, put together
Here is a line from a contacts file: name, email and city, separated with " ; ", with extra spaces at the ends:
String line = " Elena Koleva ; elena.koleva@company.com ; Plovdiv ";String[] parts = line.strip().split(" ; ");String email = parts[1];int at = email.indexOf('@');System.out.println(email.substring(0, at));System.out.println(email.substring(at + 1));System.out.println(email.indexOf('.', at));System.out.println(String.join(" | ", parts));System.out.println("=".repeat(20));elena.kolevacompany.com20Elena Koleva | elena.koleva@company.com | Plovdiv====================strip()removes the spaces at the front and at the back, liketrim(), but it also knows the "wide" spaces from other languages. Use it in new code.indexOf('@')gives the index of the first occurrence, andindexOf('.', at)starts searching only from indexaton.lastIndexOfsearches from the back.- You already know
substringandsplit.String.joinis the opposite ofsplit. "=".repeat(20)repeats the text 20 times.
Empty and "blank" are not the same thing:
String empty = "";String spaces = " ";System.out.println(empty.isEmpty() + " " + spaces.isEmpty());System.out.println(empty.isBlank() + " " + spaces.isBlank());true falsetrue trueisEmpty() is true only for text of length 0. isBlank() is also true when there are only spaces. To check a field someone filled in, you usually want isBlank().
The idea
Numbers and text
Numbers go into text and come out of text:
int code = Integer.parseInt("0042");double price = Double.parseDouble("2.40");String label = String.valueOf(code) + "-" + String.valueOf(price);System.out.println(label);42-2.4You already know Integer.parseInt and Double.parseDouble. String.valueOf is the opposite: it makes text from a number, a character or a boolean.
For a table with columns you need String.format from the lesson on input, with a width for each column. Here is a receipt from a café:
String[] names = { "Coffee", "Croissant", "Mineral water" };double[] prices = { 2.40, 3.10, 1.50 };int[] qty = { 2, 1, 3 };double total = 0;for (int i = 0; i < names.length; i++) { double sum = prices[i] * qty[i]; total += sum; System.out.println(String.format("%-16s%2d x %5.2f = %6.2f", names[i], qty[i], prices[i], sum));}System.out.println("-".repeat(35));System.out.println("%-29s€%5.2f".formatted("Total", total));Coffee 2 x 2.40 = 4.80Croissant 1 x 3.10 = 3.10Mineral water 3 x 1.50 = 4.50-----------------------------------Total €12.40%-16sis text in 16 characters, aligned left.%5.2fis a number in 5 characters with 2 after the point, aligned right.%2dis a whole number in 2 characters."template".formatted(...)does the same asString.format("template", ...). You call it directly on the template.
More precisely, if you are curious
a.concat(b)is the same asa + b. You will see it in older code.- The width is at least that many characters. Longer text is not cut. It pushes the column to the right.
Double.parseDouble("2,40")throwsNumberFormatException: here the decimal mark is always a point. ButString.format("%.2f", 2.4)on a computer with German or Bulgarian settings, for example, gives2,40. For text that another program will read, useString.format(Locale.ROOT, ...).
Try it
Find the bug
"Kayak" reads the same backwards, but the program says "kayak is not a palindrome". Which line has the bug?
Pick the line with the bug.
reversed is a StringBuilder, and word is a String. The equals of String returns true only for another String with the same characters. For an object of another class it is false, whatever is inside. The fix is word.equals(reversed.toString()).Exercise
Text you put together
Solve the tasks in your editor. Do not print prompts.
1. Palindrome. Read a line and say whether it is a palindrome, ignoring capital letters, spaces, commas and dashes. This time reverse the text with StringBuilder.reverse() instead of a loop.
Never odd or evenPalindromeNever odd, never evenNot a palindrome2. A receipt with columns. The first line has the number of items. Each line after it has a name, a price and a quantity, separated with ;. Print a receipt with aligned columns and the total at the end.
3Bread;1.20;2Cheese;9.80;1Eggs;0.35;10Bread 2 x 1.20 = 2.40Cheese 1 x 9.80 = 9.80Eggs 10 x 0.35 = 3.50------------------------------------Total 15.703. Words in reverse. Read a sentence and print its words in reverse order. There may be more than one space between the words, and the result must have exactly one. Put it together with a StringBuilder.
Tomorrow at 9 there is a meetingmeeting a is there 9 at TomorrowMore tasks
- Initials. Read a full name ("Maria Ivanova Petrova") and print the initials with dots: "M. I. P." Put them together with a
StringBuilder. - Hidden email. From
elena.koleva@company.commakee***********@company.com: the first letter, then as many asterisks as there are other characters before the@. UseindexOfandrepeat. - A tour of String. Create one string with quotes (
"Practice text") and another withnew String("Practice text"). Compare them with==and withequals, takesubstring(0, 8), split the second one on the space and print the parts, turn"100"into a number and print it withString.format("Value: %d", ...), and finally append" and more"to aStringBuilderwith the first string. Predict every line before you run the program. - Sort ignoring case. Change the bubble sort from the
compareTocard to usecompareToIgnoreCase, and try it with "ivan", "Boris" and "ana".
Test