What is the purpose of modifying a string using re

2019-02-18 21:35发布

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?

7条回答
爷、活的狠高调
2楼-- · 2019-02-18 22:12

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.

查看更多
甜甜的少女心
3楼-- · 2019-02-18 22:23

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:

import java.util.*;
import java.lang.reflect.*;

class HashTest {        

    /** Results:
     C:\Documents and Settings\glowcoder\My Documents>java HashTest
        Orig hash: -804322678
        New value: STACKOVERFLOW
        Contains orig: true
        Contains copy: false
     */

    public static void main(String[] args) throws Exception {

        Set<String> set = new HashSet<String>();
        String str = "StackOverflow";
        System.out.println("Orig hash: " + str.hashCode());
        set.add(str);

        Field stringValue = String.class.getDeclaredField("value");
        stringValue.setAccessible(true);
        stringValue.set(str, str.toUpperCase().toCharArray()); // 

        System.out.println("New value: " + str);

        String copy = new String(str); // force a copy
        System.out.println("Contains orig: " + set.contains(str));
        System.out.println("Contains copy: " + set.contains(copy));
    }

}

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!

查看更多
Emotional °昔
4楼-- · 2019-02-18 22:31

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).

查看更多
Animai°情兽
5楼-- · 2019-02-18 22:32

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.

public static void main(String args[]){

    System.setSecurityManager(new SecurityManager());
    String jedi1 = "jedi";

    toUpperCase(jedi1);
    System.out.println(jedi1);
} 

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).

查看更多
虎瘦雄心在
6楼-- · 2019-02-18 22:34

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:

public static void toUpperCase(String orig) {
    orig.value = orig.toUpperCase().toCharArray();
}

As value is of type char[], you can't assign a String to this field - this is why you need the toCharArray call after .toUpperCase(). You will get an exception if you try to do this (I suppose ClassCastException), 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 the offset field, you will get IndexOutOfBoundsExceptions 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:

public static void toUpperCase(String orig) {
    orig.value = orig.toUpperCase().toCharArray();
    orig.offset = 0;
    orig.hash = 0; // will be recalculated on next `.hashCode()` call.
}

With reflection, it looks like this:

public static void toUpperCase(String orig)
{
  try
  {
    Field stringValue = String.class.getDeclaredField("value");
    stringValue.setAccessible(true);
    stringValue.set(orig, orig.toUpperCase().toCharArray());
    Field stringOffset = String.class.getDeclaredField("offset");
    stringOffset.setAccessible(true);
    stringOffset.setInt(orig, 0);
    Field stringHash = String.class.getDeclaredField("hash");
    stringHash.setAccessible(true);
    stringHash.setInt(orig, 0);
  }
  catch (Exception ex){
     // at least print the output
     ex.printStackTrace();
  }
}
查看更多
7楼-- · 2019-02-18 22:35

Don't try this at home!

You are subverting String's immutability. There is no good reason to do this.

查看更多
登录 后发表回答