当前位置: 首页 > news >正文

Java String类:不可变性的核心奥秘

目录

一、源码

1、String类声明核心代码

2、核心方法源码

①length()方法

②charAt()方法

③substring()方法

④replace()方法

3、构造函数源码

4、总结

二、常用构造方法

三、常量池

四、常用方法

1、length()

2、equals()

3、charAt()

4、toCharArray()

5、split()

6、replace()

7、substring()

        String 类是 java 中最古老、最重要的类之一

        它属于 java.util 包下,所以使用时不需要导包

        它的设计体现了 java 的核心哲学:不可变性

一、源码

在学习之前,我们先来通过源码看一下不可变性体现在哪里:

1、String类声明核心代码

// JDK 8 中的 String 类声明
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {// 核心存储结构 - 字符数组private final char value[];// 缓存哈希码,提高性能private int hash;
}

2、核心方法源码

①length()方法

public int length() {return value.length;  // 直接返回数组长度,不修改任何内容
}

②charAt()方法

public char charAt(int index) {if ((index < 0) || (index >= value.length)) {throw new StringIndexOutOfBoundsException(index);}return value[index];  // 只读取,不修改
}

③substring()方法

public String substring(int beginIndex) {int subLen = value.length - beginIndex;if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex);}if (subLen < 0) {throw new StringIndexOutOfBoundsException(subLen);}// 返回新的String对象,共享原数组(JDK 8中)return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}public String substring(int beginIndex, int endIndex) {if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex);}if (endIndex > value.length) {throw new StringIndexOutOfBoundsException(endIndex);}int subLen = endIndex - beginIndex;if (subLen < 0) {throw new StringIndexOutOfBoundsException(subLen);}// 返回新的String对象return ((beginIndex == 0) && (endIndex == value.length)) ? this: new String(value, beginIndex, subLen);
}

④replace()方法

public String replace(char oldChar, char newChar) {// 如果新旧字符相同,直接返回原对象if (oldChar != newChar) {int len = value.length;int i = -1;// 查找第一个需要替换的字符while (++i < len) {if (value[i] == oldChar) {break;}}// 如果找到了需要替换的字符if (i < len) {char buf[] = new char[len];// 复制前面不需要替换的部分for (int j = 0; j < i; j++) {buf[j] = value[j];}// 替换并复制剩余部分for (; i < len; i++) {char c = value[i];buf[i] = (c == oldChar) ? newChar : c;}// 创建新的String对象return new String(buf, true);}}return this;  // 没有找到需要替换的字符,返回原对象
}

3、构造函数源码

// 包级私有构造函数,用于优化substring等操作
String(char[] value, boolean share) {// assert share : "unshared not supported";this.value = value;  // 直接引用传入的数组
}// 公共构造函数,复制数组确保不可变性
public String(char value[]) {this.value = Arrays.copyOf(value, value.length);  // 创建副本
}

4、总结

①类:final 修饰类,防止继承破坏不可变性

②数据:final 修饰 value 数组,防止引用改变

③方法:所有方法都返回新对象或原始值,不修改原对象

④构造函数:通过数组复制确保外部无法通过原数组修改 String 内容

⑤缺少修改方法:没有提供任何修改内部状态的方法

二、常用构造方法

方法名说明
public String()创建一个空白字符串对象,不含有任何内容
public String(char[] arr)根据字符数组的内容,创建字符串对象
public String(String original)根据传入的字符串内容,创建字符串对象
String s = "abc";直接赋值的方式创建字符串对象

示例:

public class Test{public static void main(String[] args){// public String():// 创建一个空白字符串对象,不含有任何内容String s1 = new String();System.out.println(s1);// public String(char[] chs):// 根据字符数组的内容,来创建字符串对象char[] chs = {'a','b','c'};String s2 = new String(chs);System.out.println(s2);// public String(String original) : //根据传入的字符串内容,来创建字符串对象String s3 = new String("123");System.out.println(s3);String s4 = "hello";System.out.println(s4);}
}

字符串对象创建之后,堆空间中字符串的 内容 和 内存地址 不可以被修改, 对象引用 可以修改

public class ReferenceMutability {public static void main(String[] args) {// 引用str1指向堆中的"Hello"对象String str1 = "Hello";// 引用str1现在指向堆中的"World"对象// 这是引用的改变,不是对象内容的改变str1 = "World";// 引用str1现在指向堆中的"Java"对象str1 = "Java";System.out.println(str1); // 输出: Java// 演示多个引用指向同一对象String str2 = "Hello";String str3 = "Hello";// str2和str3指向同一个对象(字符串常量池)System.out.println("str2 == str3: " + (str2 == str3)); // true// 但当我们"修改"时,实际上是创建新对象str2 = str2 + " World";// 现在str2指向新对象,str3仍指向原对象System.out.println("str2: " + str2); // Hello WorldSystem.out.println("str3: " + str3); // HelloSystem.out.println("str2 == str3: " + (str2 == str3)); // false}
}

三、常量池

