I was reading an article that said that Java strings are not completely immutable. However, in the article's sample code that modifies the string, it makes a call to string.toUpperCase().toCharArray(), which returns a new string. So what's the purpose of going through the process of changing the string if you call toUpperCase() anyway? Here is the code:
public static void toUpperCase(String orig)
{
try
{
Field stringValue = String.class.getDeclaredField("value");
stringValue.setAccessible(true);
stringValue.set(orig, orig.toUpperCase().toCharArray());
}
catch (Exception ex){}
}
Also, I noticed that string.toUpperCase() by itself doesn't work. It needs to be string.toUpperCase().toCharArray(). Is there a reason for this?
By default, String.toUpperCase() leaves the original string intact, whilst returning a new string object.
The function you defined above, edits the contents of the original string object in-place.
What he's doing:
He's acquring some character array that he knows is the right length (such as the uppercase version of the String) and putting it as the backing array of the String. (The backing array is called
value
inside the String class.)Why he's doing it:
To illustrate that you could put any char array there you wanted.
Why this is useful:
String is immutable, and this allows you to circumvent the immutability. Of course, this is not recommended to do - EVER. On the contrary, I would not be surprised if he was saying "Watch out, because people could potentially do this to YOUR code. Even if you think your stuff is safe, it might not be!"
The implications of this are wide reaching. Immutable variables are no longer immutable. Final variables are no longer final. Thread safe objects are no longer thread safe. Contracts you thought you could rely upon, you can no longer do so. All because some engineer somewhere had a problem he couldn't fix with normal means, so he delves into reflection to solve it. Don't be 'that guy'.
You'll also note that how the hashCode for that String would now be changed. So, if you've never calculated the hashCode for that String, it's still 0 so you're okay. On the other hand, if you have calculated it, when you go to put it in a HashMap or HashSet, it won't be retrieved.
Consider the following:
I would bet he is doing this as a warning against bad behavior rather than showing a 'cool' trick.
EDIT: I found the article you're referring to, and the article it is based on. The original article states: "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 ;-) " I think that makes it quite clear this is more of a protection guide than advice on how to code.
So if you walk away from this thread with only one piece of information, it is that reflection is dangerous, unreliable, and not to be trifled with!
You change a final string with reflection for testing. Sometimes that string contains the path to a default location used in the production environment but not suitable for testing. Yet, that variable is referenced by several objects/methods your trigger in your test, and hence during your tests you might want to set it to a particular value.
As others said, it's probably something you don't want to be doing (often/ever).
I think I cannot add to the explanations already provided, so perhaps I can add to the discussion by suggesting how this can be prevented.
You can prevent somebody tampering with your code in these and other unintended ways by means of using a security manager.
This will generate an exception in the toUpperCase method, provided that you are not granting all privileges to all code bases in the default policy files. (In your current code your exceptions are currently swallowed).
What is the purpose? I'm not sure, ask the one that wrote this stuff. You normally should not do something like this. There is a reason String is immutable.
Here how this method would look if the fields were public, i.e. without reflection:
As
value
is of typechar[]
, you can't assign aString
to this field - this is why you need thetoCharArray
call after.toUpperCase()
. You will get an exception if you try to do this (I supposeClassCastException
), but the try-catch block there eats it away. (This gets us another lesson: Never use such empty catch blocks.)Pay attention: This code might not do the correct thing, since the actual data of the original string might not start at the start of the
char[]
. Since you don't update theoffset
field, you will getIndexOutOfBoundsExceptions
when using such a modified String. Also, the String object caches its hashCode, thus this will be wrong, too.Here would be a correct way:
With reflection, it looks like this:
Don't try this at home!
You are subverting String's immutability. There is no good reason to do this.