Java里不改变内存的指向而改变String的值

来源:互联网 发布:fotor mac 破解 编辑:程序博客网 时间:2024/05/30 04:56

以前都以为string 定义为final的了,在java中是不能修改的,但是今天面试的时候问这个问题,我回答说如果真想修改就修改物理地址中的数据了,然后回头看了下,这个也是可以修改的:

string对象是放一个共享内存池里的,创建string时,先看池里有没有,有就返回,没有则创建。

  1. package com.leon.demo;  
  2.   
  3. import java.lang.reflect.Field;  
  4.   
  5. public class StringDemo {  
  6.     public static void main(String[] args) throws Exception {  
  7.         String str01 = "aaa";  
  8.         String str02 = "aaa";  
  9.         String str03 = "bbbb";  
  10.         System.out.println(str01.hashCode() + "/" + str02.hashCode() + "/" + str03.hashCode());  
  11.         Field field = str02.getClass().getDeclaredField("value");  
  12.         field.setAccessible(true);  
  13.         field.set(str02,new char[] { 'b''b''b''b'});  
  14.         System.out.println(str01 + "/" + str02);  
  15.         field = str02.getClass().getDeclaredField("count");  
  16.         field.setAccessible(true);  
  17.         field.set(str02,4);  
  18.   
  19.         System.out.println(str01 + "/" + str02);  
  20.         System.out.println(str01.hashCode() + "/" + str02.hashCode() + "/" + str03.hashCode());  
  21.         field = str02.getClass().getDeclaredField("hash");  
  22.         field.setAccessible(true);  
  23.         field.set(str02,0);  
  24.         System.out.println(str01.hashCode() + "/" + str02.hashCode() + "/" + str03.hashCode());  
  25.         System.out.println(str01 == str02);  
  26.         System.out.println(str02 == str03);  
  27.     }  
运行结果如下: 
Java代码  收藏代码
  1. 96321/96321/3016832  
  2. bbb/bbb  
  3. bbbb/bbbb  
  4. 96321/96321/3016832  
  5. 3016832/3016832/3016832  
  6. true  
  7. false  
  • 根据Java String的特性,str01和str02指向同一个内存地址,所以str01,str02始终保持一致。
  • String不能修改的本质是一个final的字符数组(private final char value[];),但是final只在编译时有效,而运行期间是没有作用的,所以可以通过反射修改数组的值。
  • 代码中修改后字符串的hashcode之所以没有变化,是因为String对象会缓存hashcode,去掉缓存就可以看到变化。
  • 根据Java String的特性,上面代码创建的所有字符串对象都保存在String Pool中,代码中str02与str03的内容相同,地址却不同,所以String Pool中就存在两个相同的字符串。
至于 hashcode 为什么没有改我们看源码也很清楚 

public int hashCode() { 
int h = hash; 
if (h == 0) { 
    int off = offset; 
    char val[] = value; 
    int len = count; 

            for (int i = 0; i < len; i++) { 
                h = 31*h + val[off++]; 
            } 
            hash = h; 
        } 
        return h; 


java为了提高效率, 只再第一次调用hashcode函数时生成一次hash, 并把值存在这个实例里面。 以后即使value数组的值变化了,也不再生成新的hash。

最后,感叹一下,反射机制真是厉害啊!


首先反射机制是不能访问私有成员的.只能访问公有成员.至少我的代码测试了不行。否则还有什么封装性可言? 

其次,String不可以变的原因很多.不仅仅是因为value是final,还在于这个类是final的,value是private的,而且这些都只是辅助加强对String的不可变性的保护措施,并不能完全保证不可变。因为java的对象变量存储的是引用.作为数组的vlaue也是引用。final最多只能保证其一直指向同一个数组对象,而要想保证数组本身访问时不改变其每个元素的内容,只能通过其对外提供的方法保证只能读。对外的构造方法传递的char数组必须先拷贝下来,防止外界修改。当然为了节省资源有一个构造方法能够通过直接传递数组对象,不拷贝.但是这样会容易破坏封装性,使得外界能够通过数组对象改变String值。所以这个构造方法是包内访问的。而String又在java.lang包下。java包都是被保护起来了,外界不能修改,即里面的类都是Sun公司写好的,非常安全. 

以上这些措施才保证了String的不可变性

附上

三分钟理解Java中字符串(String)的存储和赋值原理

可能很多java的初学者对String的存储和赋值有迷惑,以下是一个很简单的测试用例,你只需要花几分钟时间便可理解。

1.在看例子之前,确保你理解以下几个术语:

 

 :由JVM分配区域,用于保存线程执行的动作和数据引用。栈是一个运行的单位,Java中一个线程就会相应有一个线程栈与之对应。

 

 :由JVM分配的,用于存储对象等数据的区域。

 

常量池 :在堆中分配出来的一块存储区域,用于存储显式 的String,float或者integer.例如String str="abc"; abc这个字符串是显式声明,所以存储在常量池。

 

2.看这个例子,用JDK5+junit4.5写的例子,完全通过测试

Java代码  收藏代码
  1. import static org.junit.Assert.assertNotSame;  
  2. import static org.junit.Assert.assertSame;  
  3.   
  4. import org.junit.Test;  
  5.   
  6. /** 
  7.  * @author Heis 
  8.  * 
  9.  */  
  10. public class StringTest{  
  11.   
  12.     @Test  
  13.     public void testTheSameReference1(){  
  14.         String str1="abc";  
  15.         String str2="abc";  
  16.         String str3="ab"+"c";  
  17.         String str4=new String(str2);  
  18.           
  19.         //str1和str2引用自常量池里的同一个string对象  
  20.         assertSame(str1,str2);  
  21.         //str3通过编译优化,与str1引用自同一个对象  
  22.         assertSame(str1,str3);  
  23.         //str4因为是在堆中重新分配的另一个对象,所以它的引用与str1不同  
  24.         assertNotSame(str1,str4);  
  25.     }  
  26.       
  27. }  

 

  • 第一个断言很好理解,因为在解析的时候,"abc"被存储在常量池中,str1和str2的引用都是指向常量池中的"abc"。所以str1和str2引用是相同的。
  • 第二个断言是由于编译器做了优化,编译器会先把字符串拼接,再在常量池中查找这个字符串是否存在,如果存在,则让变量直接引用该字符串。所以str1和str3引用也是相同的。
  • str4的对象不是显式赋值的,编译器会在堆中重新分配一个区域来存储它的对象数据。所以str1和str4的引用是不一样的。




0 0
原创粉丝点击