        那么既然堆内存中的值不可变

        它又要频繁创建

        有没有什么好的办法呢?

JVM 为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化:

        为字符串开辟了一个 字符串常量池,类似于缓存区

        创建字符串常量时,首先会检查字符串常量池中是否存在该字符串,如果存在,则返回该实例的引用;如果不存在,则实例化创建该字符串,并放入池中。

        java 8 之前,字符串常量池在方法区中

        java 8 及之后,字符串常量池位于堆内存中,方便调整字符串常量池大小,并且可以享受到垃圾回收器对堆内存的优化

java 将字符串放入 String 常量池的方法:

1、直接赋值:

        eg:String str = "Hello";

2、调用 String 类提供的 intern() 方法:

        eg:String str = new String("World").intern();

该方法作用:

若字符串常量池中存在内容相同字符串,则返回池中的引用

若不存在,则将当前字符串放入池中并返回池中的引用

注意!

通过 new 关键字创建的字符串对象不会放入常量池中,也不会从池中取

而是从堆内存中创建一个新对象

示例:

public class Test{public static void main(String[] args){String s1 = "Hello";  // 字符串常量,放入常量池String s2 = "Hello";  // 直接引用常量池中的字符串对象System.out.println(s1 == s2);  // true,引用相同// 直接new String对象,不会将'World'放入常量池String s3 = new String("World");// 调用intern()方法,将'World'放入常量池,并返回常量池中的引用String s4 = new java.lang.String("World").intern();String s5 = "World";System.out.println(s3 == s4);  // false,引用不同System.out.println(s4 == s5);  // true,引用相同}
}
public class Test{public static void main(String[] args){String a = 'a';String b = 'b';// 常量优化机制:"a" 和 "b"都是字面值常量,借助 + 连接,其结果 "ab" 也被当作常量String s3 = "a" + "b";String s4 = "ab";System.out.println(s3.equals(s4)); // trueSystem.out.println(s3 == s4); // trueSystem.out.println("-------------");String s5 = s1 + s2;System.out.println(s4.equals(s5)); // trueSystem.out.println(s4 == s5); // falseSystem.out.println("-------------");String s6 = (s1 + s2).intern();System.out.println(s4.equals(s6)); // trueSystem.out.println(s4 == s6); // true}
}

四、常用方法

1、length()

public int length()
            调用者:String类型对象调用
            参数:无参
            返回值:int 类型当前字符串长度
            作用:返回 int 类型当前字符串长度
            会不会改变原始值:不会

public class StringLengthDemo {public static void main(String[] args) {String story = "在遥远的东方,有一条龙";System.out.println("故事长度:" + story.length()); // 输出:11// 小知识:这个方法的时间复杂度是 O(1),不是 O(n)// 因为长度在对象创建时就已经计算好了}
}

2、equals()

public boolean equals(Object obj)
            调用者:String类型对象调用
            参数:Object obj   要比较的字符串对象
            返回值:boolean
            作用:判断两个字符串是否相等
            会不会改变原始值:不会

public class StringComparison {public static void main(String[] args) {String a = new String("Hello");String b = new String("Hello");String c = "Hello";String d = "Hello";System.out.println(a == b);        // false - 比较引用System.out.println(a.equals(b));   // true  - 比较内容System.out.println(c == d);        // true  - 字符串常量池的魔法}
}

3、charAt()

public char charAt(int index)
            调用者:String类型对象调用
            参数:索引值
            返回值:char
            作用:返回指定索引值位置的char值
            会不会改变原始值:不会

            会不会改变原始值:不会
            其他作用:for循环遍历,将字符串变为字符数组


