在JVM中,会单独划分一块内存给String。字符串的分配需要消耗高昂的时间和空间,且使用又比较频繁。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行优化:使用字符串常量池。每当创建字符串常量时,JVM会先检查字符串常量池,如果存在,则返回引用地址;如果不存在,就会实例化该字符串并且放到常量池中。由于String字符串的不可变性,常量池中不会出现两个相同的字符串。
如果在常量池中,“abc”如果存在,就直接把“abc”对象的引用地址赋给str。找不到则创建对象“abc”,再把地址赋给str。
Object obj = new Object();
平常都会说创建了obj对象,事实上obj只是一个变量,变量里保存了Object对象的引用地址。
引用类型声明的变量是指该变量在内存中实际存储的一个引用地址,实体在堆中。
所以说变量是可以变的,但是实体对象不能变,即str是可变的,但“abc”不可变。
那么,String为什么是不可变的?
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private int hash; // Default to 0
public String() {
this.value = new char[0];
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
...
}
由源码可知:
- String类是final修饰的;
- String存储内容是char数组;
- char数组是final修饰的。
final关键字
1.修饰类:则类不可被继承,类中的成员变量可以按需求设为final,类中的方法隐式被指定为final;
2.修饰方法:不能重写(但可被重载几个final修饰的方法)。这里需注意:重写的前提是子类可继承父类此方法,若final修饰的方法同时也是private的,则子类中定义了同样的方法名和参数,则不产生重写与final的冲突,也就是说该方法属于子类,与父类没有关系。(类的private方法,隐式的被指定为final方法)
3.修饰基本数据类型:表示该基本数据类型的值一旦被初始化后就不能被改变。
4.修饰引用类型:初始化后不能再指向其他对象(引用地址不可变),但对象的内容可以发生改变。
5.修饰成员变量(属性):必须要显示初始化。(两种初始化方式:a.申明的时候赋值;b.在其类的所有构造器中都为其赋值。)。
String的其它方法
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
//返回新的对象
return new String(buf, true);
}
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
String的方法都不是在原有的字符串上进行的,而是重新生成一个新的字符串对象。即原始字符串是不改变的。
结论:
- String对象一旦被创建就是固定不变的,对于String的任何改变都不会影响到原对象,相关的任何变化性的操作都会生成新的对象。
- String对象每次有变化性操作(有变化的情况)的时候,都会new一个String对象。
分析:
String str = new String("abc");
首先,new一个对象在堆中,将new String("abc")的对象的引用地址赋值给变量str。先去常量池查找“abc”是否存在。若存在,直接放引用地址;若不存在,创建“abc”对象,并将引用地址赋给String的有参构造里。
答案:
如果常量池中存在,则只需创建一个对象,否则需要创建两个对象。
网友评论