String对象的创建
原理一:当使用任何方式来创建一个字符串对象str时,运行中的JVM虚拟机会拿着这个对象在String Pool(字符串常量池/缓冲区)中找是否存在内容相同的字符串对象,如果不存在,则在池中创建一个字符串str,否则不在池中添加。
原理二:Java中,只要使用new关键字来创建对象,则一定会在堆区或者栈区创建一个新的对象
String str1="helloworld"; String str2=new String("helloworld"); //str1指向的对象放在栈中, //str2指向的对象放在堆中(存放的是字符串"helloworld"在内存中的地址)
原理三:使用指定或者使用纯字符串串联来创建String对象,则仅仅会检查维护String Pool中的字符串,池中没有就在池中创建一个,有就不用。但绝对不会在堆栈区中再去创建String对象
原理四:使用包含变量的表达式来创建String对象,则不仅仅会见检查维护String Pool,而且还会在堆栈区创建一个String对象
在此,有两问:
为什么String类是不可变类?
这样设计String类的好处是什么?
第一个问题,为什么String类不可变?
我先去翻阅了String类的源码,正所谓万物皆可看源码。
从上图可见,String类是被final这个关键字所修饰的,实现了Serializable、Comparable、CharSequence接口
final关键字可以用来修饰引用、方法和类。在这里我不详细介绍final修饰引用和方法的作用,重点在final修饰类后的作用是什么。
- 当用final修饰类时,该类成为最终类,无法被继承,该类就不能被其他类所继承;简称为“断子绝孙类”。当我们需要让一个类永远不被继承,此时就可以用final修饰,但要注意:final类中所有的成员方法都会隐式的定义为final方法。
从上述可知,String类是不能被继承,并且它的成员方法都默认为final方法
、
String类的本质是一个不可变的char数组,String类的值实际上是通过char数组存储的,并且char数组被private和final所修饰,因此字符串一旦创建就不能再修改了,从而保证了引用的不可变和对外的不可见。
扩展:我今天刚学成反射这个内容,既然私有的成员变量可以利用暴力反射成功修改属性,那么String类是否可以通过setAccessible()方法成功被修改呢?
/** * 字符串的测试 * * @author HHLJ * @date 2022/02/28 */ public class StringTest { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { //创建三个字符串,一个用于修改,两个用于比较 String str1 = "阿弥陀佛"; String str2 = "阿弥陀佛"; String str3 = "无量天尊"; //阿弥陀佛 System.out.println(str1); //获取String类中的value字段 Field stringField = str1.getClass().getDeclaredField("value"); //改变stringField的访问权限 stringField.setAccessible(true); //修改str1的数据 stringField.set(str1,new char[]{'无','量','天','尊'}); //无量天尊 System.out.println(str1); //1170471009 System.out.println(str1.hashCode()); //比较 //true System.out.println(str1 == str2); //false System.out.println(str1 == str3); } }
扩展:由此可见,String类并不是完全不可变,我们利用反射绕过了私有权限修改了String底层的char[]数组。但是这种方式不推荐使用,不建议这么使用,这违反了 Java 对 String 类的不可变设计原则,会造成一些安全问题,整整活稍微了解了解就可以了。
第二个问题,这样设计String类的好处是什么?
主要出于这两种原因:安全性、效率
安全性
因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。
String被许多的Java类用来做参数,比如网络连接地址的url,文件路径path,反射机制所需要的String参数等通常都是用String类来保存,如果String对象不是固定不变的话,将会产生很多安全隐患。
效率
字符串不变性保证了HashCode的唯一性,因此可以放心的进行缓存,这也是一种性能优化手段,意味着不必每次都取计算新的哈希值。
只有当字符串是不可变的,字符串池才有可能实现,字符串常量池是java堆内存中一个特殊的存储区域,当创建一个String对象,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。
这样在大量使用字符串的情况下,可以节省内存空间,提高效率,String的不可变性是实现该特性的最基本的一个必要条件。假设内存里的字符串内容可以被我们改来改去,那么这么做就毫无意义可言。