            注意:
                索引超出长度    运行时错误:java.lang.StringIndexOutOfBoundsException
                索引正常范围    
                索引为负数      运行时错误:java.lang.StringIndexOutOfBoundsException

public class CharacterExplorer {public static void main(String[] args) {String password = "Java123!";// 安全的字符遍历方式for (int i = 0; i < password.length(); i++) {char ch = password.charAt(i);System.out.println("位置 " + i + " 的字符是:" + ch);}// 注意:负数索引会抛出 StringIndexOutOfBoundsExceptiontry {char error = password.charAt(-1);} catch (StringIndexOutOfBoundsException e) {System.out.println("索引越界啦!这是 Java 的保护机制。");}}
}

4、toCharArray()

public char[] toCharArray()
            调用者:String类型对象调用
            参数:无参
            返回值:char[]
            作用:字符串改为数组
            会不会改变原始值:不会

public class ToCharArrayDemo {public static void main(String[] args) {String str = "Hello World";// 将字符串转换为字符数组char[] charArray = str.toCharArray();System.out.println("原字符串: " + str);System.out.print("字符数组: ");// 遍历字符数组for (char ch : charArray) {System.out.print("'" + ch + "' ");}System.out.println();// 修改字符数组不会影响原字符串charArray[0] = 'h';System.out.println("修改数组后原字符串: " + str); // 仍然是 "Hello World"System.out.println("修改后的数组第一个字符: " + charArray[0]); // 变为 'h'}
}

5、split()

public String[] split(String regex)

public String[] split(String regex, int limit)
            调用者:String类型对象调用
            参数:正则表达式(规则)
            返回值:String[]
            作用:将字符串按照正则表达式进行切割,返回字符串数组
            注意:特殊符号要加转义字符
            会不会改变原始值:不会

public class SplitDemo {public static void main(String[] args) {// 基本用法String sentence = "Java is awesome and powerful";String[] words = sentence.split(" ");System.out.println("原句子: " + sentence);System.out.print("分割结果: ");for (String word : words) {System.out.print("[" + word + "] ");}System.out.println();// 使用正则表达式分割String data = "apple,banana;orange:grape";String[] fruits = data.split("[,;:]"); // 使用字符类分割System.out.print("多种分隔符分割: ");for (String fruit : fruits) {System.out.print("[" + fruit + "] ");}System.out.println();// 特殊字符需要转义String path = "user\\documents\\file.txt";String[] pathParts = path.split("\\\\"); // 转义用“\\”或“[]”System.out.print("路径分割: ");for (String part : pathParts) {System.out.print("[" + part + "] ");}System.out.println();// 限制分割次数String numbers = "1,2,3,4,5,6";String[] limited = numbers.split(",", 3); // 最多分割成3部分System.out.print("限制分割次数: ");for (String num : limited) {System.out.print("[" + num + "] ");}System.out.println();}
}

多个需要转义的分隔符的话:

用“\\”:每个之间需要加上“|”(或)

用“[]”:直接都写在方括号里即可

注意:

在 [ ] 中仍需转义的字符:

  • 反斜杠 \‌:由于其在正则中始终是转义字符,需双重转义为 \\\\ 13。
  • 右方括号 ]‌:作为字符组结束符,需转义为 \\] 310。
  • 脱字符 ^‌:仅在字符组开头表示取反时需转义(\\^),否则无需转义 10。
  • 连字符 -‌:仅在表示范围(如 a-z)时需转义为 \\-;位于字符组开头或结尾时可省略转义 10。

6、replace()

public String replace(char oldChar, char newChar)
            调用者:String类型对象调用
            参数:两个char类型的参数,一个旧的值,一个新的值
            返回值:新的String
            作用:将字符串中的旧字符替换成新的字符
            注意:所有满足条件的旧值都会被替换成新的值
                  所有都不满足,返回原始字符串

public String replace(CharSequence target, CharSequence replacement)
            调用者:String类型对象调用
            参数:两个CharSequence类型的参数(字符串)
            返回值:新的String
            作用:将字符串中的旧字符替换成新的字符

public class ReplaceDemo {public static void main(String[] args) {// replace(char oldChar, char newChar)String text1 = "Hello World";String result1 = text1.replace('l', 'L');System.out.println("原字符串: " + text1);System.out.println("替换字符后: " + result1); // HeLLo WorLd// replace(CharSequence target, CharSequence replacement)String text2 = "Java is great, Java is powerful";String result2 = text2.replace("Java", "Python");System.out.println("原字符串: " + text2);System.out.println("替换字符串后: " + result2); // Python is great, Python is powerful// 没有匹配项的情况String text3 = "Hello World";String result3 = text3.replace('x', 'y');System.out.println("无匹配项: " + result3); // 仍然是 "Hello World"// 部分匹配的情况String text4 = "banana";String result4 = text4.replace("ana", "XXX");System.out.println("部分替换: " + result4); // bXXXna// 替换空字符串String text5 = "Hello  World"; // 两个空格String result5 = text5.replace("  ", " "); // 替换为一个空格System.out.println("空格替换: '" + result5 + "'"); // 'Hello World'}
}

7、substring()

public String substring(int beginIndex)
            调用者:String类型对象调用
            参数:int下标,代表开始截取的位置(包含开始的位置)
            返回值:String(截取后的字符串)
            作用:字符串截取
            注意:下标越界
            会不会改变原始值:不会

public String substring(int beginIndex, int endIndex)
            调用者:String类型对象调用
            参数:int下标,代表开始截取的位置(包含开始位置)

