String类为什么被设计成不可变类
1.享元模式:
1.共享元素模式,也就是说:一个系统中如果有多处用到了相同的一个元素,那么我们应该只存储一份此元素,而让所有地方都引用这一个元素。
2.Java中String就是根据享元模式设计的,而那个存储元素的地方就叫做 "字符串常量池——String Pool"。
public class Apple { public static void main(String[] args) { String a = "abc"; String b = "abc"; String c = new String("abc"); System.out.println(a == b); // true System.out.println(a.equals(b)); // true System.out.println(a == c); // false System.out.println(a.equals(c)); // true } } // 因为String太过常用,JAVA类库的设计者在实现时做了个小小的变化,即采用了享元模式。 // 每当生成一个新内容的字符串时,他们都被添加到一个共享池中,当第二次再次生成同样内容的字符串实例时, // 就共享此对象,而不是创建一个新对象,但是这样的做法仅仅适合于通过=符号进行的初始化。 // 需要说明一点的是,在object中,equals()是用来比较内存地址的, // 但是String重写了equals()方法,用来比较内容的,即使是不同地址,只要内容一致,也会返回true, // 这也就是为什么a.equals(c)返回true的原因了。
2.享元模式分析:
int x = 10;
String y = "hello";
1). 首先,10 和 "hello"会在经过javac(或者其他编译器)编译过后变为Class文件中constant_pool table的内容。
2). 当我们的程序运行时,也就是说JVM运行时,每个Class constant_pool table中的内容会被加载到JVM内存中的方法区中各自Class的Runtime Constant Pool。
3). 一个没有被String Pool包含的Runtime Constant Pool中的字符串(这里是"hello")会被加入到String Pool中(HosSpot使用hashtable引用方式),步骤如下:
一是:在Java Heap中根据"hello"字面量create一个字符串对象
二是:将字面量"hello"与字符串对象的引用在hashtable中关联起来,键 - 值 形式是:"hello" = 对象的引用地址。
4). 当一个新的字符串出现在Runtime Constant Pool 中时怎么判断需不需要在Java Heap中创建新对象呢?
策略是这样:
会先去根据equals来比较Runtime Constant Pool 中的这个字符串是否和String Pool中某一个是相等的(也就是找是否已经存在),
如果有那么就不创建,直接使用其引用;
如此,就实现了享元模式,提高的内存利用效率。
3.设计成不可变类的好处:
优点:
1.效率:例如字符串常量池,字符串常量池可以将一些字符常量放在常量池中重复使用,避免每次都重新创建相同的对象、节省存储空间。
但如果字符串是可变的,此时相同内容的String还指向常量池的同一个内存空间,当某个变量改变了该内存的值时,其他遍历的值也会发生改变。
2.安全性:不可变对象天生是线程安全的,在不同线程共享对象,不需要同步机制,因为对象的值是固定的。
缺点:
资源开销,对象需要频繁的修改属性,则每一次修改都会新创建一个对象,产生大量的资源开销。
常见的不可变类:String
Integer
Long
等类型。
4. 如何设计一个不可变类:
1. 类使用final修饰符修饰,保证类不能被继承。
如果类可以被继承会破坏类的不可变性机制,只要继承类覆盖父类的方法并且继承类可以改变成员变量值,那么一旦子类以父类的形式出现时,不能保证当前类是否可变。
2.类的成员变量都应该是private final的,保证成员变量不可改变。
3.任何获取/修改属性的方法都不应作用于属性本身。
不提供修改成员变量的方法,例如setter方法。
getter方法不能返回对象本身,要返回对象的拷贝,防止对象外泄。
修改对象的属性时要返回新对象
4.对成员变量的初始化通过构造器进行,并进行深拷贝。
如果使用传入的参数直接赋值,则传递的只是引用,仍然可以通过外部变量改变它的值。
5.Demo:
public static void main(String[] args) { String s1 = "ab"; String s2 = "abc"; String s3 = s1 + "c"; System.out.println(s3 == s2); // false System.out.println(s3.equals(s2)); // true } // 1.Java 语言提供对字符串串联符号(”+”)和其他对象到字符串的转换的特殊支持。 // 2.字符串串联是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的,字符串转换是通过 toString 方法实现的。 // 3.在本题中,先在常量池中创建”ab“,地址指向s1,再创建”abc”,指向s2。 // 对于s3,先创建StringBuilder(或 StringBuffer)对象,通过append连接得到“abc”,再调用toString()转换得到的地址指向s3,故(s3==s2)为false。
6. 使用String str = new String("hello");会创建几个对象
1.先定义一个名为str引用变量,放入到栈中。
2.然后检查字符串池中是否存在内容为”hello“的对象。
3.不存在,则在堆中创建一个指定的对象,并让str引用指向该对象(然后需要从堆中复制到常量池中,否则导致浪费池的空间)
4.存在,则从常量池中拷贝一份到堆中(然后返回堆中的地址,让栈中的str对象指向该地址,进行关联)
注意:对字符串进行赋值时,如果右操作数含有一个或一个以上的字符串引用时,则会在堆中建立一个字符串对象,返回引用;如String str2=str1+ “abc”;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术