Artima Weblogs
"hi there".equals("cheers !") == true
by Heinz Kabutz
May 21, 2003
Summary
Java Strings are strange animals. They are all kept in one pen, especially the constant strings. This can lead to bizarre behaviour when we intentionally modify the innards of the constant strings through reflection. Join us, as we take apart one of Java's most prolific beasts.
Whenever we used to ask our dad a question that he could not possibly have known the answer to (such as: what's the point of school, dad?) he would ask back: "How long is a piece of string?"
Were he to ask me that now, I would explain to him that String is immutable (supposedly) and that it contains its length, all you have to do is ask the String how long it is. This you can do by calling length().
OK, so the first thing we learn about Java is that String is immutable. It is like when we first learn about the stork that brings the babies? There are some things you are not supposed to know until you are older! Secrets so dangerous that merely knowing them would endanger the fibres of electrons pulsating through your Java Virtual Machine.
So, are Strings immutable?
Playing with your sanity - Strings
Have a look at the following code:
public
class
MindWarp
{
public
static
void
main(String[] args)
{
System.out.println(
"
Romeo, Romeo, wherefore art thou oh Romero?
"
);
}
private
static
final
String OH_ROMEO
=
"
Romeo, Romeo, wherefore art thou oh Romero?
"
;
private
static
final
Warper warper
=
new
Warper();
}
If we are told that the class Warper does not produce any visible output when you construct it, what is the output of this program? The most correct answer is, "you don't know, depends on what Warper does". Now THERE's a nice question for the Sun Certified Java Programmer Examination.
In my case, running "java MindWarp" produces the following output
C:> java MindWarp <ENTER>
Stop this romance nonsense, or I'll be sick
And here is the code for Warper:
import
java.lang.reflect.
*
;
public
class
Warper
{
private
static
Field stringValue;
static
{
//
String has a private char [] called "value"
//
if it does not, find the char [] and assign it to valuetry {
stringValue
=
String.
class
.getDeclaredField(
"
value
"
);
}
catch
(NoSuchFieldException ex)
{
//
safety net in case we are running on a VM with a
//
different name for the char array.
Field[] all
=
String.
class
.getDeclaredFields();
for
(
int
i
=
0
; stringValue
==
null
&&
i
<
all.length; i
++
)
{
if
(all[i].getType().equals(
char
[].
class
))
{
stringValue
=
all[i];
}
}
}
if
(stringValue
!=
null
)
{
stringValue.setAccessible(
true
);
//
make field public
}
}
public
Warper()
{
try
{
stringValue.set(
"
Romeo, Romeo, wherefore art thou oh Romero?
"
,
"
Stop this romance nonsense, or I'll be sick
"
.
toCharArray());
stringValue.set(
"
hi there
"
,
"
cheers !
"
.toCharArray());
}
catch
(IllegalAccessException ex)
{}
//
shhh
}
}
How is this possible? How can String manipulation in a completely different part of the program affect our class MindWarp?
To understand that, we have to look under the hood of Java. In the language specification it says in ?3.10.5:
"Each string literal is a reference (?4.3) to an instance (?4.3.1, ?12.5) of class String (?4.3.3). String objects have a constant value. String literals-or, more generally, strings that are the values of constant expressions (?15.28)-are "interned" so as to share unique instances, using the method String.intern."
The usefulness of this is quite obvious, we will use less memory if we have two Strings which are equivalent pointing at the same object. We can also manually intern Strings by calling the intern() method.
The language spec goes a bit further:
- Literal strings within the same class (?8) in the same package (?7) represent references to the same String object (?4.3.1).
- Literal strings within different classes in the same package represent references to the same String object.
- Literal strings within different classes in different packages likewise represent references to the same String object.
- Strings computed by constant expressions (?15.28) are computed at compile time and then treated as if they were literals.
- Strings computed at run time are newly created and therefore distinct.
- The result of explicitly interning a computed string is the same string as any pre-existing literal string with the same contents.
This means that if a class in another package "fiddles" with an interned String, it can cause havoc in your program. Is this a good thing? (You don't need to answer ;-)
Consider this example
public
class
StringEquals
{
public
static
void
main(String[] args)
{
System.out.println(
"
hi there
"
.equals(
"
cheers !
"
));
}
private
static
final
String greeting
=
"
hi there
"
;
private
static
final
Warper warper
=
new
Warper();
}
Running this against the Warper produces a result of true, which is really weird, and in my opinion, quite mind-bending. Hey, you can SEE the values there right in front of you and they are clearly NOT equal!
BTW, for simplicity, the Strings in my examples are exactly the same length, but you can change the length quite easily as well.
Last example concerns the HashCode of String, which is now cached for performance reasons mentioned in "Java Idiom and Performance Guide", ISBN 0130142603. (Just for the record, I was never and am still not convinced that caching the String hash code in a wrapper object is a good idea, but caching it in String itself is almost acceptable, considering String literals.)
public
class
CachingHashcode
{
public
static
void
main(String[] args)
{
java.util.Map map
=
new
java.util.HashMap();
map.put(
"
hi there
"
,
"
You found the value
"
);
new
Warper();
System.out.println(map.get(
"
hi there
"
));
System.out.println(map);
}
private
static
final
String greeting
=
"
hi there
"
;
}
The output under JDK 1.3 is:
You found the value
{cheers !=You found the value}
Under JDK 1.2 it is
null
{cheers !=You found the value}
This is because in the JDK 1.3 SUN is caching the hash code so if it once is calculated, it doesn't get recalculated, so if the value field changes, the hashcode stays the same.
Imagine trying to debug this program where SOMEWHERE, one of your hackers has done a "workaround" by modifying a String literal. The thought scares me.
The practical application of this blog? Let's face it, none.
This is my first blog ever, I would be keen to hear what you thought of it?
摘自:http://www.daima.com.cn/Info/55/Info14695/