方法1:String s1 = "abc";
-
这一句话做了什么操作:
- 首先在常量池中查找"abc",如果没有则在常量池创建该对象
- 在栈中创建s1的引用,将s1直接指向对象"abc"
- 因此在这里"abc"是常量池中的对象,如果声明另一个String类型的对象引用,并将它指向对象"abc",则这两个引用指向的是同一个常量池中的对象。
-
详述
- 创建过程是,"abc"是字符串,是匿名的String对象。
- 当"abc"被声明时,会先去字符串常量池中(位于堆内存中)查询是够存在该字符串了,如果存在将会返回常量池中的字符串引用地址,将此赋值给s。
- 如果没有,将会把字符串"abc"存入到常量池中,然后进行再返回地址。
- 下次再使用该字符串时,直接返回该字符串再常量池中的地址即可。
- 这种创建对象的方式叫做享元模式,在Interger类中对[-128~127)之间的数值也是使用了该模式。
-
代码示例:
| String s1 = "abc";//"abc"是一个对象,将对象赋予类变量s1 |
| String s2 = new String("abc");//这里是两个对象,在内存中存在两个,包括对象abc 和 new 出来的对象 |
| String s3 = "abc"; //因为String类型数据是不可变的,所以‘abc’被放在了常量池中,这里的‘abc’ַ和s1的‘abc’是 |
| //同一个常量abc对象,因此二者的内存地址是一样的。 |
| |
| System.out.println(s1==s2);//false |
| System.out.println(s1==s3);//true 这是这号i |
| |
| public class StringDemo { |
| public static void main(String[] args) { |
| String str = "Hello"; |
| str = str + "World"; |
| str += "!!!"; |
| System.out.println(str); |
| |
| } |
| } |

| public static void main(String[] args) { |
| String stra = "hello" ; |
| String strb = "hello" ; |
| String strc = "hello" ; |
| System.out.println(stra == strb); |
| System.out.println(stra == strc); |
| System.out.println(strb == strc); |
| } |
| } |
| |

方法2:String s = new String(“abc”);
- 凡是经过 new 创建出来的对象,都会在堆内存中分配新的空间,创建新的对象,所以s是String类新创建的对象
两种初始化方法对比
| String s = "aa"; |
| s =s + "bb"; |
| String s2 = "aabb"; |
| s == s2 |
这个的结果是false,这时候s 和s2已经不是一样的了,首先看 s2,s2指向的是常量池中的对象,这是确定的。所以尽管s的值和s2是一样的,但是s指向的不是常量池的中的对象,而是一个新的new出来的对象。 解释之前,先了解一下 + 这个符号,在字符串拼接里面,相当于+ 源码大意为: (new StringBuffer()).append(s3).append(“bbb”).toString; 所以,这里的s指向的是一个新的对象。
总结: 在String的两种声明方式,直接赋予字符值的是,String对象引用获取常量池中对象的地址,所以String声明出来是不可以改变的。new String()出来的是在堆内存创建对象。如果要给每个对象中的String属性赋予一个初始值,采用String s = ‘abc’方式,这样创建的是常量池中的一个对象,其他对象是获取这个常量的地址。要是new 则每次都要创建,加大内存消耗。还要注意,字符串拼接不要用+ ,会创建对象。
| public class Demo1 { |
| |
| @Test |
| public void test1() { |
| String s1 = "abc"; |
| String s2 = new String("abc"); |
| String s3 = "abc"; |
| |
| |
| System.out.println(s1==s2); |
| System.out.println(s1==s3); |
| |
| |
| |
| s3 = s3+"bbb"; |
| String s4 = "abcbbb"; |
| String s5 = new String("abcbbb"); |
| System.out.println(s3); |
| System.out.println(s3==s4); |
| System.out.println(s4=="abcbbb"); |
| System.out.println(s4==s5); |
| } |
| } |
| |
intern()方法
-
概述
- intern()方法是能使一个位于堆中的字符串在运行期间动态地加入到字符串常量池中(字符串常量池的内容是程序启动的时候就已经加载好了),如果字符串常量池中有该对象对应的字面量,则返回该字面量在字符串常量池中的引用,否则,创建复制一份该字面量到字符串常量池并返回它的引用。
-
代码示例:
| String s1 = "abc"; |
| String s2 = new String("abc"); |
| String s3 = new String("abc").intern();//s3其实是字符串常量"abc" |
| |
| |
| |
| |
| System.out.println(s1 == s2);//false |
| |
| //两者都是表示字符串常量abc,所以是true |
| System.out.println(s1 == s3);//true |
| |
| //s3是常量池中的对象abc,s2是堆中对象,是不同对象 |
| System.out.println(s2 == s3); |
| |
| //都表示一个值abc |
| System.out.println(s1 == "abc"); //true |
| System.out.println(s3 == "abc"); //true |
字符串内容不可变属性分析
-
弊端:
- 字符串内容的更改,实际上改变的是字符串对象的引用过程,并且会伴随有大量的垃圾出现,在实际开发中应该避免。
-
好处:
- 可以缓存hash值:
- String的值是不允许改变的,因此hash值就可以计算出来,缓存起来,不要要每次使用都计算。
- String pool的需要
- 如果一个String对象已经创建过了,那么将会从String pool中取得引用,只有String不可变,才能保证其他使用该对象是安全的。
- 线程安全
- String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
- 安全性
- String经常作为参数,String的不可变特性可以保证参数不可变。列如在作为网络连接参数的情况下,如果String可变,那么在网络连接过程中,String被改变,改变String对象的哪一方以为现在连接的是其他主机,而实际情况却不是。
参考链接
END
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现