ExerciseChallenge
Challenge
Break a String with reflection
20 minintermediate2–8 yrs
Edge cases. You have to reason, and two valid fixes differ.
What this teaches
- final on a reference field protects the reference, not the referent
- Immutability is an API property — private state that never leaks
- String caches its hash, which is only sound because the value cannot change
- A string with a stale hash is a map key that can never be found again
- Mutating a pooled literal corrupts it for every class in the JVM
Starter
Starter.java
import java.lang.reflect.*;
/**
* Challenge: prove that "String is immutable because the field is final" is
* wrong, and work out what the real reason is.
*
* Run with:
* javac Starter.java -d /tmp/out
* java --add-opens java.base/java.lang=ALL-UNNAMED -cp /tmp/out Starter
*
* The --add-opens flag is required: reflecting into java.lang has been blocked
* by default since JDK 16. That refusal is itself part of the answer.
*/
public class Starter {
public static void main(String[] args) throws Exception {
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
System.out.println("declared as: " + Modifier.toString(value.getModifiers())
+ " " + value.getType().getSimpleName());
String s = new String("hello".toCharArray());
System.out.println("before: " + s);
// TODO 1: mutate s in place through the array you just obtained, and
// print it. The field is final. Does that stop you?
// TODO 2: print s.hashCode() before and after your mutation. Explain
// the result. Then work out what it means for using s as a HashMap key.
// TODO 3: do the same to the literal "hello" instead of to s.
// Then, on a later line, print the plain literal "hello" and see what
// comes out. Why is this different from TODO 1?
// TODO 4: in one sentence, say what actually makes String immutable.
// "The field is final" is not the answer — you just disproved it.
}
}Run it locally:
cd exercises/java/strings/string-immutability-and-pool/02-challenge
javac Starter.java -d /tmp/out && java -cp /tmp/out StarterHints
Hint 1
String.value is private final byte[]. setAccessible(true) gets you the array itself, not a copy — and final says nothing about the contents.
Hint 2
hashCode() is computed once and stored in a field. Read it before you mutate, and again after.
Hint 3
new String(char[]) copies. A literal does not — so mutating a literal's array changes it everywhere that literal appears.
Done when
- You mutate a String in place and print the new value
- You show hashCode() returning a value that no longer matches the content
- You demonstrate that the same trick on a literal affects an unrelated line
- A comment explains what actually makes String immutable, in one sentence
← Back to Why is String immutable, and what is the string pool?