Topic 1.10
Strings: The Basics
In one line
A String is an object that holds a fixed sequence of characters. You create one with double quotes, join Strings with +, read characters by position starting at 0, compare contents with equals (never ==), and every method that seems to change a String actually returns a new one.
Think of it like this
A necklace of letter beads. Each bead has a position number, starting at 0 for the first bead. You can count the beads, look at bead number 3, or copy a part of the necklace to make a shorter one. But this necklace is glued shut: you can never swap a bead. To get a different word you make a new necklace. A Java String is that glued necklace of characters.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- String
- A piece of text: an object holding characters in order, such as
"hello". - String literal
- Text written directly in your code between double quotes, like
"Asha". - Index
- The position number of a character in a String. The first character is at index 0.
- Concatenation
- Joining pieces of text end to end. In Java the
+operator does it when one side is a String. - Immutable
- Can't be changed after it is created. Every String method that 'changes' text returns a new String instead.
- Reference
- The arrow (address) stored in a variable that points to an object living on the heap.
- String pool
- A shared store where the JVM keeps one copy of each String literal, so equal literals reuse the same object.
- Method
- A named action an object can do, called with a dot and brackets:
name.length(). - null
- A special value meaning 'this reference points at no object at all'.
Step by step
01Make a String and ask it questions
Write text between double quotes and store it in a String variable. Because a String is an object, you ask it questions by calling methods with a dot: city.length() asks 'how many characters do you have?'.
Methods always need brackets, even with nothing inside. city.length without brackets is a compile error for Strings (arrays use .length with no brackets, Topic 3.8; mixing these up is a classic slip).
String city = "Pune";
int n = city.length(); // 4
char first = city.charAt(0); // 'P'
String loud = city.toUpperCase(); // "PUNE", city is still "Pune"02Positions start at 0
Index 0 is the first character, so the last character is at length() - 1. In "Pune" the characters are P(0) u(1) n(2) e(3); there is no index 4.
substring(begin, end) takes a half-open range: it includes begin and stops just before end. The nice side effect is that end - begin is exactly the length of the result, and s.substring(0, k) + s.substring(k) always rebuilds s. With one argument, substring(begin) runs to the end.
03Where a String lives in memory
The variable city sits in the stack frame like any local (Topic 1.1), but it holds only a reference. The String object lives on the heap. Since Java 9 it stores its characters in a byte[]: one byte per character when every character fits in Latin-1 (this is called compact strings), two bytes per character otherwise.
Literals are special. When a class loads, each distinct literal is placed once in the string pool. Two variables initialised with the same literal "java" therefore point at one shared object. That sharing is safe only because Strings are immutable: nobody can change the shared object under somebody else's feet.
04Immutability: methods return a new String
Call greeting.toUpperCase() and Java creates a brand-new String "HI". The old object "hi" is untouched. If you don't store the new reference somewhere, it is simply garbage-collected later.
So the rule is: assign the result. greeting = greeting.toUpperCase(); points the variable at the new object. The variable changed; the String objects never did.
String greeting = "hi";
greeting.toUpperCase(); // result discarded, greeting is still "hi"
greeting = greeting.toUpperCase(); // greeting now refers to a new String "HI"05+ reads left to right
Java evaluates + from left to right, one pair at a time. 1 + 2 + "3" is (1 + 2) + "3", which is 3 + "3", which is "33". "1" + 2 + 3 is ("1" + 2) + 3, which is "12" + 3, which is "123".
Watch out for char: 'a' + 'b' adds the character codes (97 + 98 = 195) because neither side is a String. Start with "" or use String.valueOf to force text: "" + 'a' + 'b' is "ab".
When every piece is a constant (literals or final variables holding literals), the compiler joins them at compile time: "ja" + "va" is stored in the class file as the single literal "java". Otherwise the joining happens at run time and produces a new object.
06Comparing: equals, not ==
== on references asks 'same object?'. equals asks 'same characters?'. For text you almost always mean the second.
The trap is that == sometimes gives the right answer by luck: two identical literals share the pooled object, so "java" == "java" is true. But a String made at run time (new String(...), joining a non-constant, reading input, substring) is a different object, and == returns false even though the text is identical.
If one side might be null, put the literal first: "yes".equals(answer) returns false for a null answer instead of throwing.
07Empty, blank and null
"" is a String with zero characters. " " has three space characters. null is not a String at all. isEmpty() is true only for ""; isBlank() (Java 11) is true for both "" and whitespace-only text.
Calling a method on null throws NullPointerException at run time. Joining null with + does not throw; it produces the four letters null, which then quietly ends up in your output. Phase 4.12 covers null in depth.
Try it yourself
- 1
Slice your own name
In the first example, change
cityto your own name. Before running, write down whatlength(),charAt(0)andsubstring(0, 2)will print. Then run and check.Now print
city.substring(1, city.length() - 1): it should be your name without its first and last letters. - 2
Predict the + puzzles
Add these lines to the second example and predict each result before running:
System.out.println(10 + 20 + "x" + 10 + 20);andSystem.out.println('x' + 1);.You should get
30x1020(maths until the String appears, joining after) and121(the code ofxis 120, plus 1). - 3
Catch == lying
In the third example, add
String e = "ja" + "va";and printa == e. It printstrue, unlikea == d. Both sides of this+are constants, so the compiler joined them into the literal"java", which comes from the pool. Now makehalffinaland checka == dagain: it also becomestrue. This is exactly why==is unreliable for text.
Code & diagrams
Expected output
text: Bengaluru
length: 9
first: B
last: u
indexOf u: 6
indexOf z: -1
0 to 4: Beng
from 4: aluru
upper: BENGALURU
unchanged: BengaluruExpected output
33
123
15
195!
ab
score: 9.5, passed: true
value: null`compareTo` returns the difference of the first differing characters: 'a' (97) minus 'b' (98) is -1. Only its sign is guaranteed to mean anything.
Expected output
a == b true
a == c false
a == d false
a.equals(c) true
a.equals(d) true
ignore case true
compareTo -1`strip`, `isBlank` and `repeat` arrived in Java 11. Older code uses `trim()`, which removes only characters with codes up to 32 (space and control characters).
Expected output
ignored: hi
assigned: HI
[ Hello World ]
[Hello World]
contains World: true
replace: HeLLo WorLd
repeat: ababab
"" isEmpty: true
" " isEmpty: false
" " isBlank: trueString answer = null;
// answer.equals("yes") // NullPointerException: answer points at nothing
"yes".equals(answer); // false, no exception: the literal is never null
java.util.Objects.equals(answer, "yes"); // false: handles null on either sideBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use single quotes for text
Write String name = 'Asha';.
public class Main {
public static void main(String[] args) {
String name = 'Asha';
System.out.println(name);
}
}Break #2
Read one past the end
Print word.charAt(3) when word is "cat".
public class Main {
public static void main(String[] args) {
String word = "cat";
System.out.println(word.charAt(3));
}
}Break #3
Compare text with ==
Build a String at run time and compare it to a literal with ==.
public class Main {
public static void main(String[] args) {
String typed = "ye";
typed = typed + "s"; // made at run time, like real input
if (typed == "yes") {
System.out.println("confirmed");
} else {
System.out.println("not confirmed?!");
}
}
}Myth vs fact
Myth
String is a primitive type like int.
Fact
It is a class in java.lang. Its variables hold references, it can be null, and it has methods. Java just gives it literal syntax and the + operator.
Myth
s.toUpperCase() changes s.
Fact
Strings are immutable. The method returns a new String; you must assign it (s = s.toUpperCase();) to keep it.
Myth
== works for Strings because it worked in my test.
Fact
It worked because both sides were pooled literals. Strings built at run time are different objects, and == then returns false for identical text. Always use equals.
Myth
length() counts the letters a person sees.
Fact
It counts UTF-16 code units. Most characters are one unit, but an emoji such as a smiling face is two, so length() returns 2 for it (Topic 6.8).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Compact strings (JEP 254, Java 9): a String holds a
byte[] valueplus acoderbyte (LATIN1 or UTF16). Latin-1-only text uses one byte per character, roughly halving memory for typical English text;charAtchecks the coder and reads one or two bytes. - ▸
Since Java 9 (JEP 280),
javaccompiles non-constant concatenation into oneinvokedynamiccall bootstrapped byStringConcatFactory, which sizes the result once. Before that, it generated aStringBuilderchain. Concatenation inside a loop still creates a new String on every pass; useStringBuilderthere (Topic 6.3). - ▸
Compile-time constant expressions (JLS 15.29) are folded by
javac, and constant Strings are always interned. That is why"ja" + "va" == "java"istruewhile the same join with a non-finalvariable isfalse.s.intern()returns the pooled copy at run time. - ▸
hashCode()is computed once and cached in a field (the String is immutable, so it can't go stale). That, plus immutability, makes String the most commonHashMapkey; Topic 9.4 shows why mutable keys break maps.
Remember this
- 1
Stringis not one of the eight primitive types (Topic 1.2). It is a class fromjava.lang, so aStringvariable holds a reference to an object on the heap. Java gives it special treatment: you can create one with a literal in double quotes ("Asha"), and the+operator joins Strings. Single quotes are only for onechar(Topic 1.5). - 2
Characters are numbered by index from
0tolength() - 1.s.length()counts them,s.charAt(i)returns thecharat indexi,s.indexOf("x")finds the first position of some text (or-1if it isn't there), ands.substring(begin, end)copies the characters frombeginup to but not includingend. Asking for an index outside the range throwsStringIndexOutOfBoundsException. - 3
Strings are immutable: once created, their characters never change.
toUpperCase(),trim(),replace(...)and friends build and return a new String; the original is untouched. Writingname.toUpperCase();on its own line does nothing useful, because the result is thrown away. Writename = name.toUpperCase();to keep it. - 4
+works left to right. As long as both sides are numbers it adds; as soon as one side is a String it concatenates (joins), turning the other side into text. So1 + 2 + "3"is"33"but"1" + 2 + 3is"123". Use brackets to force the maths first:"1" + (2 + 3)is"15". Any value can be joined: numbers,char,boolean, and evennull, which becomes the textnull. - 5
To compare text use
a.equals(b)(same characters, same case) ora.equalsIgnoreCase(b). The==operator compares references: it asks whether both variables point at the very same object. Identical literals happen to share one object from the string pool, so==sometimes looks like it works, then fails for Strings built at run time.a.compareTo(b)orders Strings alphabetically by character code: negative ifacomes first,0if equal, positive if after. - 6
An empty String
""has length 0 and is a real object;nullmeans there is no String at all, and calling any method on it throwsNullPointerException.isEmpty()tests for length 0;isBlank()(Java 11) is also true for whitespace-only text like" ". Phase 6 goes much deeper: the pool and immutability (6.1),==vsequals(6.2),StringBuilder(6.3) and every everyday method (6.4).
Explain it without notes
Why is String immutable, and what does that let the JVM do?
Explain the difference between == and equals for Strings, and why == sometimes returns true for equal text.
What do 1 + 2 + "3" and "1" + 2 + 3 evaluate to, and why?
What is the difference between "", " " and null, and how do you test for each?
Practice
Given String word = "programming";, print its length, its first and last characters, and the middle part without the first and last characters.
Given String email = "asha.k@example.com";, use indexOf and substring to print the user part (before @) and the domain (after @).
Given String a = "Level";, print whether it reads the same forwards and backwards ignoring case, by comparing its lower-case form with the reverse built from charAt in a simple sequence (the word has 5 letters).
Trade-offs
- ↔
Immutability makes Strings safe to share, cache and use as map keys, but every change allocates a new object; for text built piece by piece in a loop,
StringBuilderis the better tool. - ↔
+concatenation is the most readable way to build a short message; for aligned columns and number formats,printf/formatted(Topic 1.12) reads better than long+chains. - ↔
"literal".equals(x)is null-safe but reads backwards to some people;Objects.equals(a, b)is null-safe on both sides at the cost of a static call.
Done when you can
I can create Strings and use
length,charAt,indexOfandsubstringwith correct 0-based indexes.I can predict the result of mixed
+expressions with numbers, chars and Strings.I always compare text with
equalsand can explain why==sometimes seems to work.I remember to assign the result of
toUpperCase,stripandreplace.I can tell empty, blank and
nullapart and test each one safely.