String类对象的实例化有两种方式:采用直接赋值实例化和使用构造方法实例化。
1. 直接赋值实例化String类对象的情况
直接赋值实例化只会开辟一块堆内存空间,并且利用直接赋值实例化还可以实现堆内存空间的重用,即采用直接赋值的方式,在内容相同的情况下不会开辟新的内存空间,而会直接指向已有的堆内存空间。
范例:直接赋值的堆内存自动引用。
public class StringDemo {
public static void main(String[] args) {
String str1 = "hello"; //直接赋值
String str2 = "hello"; //直接赋值
String str3 = str2; //对象赋值
String str4 = "world";
System.out.println(str1==str2);
System.out.println(str2==str3);
System.out.println(str1==str4);
}
}
执行结果: true
true
false
下图是直接赋值自动引用的内存分配图;
2. 构造方法实例化String类对象的情况
如果要使用String类的构造方法尽心String类对象的实例化操作,那么一定要使用使用关键字new,而每当使用关键字 new 就代表着开辟新的堆内存空间,这块堆内存空间的内容就是传入到构造方法中的字符串数据。
String str = new String("hello");
我们知道每一个字符串常量都是String类的匿名对象,所以上方代码的含义是,根据 " hello " 这个匿名对象的内容创建一个新的String类对象,所以此时的内存关系如下;
因为每一个字符串都是一个 String 类的匿名对象,所以会首先在堆内存中开辟一块空间保存字符串 “hello” ,然后使用关键字 new ,开辟另一块堆内存空间。因此真正使用的是关键字 new 开辟的堆内存,而之前定义的字符串常量的堆内存空间将不会有任何的栈内存指向,将成为垃圾,等待被GC回收。所以,使用构造方法的方式开辟的字符串对象,实际上会开辟两块空间,其中一块空间将会成为垃圾。
除了内存空间的浪费外,如果使用构造方法实例化 String 类对象,由于关键字 new 永远表示开辟新的堆内存空间,所以其内容不会自动保存到对象池中。
范例:不自动保存到对象池操作。
public class Demo{
public ststic void main(String[] args){
String str1 = new String("hello"); //使用构造方法定义新的堆内存空间,不会自动入池
String str2 = "hello"; //直接赋值
System.out.println(str1==str2); //判断结果:false
}
}
运行结果: false
本程序首先利用构造方法开辟了一个新的 String 类对象,由于此时不会自动保存到对象池中,所以在使用直接赋值的方式声明 String 类对象后将开辟新的堆内存空间,因为两个堆内存空间的地址不同,所以最终的结果为false。
如果希望开辟的新内存数据也可以进行对象池的保存,那么可以采用String 类定义的一个手工入池的操作。保存到对象池的语法如下:
public String intern();
范例:手工入池
public class StringDemo{
public static void main(String[] args){
String str1 = new String("hello").intern(); //使用构造方法定义新的内存空间,手动入池
String str2 = "hello"; //直接赋值
System.out.println(str1 == str2); //判断结果:true
}
}
运行结果: true
该程序由于使用了 String 类的 intern()方法,所以会将指定的字符串对象保存在对象池中,随后如果使用直接赋值的形式将会自动引用已有的堆内存空间所以地址判断的结果为 true。
总结:String 类的两种对象实例化的区别。
. 直接赋值(String str = "字符串"; ): 只会开辟一块堆内存空间,并会自动保存在对象池中供下次重复使用;
. 构造方法(String str = new String("字符串");):会开辟两块堆内存空间,其中有一块空间将成为垃圾,并且不会自动入池,但是可以通过使用 intern()方法手工入池。
String类字符串一旦定义就不可改变
public class Demo{
public static void main(String[] args){
String str = "hello"; //直接赋值实例化String类对象
str = str + "world"; //字符串连接,同时修改String类对象的引用关系
str += "!!!"; //字符串连接,同时修改String类对象的引用关系
System.out.println(str);
}
}
执行结果: helloworld!!!
该程序首先声明了一个String类对象,然后修改了两次String类对象的内容(注意:实际上是发生了两次引用改变),所以最终String类对象的内容就是 “helloworld!!!” 。但是整个操作过程中,只是String类的对象的引用发生了改变,而字符串本身并没有发生改变。
通过上图可以发现,在进行String 类对象内容修改时,实际上原始的字符串都没有发生变化(最终没有引用的堆内存空间将成为垃圾空间), 而改变的只是String 类对象的引用关系。所以可以得出结论,字符串一旦定义那么就不可改变。