找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

411

积分

0

好友

51

主题
发表于 2025-12-27 09:20:02 | 查看: 27| 回复: 0

在Java编程中,String 类是使用最频繁的类之一,也是面试中的常客。深刻理解其设计原理和内存机制,是编写高效、健壮代码的基础。本文将从底层特性出发,系统解析String的不可变性、实例化方式、内存分配及常用方法。

String类概述与不可变性

String 类被声明为 final,意味着它不可被继承。这种设计是保证其不可变性的基石。不可变性是指一个 String 对象一旦被创建,其内容(即内部的字符序列 value)就不可被改变。

其内部使用 final char[] value(在Java 9及之后,为 byte[])来存储字符串数据,这也从实现层面确保了数据的不可变。这种设计带来了诸多好处,如线程安全、作为哈希表键值的可靠性(哈希值不变)以及字符串常量池的实现基础。

不可变性的具体体现

  1. 重新赋值时,并非修改原值,而是将引用指向新创建的对象。
  2. 字符串连接操作时(如+concat),同样会生成新的对象。
  3. 调用 replace() 等方法修改字符时,也会返回一个新的字符串对象。
@Test
public void testImmutability(){
    String s1 = "abc"; // 字面量定义,值在常量池
    String s2 = "abc";
    s1 = "hello"; // s1引用指向新的常量池地址

    System.out.println(s1 == s2); // false,比较地址
    System.out.println(s1); // hello
    System.out.println(s2); // abc

    String s3 = "abc";
    s3 += "def"; // 连接操作,生成新对象“abcdef”,s3指向它
    System.out.println(s3); // abcdef
    System.out.println(s2); // abc

    String s4 = "abc";
    String s5 = s4.replace('a', 'm'); // 替换操作,生成新对象“mbc”
    System.out.println(s4); // abc
    System.out.println(s5); // mbc
}

Java String类核心特性全解析:不可变性、常量池与常用方法 - 图片 - 1

两种实例化方式与内存分配

理解 String 对象在内存中的创建方式是掌握其本质的关键。

  • 字面量定义:如 String s1 = "abc";
    这种方式会首先检查字符串常量池中是否存在内容为 "abc" 的字符串。如果存在,则直接返回该字符串的引用;如果不存在,则在常量池中创建该字符串,然后返回引用。因此,相同字面量的变量指向同一内存地址。

  • new + 构造器:如 String s2 = new String("abc");
    这种方式会先在堆空间中创建一个新的 String 对象。构造器中的参数字符串 "abc" 本身会遵循常量池的规则(如果常量池没有,则先创建)。因此,s2 指向的是堆中新对象的地址,而非常量池中的地址。

经典面试题String s = new String("abc"); 创建了几个对象?

答案:1个或2个。

  • 如果字符串常量池中已存在 "abc",则只在堆中创建1个新对象。
  • 如果常量池中不存在 "abc",则会先在常量池创建该字符串对象(第1个),然后在堆中创建新的 String 对象(第2个)。
@Test
public void testInstantiation(){
    // 字面量定义:s1和s2指向常量池中的同一个“javaEE”
    String s1 = "javaEE";
    String s2 = "javaEE";

    // new+构造器:s3和s4分别指向堆中两个不同的对象,但这两个对象内部的value都指向常量池的“javaEE”
    String s3 = new String("javaEE");
    String s4 = new String("javaEE");

    System.out.println(s1 == s2); // true,地址相同
    System.out.println(s1 == s3); // false,一个在池,一个在堆
    System.out.println(s1 == s4); // false
    System.out.println(s3 == s4); // false,堆中两个不同对象
}

Java String类核心特性全解析:不可变性、常量池与常用方法 - 图片 - 2

字符串拼接的底层规则

字符串的 + 连接操作在编译期和运行期有不同的处理逻辑,是另一个考察重点。

结论

  1. 常量与常量的拼接结果在编译期就会被确定,并直接放入字符串常量池。
  2. 只要拼接的操作数中有一个是变量(非常量),结果就会在堆中(new)创建。
  3. 可以调用 intern() 方法,主动将堆中字符串对象的引用放入常量池(如果池中已有则直接返回池中引用),并返回该常量池引用。
@Test
public void testConcatenation(){
    String s1 = "javaEEhadoop";
    final String s4 = "javaEE"; // s4被final修饰,是编译期常量
    String s5 = s4 + "hadoop"; // 常量+常量,结果在常量池
    System.out.println(s1 == s5); // true

    String s2 = "javaEE";
    String s3 = s2 + "hadoop"; // 变量(s2) + 常量,结果在堆中
    System.out.println(s1 == s3); // false

    // intern()方法示例
    String s6 = s3.intern(); // 将s3对应的字符串尝试放入常量池,并返回池中引用
    System.out.println(s1 == s6); // true
}

Java String类核心特性全解析:不可变性、常量池与常用方法 - 图片 - 3

一道关于值传递的面试题

这道题综合考察了字符串的不可变性和Java的参数传递机制(值传递)。

