Question

I'm new to Java and I saw a Q&A section here with two examples where mutability is removed. Upon testing MutableString.java:

import java.lang.reflect.Field; 

public class MutableString {

    public static void main(String[] args) { 
        String s = "Immutable"; 
        String t = "Notreally"; 

        mutate(s, t);
        StdOut.println(t); 

        // strings are interned so this doesn't even print "Immutable" (!)
        StdOut.println("Immutable");
    } 

    // change the first min(|s|, |t|) characters of s to t
    public static void mutate(String s, String t) {
        try {
            Field val = String.class.getDeclaredField("value"); 
            Field off = String.class.getDeclaredField("offset"); 
            val.setAccessible(true); 
            off.setAccessible(true); 
            int offset   = off.getInt(s); 
            char[] value = (char[]) val.get(s); 
            for (int i = 0; i < Math.min(s.length(), t.length()); i++)
                value[offset + i] = t.charAt(i); 
        } 
        catch (Exception e) { e.printStackTrace(); }
    } 

} 

I received the following error:

java.lang.NoSuchFieldException: offset

Any input on the following would be greatly appreciated:

a) why do I get this exception
b) how do I check which fields exist in a class (Java strings specifically)

Was it helpful?

Solution

Disclaimer: these kinds of hacks are interesting lessons in learning and fun trivia. But they are definitely not something that you want to use in any production code. It will lead to pain.

By their very nature, such a hack always depends on implementation details of the classes that are hacked.

In your case you seem to be using a String implementation that doesn't have an offset field, but uses some other mechanism (or maybe just a different name!).

For example, I've just reviewed the Oracle Java 7 String class and it no longer has the offset field (which was used in Java 6 and earlier to share the char[] among substrings)!*

You can use Class.getDeclaredFields() to check which fields this implementation does define:

for (Field f : String.class.getDeclaredFields()) {
  System.out.println(f);
}

For a version of that hack that works with Java 7, you could do this:

public static void mutate(String s, String t) {
    try {
        Field val = String.class.getDeclaredField("value"); 
        val.setAccessible(true); 
        char[] value = (char[]) val.get(s); 
        for (int i = 0; i < Math.min(s.length(), t.length()); i++)
            value[i] = t.charAt(i); 
    } 
    catch (Exception e) { e.printStackTrace(); }
} 

Of course, this too will break if the internals of String change again.

* Here's an Email that talks about that change, it seems that the sharing of the char[] only lead to improved performance in a few, special cases.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top