Programs tend to contain a lot of String literals in their code. In Java, these constants are collected in something called the string table for efficiency. For instance, if you use the string "Name: "
in ten different places, the JVM (typically) has just one instance of that String and in all ten places where it's used, the references all point to that one instance. This saves memory.
This optimization is possible because String
is immutable. If it were possible to change a String, changing it one place would mean it changes in the other nine as well. That's why any operation that changes a String returns a new instance. That's why if you do this:
String s = "boink";
s.toUpperCase();
System.out.println(s);
it prints boink
, not BOINK
.
Now there's one more tricky bit: multiple instances of java.lang.String
may point to the same underlying char[]
for their character data, in other words, they may be different views on the same char[]
, by using just a slice of the array. Again, an optimization for efficiency. The substring()
method is one of the cases where this happens.
s1 = "Fred47";
//String s1: data=[ 'F', 'r', 'e', 'd', '4', '7'], offset=0, length=6
// ^........................^
s2 = s1.substring(2, 5);
//String s2: data=[ 'F', 'r', 'e', 'd', '4', '7'], offset=2, length=3
// ^.........^
// the two strings are sharing the same char[]!
In your SCJP question, all this boils down to:
- The string
"Fred"
is taken from the String table.
- The string
"47"
is taken from the String table.
- The string
"Fred47"
is created during the method call. //1
- The string
"ed4"
is created during the method call, sharing the same backing array as "Fred47"
//2
- The string
"ED4"
is created during the method call. //3
- The
s.toString()
doesn't create a new one, it just returns this
.
One interesting edge case of all this: consider what happens if you have a really long String, for example, a web page taken from the Internet, let's say the length of the char[]
is two megabytes. If you take the substring(0, 4)
of this, you get a new String that looks like it's just four characters long, but it still shares those two megabytes of backing data. This isn't all that common in the real world, but it can be a huge waste of memory! In the (rare) case that you run into this as a problem, you can use new String(hugeString.substring(0, 4))
to create a String with new, small backing array.
Finally, it's possible to force a String into the string table at runtime by calling intern()
on it. The basic rule in this case: don't do it. The extended rule: don't do it unless you've used a memory profiler to ascertain that it's a useful optimization.
s
is not a compile-time constant expression so that doesn't happen. (Compile the code and usejavap -c
, or evenstrings
.) – Largehearted