public class StringTest {
    String str = new String("good");
    char[] ch = { 't', 'e', 's', 't' };

    public void change(String str, char ch[]) {
        str = "test ok"; // 修改的是形参str的指向,不影响成员变量str
        ch[0] = 'b';     // 直接修改了传入的数组对象的内容
    }
    public static void main(String[] args) {
        StringTest ex = new StringTest();
        ex.change(ex.str, ex.ch);
        System.out.println(ex.str); // 输出 "good"
        System.out.println(ex.ch); // 输出 "best"
    }
}

Java String类核心特性全解析:不可变性、常量池与常用方法 - 图片 - 4

String类的常用方法

String 类提供了丰富的方法,熟练使用它们是进行日常Java字符串处理的基础。

常用方法示例

public class StringMethodTest {

    // 1. 获取与判断
    @Test
    public void testBasicMethods(){
        String s1 = "HelloWorld";
        System.out.println(s1.length()); // 10
        System.out.println(s1.charAt(4)); // 'o'
        System.out.println(s1.isEmpty()); // false

        String s2 = s1.toLowerCase();
        System.out.println(s1); // HelloWorld (不变)
        System.out.println(s2); // helloworld

        String s3 = "   he  llo   world   ";
        System.out.println("---" + s3.trim() + "---"); // ---he  llo   world---
    }

    // 2. 比较、截取与连接
    @Test
    public void testCompareAndSubstring(){
        String s1 = "HelloWorld";
        String s2 = "helloworld";
        System.out.println(s1.equals(s2)); // false
        System.out.println(s1.equalsIgnoreCase(s2)); // true

        String s3 = "北京尚硅谷教育";
        String s4 = s3.substring(2); // "尚硅谷教育"
        String s5 = s3.substring(2, 5); // "尚硅谷" (包左不包右)
        System.out.println(s4);
        System.out.println(s5);
    }

    // 3. 查找与匹配
    @Test
    public void testSearch(){
        String str1 = "hellowworld";
        System.out.println(str1.contains("wor")); // true
        System.out.println(str1.indexOf("lo")); // 3
        System.out.println(str1.lastIndexOf("or")); // 7
        System.out.println(str1.startsWith("He")); // false
        System.out.println(str1.endsWith("rld")); // true
    }

    // 4. 替换、分割与匹配
    @Test
    public void testReplaceAndSplit(){
        String str1 = "北京尚硅谷教育北京";
        System.out.println(str1.replace('北', '东')); // 东京尚硅谷教育东京
        System.out.println(str1.replace("北京", "上海")); // 上海尚硅谷教育上海

        String str2 = "12hello34world5java7891mysql456";
        // 将数字替换为逗号,并去掉首尾多余的逗号
        String result = str2.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
        System.out.println(result); // hello,world,java,mysql

        String str3 = "hello|world|java";
        String[] strs = str3.split("\\|");
        for (String s : strs) {
            System.out.print(s + " "); // hello world java
        }
    }
}

Java String类核心特性全解析:不可变性、常量池与常用方法 - 图片 - 5

String与其他结构的转换

1. 与基本类型/包装类互转

@Test
public void testParse(){
    String str1 = "123";
    int num = Integer.parseInt(str1); // String -> int

    String str2 = String.valueOf(num); // int -> String (推荐)
    String str3 = num + ""; // 会在堆中生成新对象

    System.out.println(str1 == str3); // false
}

2. 与字符数组互转

@Test
public void testCharArray(){
    String str1 = "abc123";
    char[] charArray = str1.toCharArray(); // String -> char[]
    for(char c : charArray){
        System.out.println(c);
    }

    char[] arr = {'h','e','l','l','o'};
    String str2 = new String(arr); // char[] -> String
    System.out.println(str2);
}

3. 与字节数组互转(涉及编码)
编码解码是网络传输和文件处理中的常见操作,理解网络与系统基础中的字符集概念非常重要。

@Test
public void testByteArray() throws UnsupportedEncodingException {
    String str1 = "abc123中国";
    // 编码(String -> byte[]),使用默认字符集(UTF-8)
    byte[] bytes = str1.getBytes();
    System.out.println(Arrays.toString(bytes));

    // 使用指定字符集(GBK)编码
    byte[] gbks = str1.getBytes("gbk");

    // 解码(byte[] -> String),必须使用与编码一致的字符集
    String str2 = new String(bytes); // 默认UTF-8解码,正常
    String str3 = new String(gbks, "gbk"); // 使用GBK解码,正常
    String str4 = new String(gbks); // 使用UTF-8解码GBK编码的字节,乱码

    System.out.println(str2);
    System.out.println(str3);
    System.out.println(str4); // 输出乱码
}

Java String类核心特性全解析:不可变性、常量池与常用方法 - 图片 - 6




上一篇:Apache ActiveMQ CVE-2015-5254反序列化漏洞实战复现与原理分析
下一篇:Android Jetpack Lifecycle组件详解:实现生命周期感知与代码解耦
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 18:36 , Processed in 0.203724 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表