如何证明String常量值在栈中存放的是常量池中的地址呢?

本文通过三个实验,深入探讨了Java反射机制如何影响字符串常量池中的对象引用和修改。揭示了字符串在堆内存中的存储方式,以及通过反射修改字符串内部数组的潜在风险。
package reflect;

import java.lang.reflect.Field;

/**
 * @ClassName: TestReflect
 * @Description: 
 * @Author: xuezhouyi
 * @Version: V1.0
 **/
public class TestReflect {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        f0();
        System.out.println("--------------------");
        f1();
        System.out.println("--------------------");
        f2();
    }

    public static void f0(){
        /* 看上去栈中s1存的好像是常量值,s2存的是地址,其实都是地址 */
        String s1 = "abc";
        String s2 = new String("abc");
        System.out.println("s1="+s1+"|s2="+s2+"|"+(s1==s2));
    }

    public static void f1() throws NoSuchFieldException, IllegalAccessException {
        /* s1和s2在栈中开辟空间存放字符串常量池中的地址 */
        String s1 = "abc";
        String s2 = "aXc";

        /* 比较栈中的地址肯定不同 */
        System.out.println("s1="+s1+"|s2="+s2+"|"+(s1==s2));
        Class<String> stringClass = String.class;
        Field fValue = stringClass.getDeclaredField("value");
        fValue.setAccessible(true);
        char[] chars = (char[]) fValue.get(s2);

        /* 强制修改成相同的字符串,栈中的地址没有被修改所以仍为false */
        chars[1] = 'b';
        System.out.println("s1="+s1+"|s2="+s2+"|"+(s1==s2));
    }

    public static void f2() throws NoSuchFieldException, IllegalAccessException {
        String s1 = "abc";
        String s2 = "abc";
        System.out.println("s3="+s1+"|s4="+s2+"|"+(s1==s2));
        Class<String> stringClass = String.class;
        Field fValue = stringClass.getDeclaredField("value");
        fValue.setAccessible(true);
        char[] chars = (char[]) fValue.get(s2);
        chars[1] = 'X';
        System.out.println("s3="+s1+"|s4="+s2+"|"+(s1==s2));
    }
}

 

### Java String 常量池的工作原理及作用 #### 工作原理 在 Java 中,`String` 是一种特殊的对象类型,其不可变性和常量池机制是为了优化内存使用和提升程序性能而设计的。当创建一个 `String` 对象时,如果该字符串已经存在于常量池中,则不会重新分配内存空间来存储相同的值,而是直接返回已存在的引用[^1]。 具体来说,Java 的字符串常量池主要由两部分组成: - **静态常量池**:这是在编译阶段形成的,在 `.class` 文件中有对应的结构表示。 - **运行时常量池**:这是在 JVM 加载类之后形成的一种数据结构,位于方法区内存区域。它不仅包含了静态常量池的内容,还可以动态加入新生成的字符串实例[^4]。 以下是关于字符串常量池的一些关键行为: 1. 当通过字面量方式定义字符串(如 `String str = "abc";`),JVM 会先检查字符串常量池是否存在相同内容的对象。如果存在则重用已有对象;否则将其添加到池中并返回相应引用[^3]。 2. 使用 `new String()` 构造器显式创建字符串时,会在堆上开辟一块独立的空间存放此字符串,并不自动将其实例化版本放入常量池中。因此即使两个变量都指向同一个字面意义上的字符串,它们仍然可能代表不同地址上的实体[^2]。 3. 方法 `intern()` 可以手动干预这一过程——调用某个非池化的字符串实例的 `intern()` 函数后,若发现当前字符串尚未驻留于全局共享表里,则会被复制一份进去供后续查找匹配之需[^4]。 #### 作用 引入字符串常量池的主要目的是为了节省资源消耗以及加速比较操作: - **减少重复占用**: 如果多个地方需要用到完全一致的文字序列表达形式的话,那么只需要保存单一副本即可满足需求. - **加快相等判断速度**: 因为基于引用对比(`==`)要比逐字符逐一核验效率高得多所以只要确认两者确实来自同一份原始资料就足以得出结论. ```java // 示例代码展示 intern() 功能 public class Main { public static void main(String[] args) { String a = new StringBuilder("计算机").append("软件").toString(); System.out.println(a.intern() == a); // 输出 true String b = new StringBuilder("ja").append("va").toString(); System.out.println(b.intern() == b); // 输出 false (因为"java"已经在加载时进入了常量池) } } ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值