Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/368.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 通过反射操作的字符串及其对equals方法的影响_Java - Fatal编程技术网

Java 通过反射操作的字符串及其对equals方法的影响

Java 通过反射操作的字符串及其对equals方法的影响,java,Java,它为示例代码中的以下两个打印语句打印true。我理解,它按照字符串类的等于方法的逻辑为: public boolean equals(Object anObject) { if (this == anObject) { return true; } ... } 但我无法理解他们的哈希代码是如何保持不变的。条件this==anObject是否与String类的hashCode方法有任何关系?如果是,那么它们如何相等 请帮助我

它为示例代码中的以下两个打印语句打印true。我理解,它按照字符串类的等于方法的逻辑为:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        ...
}
但我无法理解他们的哈希代码是如何保持不变的。条件this==anObject
是否与String类的hashCode
方法有任何关系?如果是,那么它们如何相等

请帮助我理解这一点

确实,字符串的值可以通过反射进行修改(在反射中它失去了不变性)。但在本例中,哈希代码保持不变。为什么?

import java.lang.reflect.Field;
public class StringHacker {
    public static void main(String[] args) throws Exception {
        String myMonth = "January";
        char[] yourMonth = {'M', 'a', 'y'};
        Field value = String.class.getDeclaredField("value");
                value.setAccessible(true);
                value.set(myMonth, yourMonth);
                System.out.println(myMonth.equals("January"));
                System.out.println(myMonth.equals("May"));
    }
}
输出为:

真的

真的

但在本例中,哈希代码保持不变。为什么?

import java.lang.reflect.Field;
public class StringHacker {
    public static void main(String[] args) throws Exception {
        String myMonth = "January";
        char[] yourMonth = {'M', 'a', 'y'};
        Field value = String.class.getDeclaredField("value");
                value.setAccessible(true);
                value.set(myMonth, yourMonth);
                System.out.println(myMonth.equals("January"));
                System.out.println(myMonth.equals("May"));
    }
}
答案是
String::hashCode
将其结果缓存在私有字段中。因此,如果您这样做:

String s = /* create string */
int hash = s.hashcode();
/* use reflection to mutate string */
int hash2 = s.hashCode();
您会发现
hash
hash2
是相同的值。这只是使用反射来改变字符串是个坏主意的另一个原因

(但是如果您阅读
String
的代码,您可以看到如何实现
hashCode
,然后使用反射清除缓存的hashCode值。)

但在本例中,哈希代码保持不变。为什么?

import java.lang.reflect.Field;
public class StringHacker {
    public static void main(String[] args) throws Exception {
        String myMonth = "January";
        char[] yourMonth = {'M', 'a', 'y'};
        Field value = String.class.getDeclaredField("value");
                value.setAccessible(true);
                value.set(myMonth, yourMonth);
                System.out.println(myMonth.equals("January"));
                System.out.println(myMonth.equals("May"));
    }
}
答案是
String::hashCode
将其结果缓存在私有字段中。因此,如果您这样做:

String s = /* create string */
int hash = s.hashcode();
/* use reflection to mutate string */
int hash2 = s.hashCode();
您会发现
hash
hash2
是相同的值。这只是使用反射来改变字符串是个坏主意的另一个原因


(但是如果您阅读
String
的代码,您可以看到
hashCode
是如何实现的,然后使用反射清除缓存的hashCode值。)

hashCode不会更改,因为
String
是一个不可变的类

这意味着通过合同,其价值不会改变。由于相同的值必须始终具有相同的哈希代码,因此无需更改哈希代码。更糟糕的是,哈希代码随时间变化的对象可能会给您带来大麻烦,例如,在处理
Set
Map

对象不能更改其哈希代码


如果您通过反射更改字符串的值,那么您就是在主动破坏契约,从而导致未定义、混乱和可能的灾难性行为。

哈希代码不会更改,因为
string
是一个不可变的类

这意味着通过合同,其价值不会改变。由于相同的值必须始终具有相同的哈希代码,因此无需更改哈希代码。更糟糕的是,哈希代码随时间变化的对象可能会给您带来大麻烦,例如,在处理
Set
Map

对象不能更改其哈希代码


如果您通过反射更改字符串的值,那么您就是在主动破坏契约,从而导致未定义、混乱和可能的灾难性行为。

您在问题中提到了hashcode,但决不调用它,也不显示它的值,也不比较hoshcode值。所以要回答你的问题:

条件this==anObject是否与String类的hashCode方法有任何关系

答案是一个强调的“否”(当然,不同于两个对同一对象的引用显然会调用同一个方法并返回相同结果的明显情况)。同样,equals()方法也不会调用/考虑hashcode()

所以让我们考虑一下=,等式()和HASCODE(),以及这些在你的例子中是如何发挥出来的。不过,首先必须提到的是,您使用反射的方式从来都不是有意使用的。在某些情况下,调用

value.set(object,value)
是有效且必要的,但是更改诸如“String”之类的不可变类的值并不是其中之一。结果是,这样做会得到奇怪的结果并不奇怪

让我们首先重申,每个对象(如字符串)都位于计算机内存中自己的位置。例如,考虑如下代码:

String myName = "Fred";
String yourName = "Fred";
String databaseName = fetchNameFromDatabase();     // returns "Fred"
boolean mineIsYours = (myName == yourName);                 // true
boolean mineIsDatabases = (myName == databaseName);         // false
boolean mineEqualsDatabases = myName.equals(databaseName);  // true
所有3个字符串都将具有相同的值“Fred”-但有一个巧妙的技巧。当Java编译器编译程序时,它会将所有硬编码字符串加载到
.class
文件中。由于字符串是不可变的,它通过在“字符串池”中创建唯一的值来节省一些空间-因此,在我的示例中,“Fred”将只创建一次,
myName
yourName
都将指向内存中的同一实例-因此
mineIsYours
将是
true

动态创建的字符串(如从数据库读取)不会使用此字符串池,因此即使它们可能具有相同的值,也会有不同的实例-因此使用
equals()
而不是
=
测试相等性的重要性

你现在能看到你的程序中发生了什么吗?让我们看几行具体内容:

   String myMonth = "January";
“一月”是一个硬编码的常量,所以它被放在字符串池中,myMonth指向该实例在内存中的位置

            value.set(myMonth, yourMonth);
myMonth的值(即myMonth指向的内存中该实例的值)更改为“May”

在myMonth上调用“equals”,将Java编译器放入字符串池中的硬编码字符串实例传递给“一月”。但是,此实例与myMonth初始化为的实例相同(请记住我的变量<代码>mineIsYours为true)!!是的,与您将的值更改为“May”的实例相同

因此,当您将字符串池中myMonth的实例值从“一月”更改为“五月”时,您不仅更改了一个myMonth变量的实例值,还更改了程序中每个硬编码的“一月”值

            System.out.println(myMonth.equals("May"));
myMonth指向的实例的值具有