                       int下标,代表结束截取的位置(不包含结束位置)
            返回值:String(截取后的字符串)
            作用:字符串截取
            注意:下标越界
            会不会改变原始值:不会

public class SubstringDemo {public static void main(String[] args) {String email = "user@example.com";// substring(int beginIndex) - 从指定位置到末尾String username = email.substring(0, email.indexOf('@'));String domain = email.substring(email.indexOf('@') + 1);System.out.println("原邮箱: " + email);System.out.println("用户名: " + username);System.out.println("域名: " + domain);// substring(int beginIndex, int endIndex) - 指定起始和结束位置String str = "Hello World Java";String sub1 = str.substring(6, 11); // 从索引6到10(不包含11)String sub2 = str.substring(12);    // 从索引12到末尾System.out.println("原字符串: " + str);System.out.println("substring(6,11): " + sub1); // WorldSystem.out.println("substring(12): " + sub2);   // Java// 边界情况演示try {String error = str.substring(20); // 索引越界} catch (StringIndexOutOfBoundsException e) {System.out.println("索引越界异常: " + e.getMessage());}try {String error = str.substring(5, 2); // 起始索引大于结束索引} catch (StringIndexOutOfBoundsException e) {System.out.println("索引范围错误: " + e.getMessage());}// 空字符串处理String empty = "";try {String result = empty.substring(0);System.out.println("空字符串substring(0): '" + result + "'");} catch (StringIndexOutOfBoundsException e) {System.out.println("空字符串异常: " + e.getMessage());}}
}

http://www.dtcms.com/a/326865.html

相关文章:

  • Evaluation Warning: The document was created with Spire.XLS for Pyth用Python实现Excel转PDF并去除Spire.XLS水印
  • 银河通用招人形机器人强化学习算法工程师了
  • Python 类元编程(类工厂函数)
  • C语言(06)——二、八、十、十六进制的相互转换
  • Webpack Loader 完全指南:从原理到配置的深度解析
  • TRL - Transformer Reinforcement Learning 传递给SFTTrainer的数据集
  • 【linux】企业高性能web服务器
  • 多路转接 select
  • FinQ4Cn: 基于 MCP 协议的中国 A 股量化分析
  • CSS预处理器之Sass全面解析与实战指南
  • PowerDesigner生成带注释的sql方法
  • 腾讯前端面试模拟详解
  • 分享一款基于STC32G12K128单片机的螺丝机供料器控制板 ES-IO2422 S4
  • 浅谈 LangGraph 子图流式执行(subgraphs=True/False)模式
  • [鹧鸪云]光伏AI设计平台解锁电站开发新范式
  • Kubernetes生产环境健康检查自动化指南
  • Centos8系统在安装Git包时,报错:“没有任何匹配: git”
  • 【ros-humble】4.C++写法巡场海龟(服务通讯)
  • 搭建纯竞拍商城的核心技术要点与实施指南
  • 4-下一代防火墙组网方案
  • [Element-plus]动态设置组件的语言
  • GPT-oss:OpenAI再次开源新模型,技术报告解读
  • 【无标题】matplotlib与seaborn数据库
  • 基于FPGA的热电偶测温数据采集系统,替代NI的产品(二)总体设计方案
  • 嵌入式硬件中AI硬件设计方法与技巧
  • java内部类-匿名内部类
  • 编程技术杂谈4.0
  • Dify入门指南(2):5 分钟部署 Dify:云服务 vs 本地 Docker
  • 做调度作业提交过程简单介绍一下
  • 第二十九天(文件io)