Chinaunix首页 | 论坛 | 博客
  • 博客访问: 117455
  • 博文数量: 18
  • 博客积分: 2015
  • 博客等级: 大尉
  • 技术积分: 245
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-07 12:38
文章分类

全部博文(18)

文章存档

2010年(3)

2009年(1)

2008年(14)

我的朋友

分类: Java

2008-05-07 14:34:24

The behaviour of Java’s ‘string literals’ is a commonly misunderstood feature of the platform, but with a little knowledge of this system and some liberal hacking using the reflection API it is possible to cause ‘System.out.println(“Hello World”)’ to actually display a completely different piece of text… doubters read on…

Before we can perform this impossible feat it is necessary to understand a little about how literal strings are dealt with in Java. Strings in java are objects, but they are a very unique kind of object in that they can be created using a ‘string literal’. An example of this would be…
String osiName = “OSI”
Here the string literal “OSI” is used to create an instance of the java.lang.String class and assign it the name osiName. It is important to understand that this is what is playfully known as ‘syntax sugar’, meaning it doesn’t really add any functionality to the language it is just ease of use for the programmer, an equivalent string object could be created like this…
char[] osiNameChars = {‘O’, ‘S’, ‘I’};
String osiName = new String(osiNameChars)
Here the equivalent string is created using no string literals at all, simple an array of primitive chars (the array was also created using some ‘syntax sugar’). The usage of string literals is purely a convenience.The Java virtual machine handles these string ‘literals’ in a way that can make their behaviour a little strange. When given the code…

String string1 = “OSI”;
String string2 = “OSI”;
…it is widely assumed that two string objects would be created. This would be incorrect. The JVM recognises that the two string literals are identical and only creates a single instance. This is sensible on the part of the JVM for performance and memory reasons. One of the earliest things a Java programmer will learn is that == is fine for testing equality of ints or booleans, but for objects the equals method is used. The reason for this is that the Java platform cannot assume how the programmer wishes to confirm two objects are equal. Two Flight objects maybe considered equal if there flight numbers are identical and ignore other data. The programmer provides the information regarding equality of objects by implementing the equals method. However, the == between object references does perform a useful task. It returns true if the two references are pointing to the exact same object instance. This becomes clear with a code example…

Object object1 = new Object();
Object object2 = new Object();
Object object3 = object2;
System.out.println(object1 == object2);
System.out.println(object2 == object3);
When the above code is run, the output is false, true. In this code only 2 objects are created, object1 and object2. object3 is another reference that points to the same object as object2. object1 and object2 point to different objects so the first output is false. object2 and object3 point to the same object so the output is we true.With that in mind lets take a look at this code…

char[] osiNameChars = {‘O’, ‘S’, ‘I’};
String string1 = new String(osiNameChars);
String string2 = “OSI”;
String string3 = “OSI”;
System.out.println(string1 == string2);
System.out.println(string2 == string3);
In the above code we didn’t point string3 back to string2 as we did in the previous example. Instead we create another string literal. So it may seem strange that we get the exact same output. However, given what we know about string literals the reason should be obvious. Only 1 string object is created for each identical string literal. string2 and string3 point to the very same object.There is a potential problem with this, and some more code to consider…

String string1 = “OSI”;
String string2 = “OSI”;
string1.setValue(“SOMETHING
ELSE”);
System.out.println(string1);
System.out.println(string2);
Here we have created string1 and string2 that point to the same String object with the value “OSI”. Then we changed the value of the string object referenced by string1 to “SOMETHING ELSE”. The string object referenced by string1 is the same and the one referenced by string1. So the output is “SOMETHING ELSE” twice. Perhaps the more observant among you have noticed that the above code wouldn’t compile (I hope no one has been trying to compile it before reading this far >=o)). This is because there is no setValue method on string. The String object is immutable, meaning that once it is created it cannot be changed. This is supposed to prevent the strange behaviour in the ‘theorectical’ example above.Using some obscure methods in the java reflections API we can bypass the immutability of strings. Writing ‘reflective’ code is a dubious concept, where the programmer can reference and work with parts of the actual class… There are classes within the reflections API such as Method, Constructor and Field. Also (and you might have to concentrate here ;o)) there is a class called Class in java.lang. So if the String wont let us change its value with setValue we can force it with reflection… here is the code a piece at a time…

Class stringClass = String.class;
This code gives us a reference to the string Class. The reference is named stringClass.

Field valueField = stringClass.getDeclaredField(“value”);
Using the above code we can obtain a reference to a field called value in the String class, ‘value’ is the name of the field in String that contains the actual data of the text.

valueField.setAccessible(true);
The line above is the killer! This line bypasses the access modifiers of the value field in String. String’s value field is declared as private, so cannot normally be accessed outside of the class itself, but the above method bypasses this, we can now write to and read from this private field.

String sneakString = “SURPRISE!!!”;
Object sneakedValue = valueField.get(sneakString);
The first line of code in this pair creates a string that will be the string that is displayed when “HELLO WORLD” is printed. The second line is a little bit more complicated. Here we want to obtain the data stored in the ‘value’ field of our sneakString. It feels a little backwards because we are taking a Field and passing it an object to get the value of that field in that object. Every string object will have its own ‘value’ field, we want to obtain what is stored in this field on our sneakString object.

valueField.set(“Hello World”, sneakedValue”);
This is the final piece of magic. Here we take the string literal “Hello World” and set its value to the value of our sneak string. Because of the behaviour of the string literals, this will also set this value to all other metions of “Hello World”.We now have the ability to make System.out.println(“Hello World”); actually print “SURPRISE!!!”, by changing the value of the “Hello World” literal… and the task is accomplished!I will finish up here with full code that can be run to see this behaviour for yourself…

public class Demo
{
    public static void main(String[] args) throws Exception
    {
       //Call the method that will warp our Hello World String
       warpString();
       //Print the warped string
       System.out.println("Hello World");
    }

    private static void warpString() throws Exception
    {
       //Obtain our reference to the String class
       Class stringClass = String.class;
        //and now the reference to the value field
                    java.lang.reflect.Field valueField = stringClass.getDeclaredField("value");
        //Bypass the private modifier
                    valueField.setAccessible(true);
                   //get our sneaked value from the surprise string
       
             Object sneakedValue = valueField.get("Surprise!!!");
                    //swap the values over
                    valueField.set("Hello World", sneakedValue);
    }
}

阅读(794) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~