使用反射修改字符串的目的是什么?

发布于 2024-11-27 12:39:16 字数 619 浏览 1 评论 0原文

我正在阅读一篇 文章 说 Java 字符串并不完全不可变的。但是,在本文修改字符串的示例代码中,它调用了 string.toUpperCase().toCharArray(),这会返回一个新字符串。那么,如果您无论如何调用 toUpperCase() ,那么经历更改字符串的过程的目的是什么?这是代码:

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){}
}

另外,我注意到 string.toUpperCase() 本身不起作用。它需要是 string.toUpperCase().toCharArray()。这是有原因的吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

水溶 2024-12-04 12:39:16

他正在做什么:

他正在获取一些他知道长度正确的字符数组(例如字符串的大写版本)并将其作为字符串的支持数组。 (支持数组在 String 类中称为 value。)

为什么这样做:

为了说明您可以在其中放置任何您想要的 char 数组。

为什么这有用:

字符串是不可变的,这允许您规避不可变性。当然,永远不建议这样做。相反,如果他说“小心,因为人们可能会对您的代码执行此操作。即使您认为您的东西是安全的,但它可能并不安全!”我不会感到惊讶。

其影响是广泛的。不可变变量不再是不可变的。最终变量不再是最终变量。线程安全对象不再是线程安全的。您认为可以信赖的合同,现在您不能再信赖了。这一切都是因为某个地方的某个工程师遇到了一个他无法用正常方法解决的问题,所以他深入思考来解决它。 不要成为“那个人”。

您还会注意到该字符串的 hashCode 现在将如何更改。因此,如果您从未计算过该字符串的 hashCode,它仍然是 0,所以没问题。另一方面,如果你计算过它,当你把它放入HashMap或HashSet时,它就不会被检索到。

考虑以下几点:

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));
    }
        
}

我敢打赌他这样做是为了警告不良行为,而不是展示“很酷”的把戏。

编辑:我找到了您所指的文章及其所基于的文章。原始文章指出:“这意味着,如果另一个包中的类“摆弄”了一个实习字符串,它可能会对您的程序造成严重破坏。这是一件好事吗?(您不需要回答;-)”我我认为这很清楚地表明,这更多的是保护指南,而不是如何编码的建议。

因此,如果您离开此线程时只留下一条信息,那就是反射是危险的、不可靠的、不容忽视的!

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!

厌味 2024-12-04 12:39:16

不要在家里尝试这个!

你正在破坏 String 的不变性。 没有充分的理由这样做。

Don't try this at home!

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

葬心 2024-12-04 12:39:16

我想我无法对已经提供的解释进行补充,所以也许我可以通过建议如何防止这种情况来补充讨论。

您可以通过使用安全管理器来防止有人以这些和其他意外方式篡改您的代码。

public static void main(String args[]){

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

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

如果您没有向默认策略文件中的所有代码库授予所有权限,这将在 toUpperCase 方法中生成异常。 (在您当前的代码中,您的异常目前已被吞掉)。

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

蓝色星空 2024-12-04 12:39:16

目的是什么?我不太确定,请问写这个东西的人。你通常不应该做这样的事情。 String 不可变是有原因的。

如果字段是公共的,即没有反射,则该方法的外观如下:

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

由于 value 的类型为 char[],因此无法分配 String< /code> 到此字段 - 这就是为什么您需要在 .toUpperCase() 之后调用 toCharArray 。如果您尝试这样做,您将得到一个异常(我想是ClassCastException),但是那里的 try-catch 块将其吞噬。 (这给我们带来了另一个教训:永远不要使用这样的空 catch 块。)

注意:这段代码可能不会做正确的事情,因为原始字符串的实际数据可能不会从char[]。由于您没有更新 offset 字段,因此在使用此类修改后的字符串时,您将得到 IndexOutOfBoundsExceptions。另外,String 对象缓存了它的 hashCode,因此这也是错误的。

这是一个正确的方法:

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

通过反射,它看起来像这样:

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();
  }
}

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();
  }
}
何处潇湘 2024-12-04 12:39:16

1.) 阅读Bohemian答案。

2.) 字符串内部存储在 char 数组中,这就是为什么您需要调用 toCharArray 来设置字段。

1.) Read Bohemian answer.

2.) Strings are internally stored in a char array, that's why you need to call toCharArray to set the field.

江城子 2024-12-04 12:39:16

默认情况下,String.toUpperCase() 保持原始字符串不变,同时返回一个新的字符串对象。

您上面定义的函数会就地编辑原始字符串对象的内容。

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.

岛徒 2024-12-04 12:39:16

您可以通过反射更改最终字符串以进行测试。有时,该字符串包含生产环境中使用的默认位置的路径,但不适合测试。然而,该变量被测试中触发器的多个对象/方法引用,因此在测试期间您可能希望将其设置为特定值。

正如其他人所说,这可能是您不想做的事情(经常/永远)。

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文