First Steps
Strings, the Basics
A String is an immutable object, not a primitive — so every "change" makes a new String, methods that ignore their result do nothing, and comparing text needs .equals (characters) not == (identity), a distinction the String pool makes deceptively easy to get wrong. Creating strings, everyday methods, concatenation, and a first look at == vs equals.
Suggest an editStrings, the Basics — Text as an Immutable Object
A String is a piece of text — characters in double quotes, like "Ada". But unlike the primitives of the last chapter, a String is an object, and two consequences of that flow through everything you do with text. First, a String is immutable: it can never be changed, so every method that seems to edit it actually returns a new String. Second, because a String is an object, comparing two strings means choosing between two questions — "the same object?" (==) or "the same characters?" (.equals) — and choosing wrong is one of the most common beginner bugs in Java.
💡 The core idea.
- A
Stringis an object, not a primitive. - It is immutable, so every method that seems to edit it returns a new String.
- Compare text with
.equals(same characters), not==(same object).
This is a gentle first pass. The reference model under "object" gets its rigorous treatment in Tier 2, and Strings return in depth in Tier 3. For now, two ideas carry the chapter: a String is immutable, and you compare its contents with .equals. Every output below was produced by compiling and running the code.
📘 How to read the Intuition boxes. Each one is built in three moves:
- The mechanism — what the compiler and the JVM are actually doing.
- A concrete bite — a specific, runnable failure (often a real compiler error), shown so the trap is visible.
- The earned rule — the decision heuristic, now justified rather than asserted, plus its cost.
Table of contents
- Strings are objects made of characters
- Immutability: methods return new strings
- Everyday string methods
- Concatenation with
+ ==vs.equalsand the String pool- Mental-model summary
- Gotcha checklist
1. Strings are objects made of characters
You write a String with double quotes. Because it is an object, it carries methods — actions you call on it with a dot, like name.length().
Output:
Ada
3
AAnalysis. "Ada" is a String — a sequence of characters, and an object. Being an object, it answers method calls: name.length() asks how many characters it has (3), and name.charAt(0) asks for the character at position 0 — the first, because counting starts at zero — which is 'A'. Note the two kinds of quote from the last chapter: charAt returns a char ('A', single quotes, one character), not a one-character String ("A", double quotes).
Intuition.
Mechanism. A String variable is a reference type: name holds a reference to a String object stored elsewhere, rather than holding the characters directly the way an int holds its number. (The full reference model is Tier 2; here it is enough that a String is an object you send method calls to.)
Concrete bite. Positions run from 0 to length − 1, so reaching past the end is a run-time error:
Output (a thrown exception; a stack trace follows the first line):
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: Index 3 out of bounds for length 3"Ada" has length 3, so the valid positions are 0, 1, 2; charAt(3) is one past the end and throws.
💡 Earned rule. Index strings from 0 to length − 1; charAt(length) is always off the end. The cost of a String being an object is that an out-of-range access fails at run time, with an exception, not at compile time — the compiler cannot know the index in advance. When the position comes from a calculation, check it against length() first.
2. Immutability: methods return new strings
A String never changes. Methods that look like they edit it — toUpperCase, replace, strip — actually return a new String and leave the original untouched. That property is called immutability.
Output:
HELLO
helloAnalysis. toUpperCase() produced a new String, "HELLO", which we stored in shout. The original greeting is still "hello". The method did not edit the string in place; it built a new one and returned it.
Intuition. Mechanism. Every String operation that "transforms" text returns a brand-new String object; the original's characters are fixed for its entire life. No method mutates a String in place, because none can.
Concrete bite. So calling such a method and ignoring its result does nothing at all:
Output:
hellotoUpperCase() ran and produced "HELLO", but nothing caught the result, so it was discarded; greeting was never going to change. To keep the new value you must assign it: greeting = greeting.toUpperCase();.
💡 Earned rule. Treat every String method as returning a new value you must capture — s = s.strip();, not s.strip();. The cost of immutability is exactly this gotcha (a method call that appears to do nothing) plus extra objects when you transform text repeatedly. The benefit is safety: a String you hand to other code can never be altered behind your back, which is what makes Strings dependable as constants and as map keys — a payoff you'll collect in Tier 3.
3. Everyday string methods
Strings come with a deep toolkit. A working handful covers most days: length, charAt, substring, indexOf, contains, replace, toUpperCase/toLowerCase, and strip (remove surrounding whitespace; trim is its older sibling).
Output:
Hello, World
12
7
World
Hello, Java
trueAnalysis. strip() removed the surrounding spaces, giving "Hello, World" (length 12). indexOf("World") reported the position where "World" begins — 7. substring(7) returned everything from position 7 onward, "World". replace produced "Hello, Java", and contains("ello") answered true. Notice methods chain: s.strip().length() runs strip first (a new String), then length on that result.
Intuition.
Mechanism. Each method reads the String and returns a result — a new String, an int index, or a boolean — without altering the original (immutability again). indexOf returns the position, or -1 when the text is not found.
Concrete bite. That -1 is a value, not an error, and it bites if you assume the search succeeded:
Output:
-1"z" is not in "Hello", so indexOf returns -1 — a sentinel, not an exception. Hand that -1 to substring or charAt and that call throws; the search itself quietly gave you a value you were supposed to check.
💡 Earned rule. Check indexOf's result against -1 before using it as a position. The cost of a method that returns a sentinel rather than throwing is a delayed failure: you learn nothing is wrong at the indexOf call, only later when -1 reaches code that cannot handle it. (Tier 5's Optional is the modern, typed alternative to sentinel return values.)
4. Concatenation with +
+ between two strings joins them; + between a string and a number turns the number into text and joins. It is the quickest way to build output — and it hides a precedence surprise.
Output:
name: Ada, age: 36
sum: 12
3 = sumAnalysis. The first line joins text with name and age (the int 36 becomes "36"). The next two look symmetric but differ, because + is evaluated left to right and, once one side is a String, + means "join." In "sum: " + 1 + 2, the first + joins "sum: " and 1 into "sum: 1", then + 2 joins to "sum: 12" — the numbers were never added. In 1 + 2 + " = sum", the first + is int + int = 3 (no String yet), and only then does 3 + " = sum" join into "3 = sum".
Intuition.
Mechanism. + is left-associative; each + looks only at its immediate left and right. If either side is a String, it concatenates (converting the other side to text); only when both sides are numbers does it add.
Concrete bite. The "sum: " + 1 + 2 → "sum: 12" above is the bite — a "sum" that never added. Parenthesise the arithmetic to fix it:
Output:
sum: 3The parentheses force 1 + 2 = 3 first, then the concatenation.
💡 Earned rule. When you mix text and arithmetic with +, wrap the arithmetic in parentheses — "sum: " + (1 + 2). The cost of +'s double meaning is this ordering trap; and there is a second cost — building long text with + inside a loop creates a new String on every step, which is quadratic work. Tier 3's StringBuilder is the linear-time fix, and the spot where this chapter's "every change makes a new String" stops being a curiosity and starts mattering for speed.
5. == vs .equals and the String pool
To compare strings you have two tools that look interchangeable but ask different questions. == asks "are these the same object?" .equals asks "do these have the same characters?" For text you nearly always mean the second.
Output:
true
true
true
falseAnalysis. .equals is true for both comparisons — a, b, and c all spell "hello". But == disagrees: a == b is true while a == c is false. The cause is the String pool: identical string literals like the "hello" written for a and b are stored once and shared, so a and b are the same object and == happens to be true. new String("hello") deliberately builds a separate object, so a == c is false even though the characters match.
Intuition.
Mechanism. A String variable holds a reference. == compares the two references — same object? — while .equals compares the characters. The pool reuses literal strings, which makes == coincidentally true for literals: the most dangerous kind of "it works."
Concrete bite. The coincidence collapses the moment a string comes from anywhere but a literal — a new String, user input, a computation:
Output:
false
trueThe text is "yes" both ways, but == compared object identities — two different objects — and was false, while .equals compared characters and was true. A program that checks user input with == will reject correct answers seemingly at random, because typed-in or computed strings are not pooled.
💡 Earned rule. Compare String contents with .equals (or .equalsIgnoreCase); reserve == for primitives and deliberate identity checks. The cost of the pool is that == looks right in quick tests with literals and then fails on real, non-literal strings — so never let a passing literal test convince you == is correct for text. This is one face of a deeper rule about objects: references and equality get their full pass in Tier 2, and the equals/hashCode contract behind hash-based collections in Tier 3.
6. Mental-model summary
| Principle | Consequence |
|---|---|
| A String is an object (a reference type), not a primitive | It has methods (.length(), .charAt(i)); out-of-range access throws at run time |
| Strings are immutable | Every "edit" returns a new String; s.strip(); alone changes nothing — assign it |
| String methods return values without mutating | indexOf returns a position or -1; check for -1 before using it |
+ is left-associative and means "join" once a String is involved |
"sum: " + 1 + 2 is "sum: 12"; parenthesise arithmetic: "sum: " + (1 + 2) |
== compares identity; .equals compares characters |
== is true for shared literals, false for new String/input — use .equals |
7. Gotcha checklist
- A string "edit" didn't take → the method returned a new String you ignored; assign it back (
s = s.toUpperCase();). StringIndexOutOfBoundsException→ an index reachedlengthor beyond; valid positions are0 … length − 1; check againstlength().- A
+"sum" concatenated instead of adding → left-to-right+joined once a String appeared; wrap the arithmetic:... + (a + b). - String comparison with
==works in tests, fails in production → you compared identity; literals are pooled but real input is not — use.equals. substring/charAtthrows on a search result →indexOfreturned-1(not found) and you used it as a position; test for-1first.
🧪 Predict, then check. Predict the four lines of output before running this: String x = "java"; String y = "ja" + "va"; String z = new String("java"); then print x == y, x.equals(y), x == z, and x.equals(z). (Hint: "ja" + "va" is two literals joined by the compiler before the program runs — does that land it in the pool with x, or make a separate object like z?) Decide each true/false, then run it and reconcile any surprise with the rule that == compares identity while .equals compares characters.
Your Turn
Before you move on, check your understanding with the coach — explain the idea, apply it, weigh the trade-offs, then defend your reasoning.