public class TestClass {
public static void main( String[] args ) {
String s = new String("AaAa");
}
}
问题:以上代码创建了几个对象?
String类特性
1. String类被final修饰,不可以被子类继承
2. String类中value属性被final修饰,value的值不能被修改
3. value属性被private修饰,此属性只能在当前类被访问
以上决定了String对象是不可变的,所以每次使用都是创建新的对象
String创建的方式
1. 字面量赋值
String s = "AaAa";
2. 使用构造器创建
String s = new String("AaAa");
两者的区别:
第一种:创建对象时,会去堆的字符串常量池中查找是否已经存在数据,是直接返回,否重新创建
第二种:创建一个新的对象,并将传入的对象浅拷贝到此对象中
String赋值原理
String s = new String("AaAa");
字节码分析
Java虚拟机在创建s对象时的字节码文件如下:
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String AaAa
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: return
- new 创建了一个字符串对象,此时这个字符串对象中,只有基本类型的属性有默认值。存放字符串数据的 value 属性是 null
- ldc 去常量池中查找字符串是否存在,存在直接返回,否则创建之后再返回
- invokespecial 调用构造函数,最终得到一个完整的对象
由此可见:String s = new String("AaAa") 会创建两个字符串对象。
一个对象是创建之后,常量池会保存这个对象的引用(地址)。
一个对象时创建之后,变量s会保存这个对象的引用(地址)。
那么真正存放字符串数据的value数组创建了几个呢?
答案是:一个
创建对象堆栈结构
第一步:创建的是指向变量s的String对象1,对象存放到堆中。
第二步:判断"AaAa"字符串在常量池中是否存在。不存在,就新建了一个String对象2。
此时创建的对象,value数组中是有值的,值也是存在堆中。数据对象的地址
指向了String对象2中的value。并且将String对象2的地址存放到了字符串常量池中。
第三步:调用String对象1的构造方法,将String对象2的value值(指向数组的地址)拷贝到
String对象1中。到此得到了一个完整的String对象1
String构造方法
String类中提供了很多构造方法
//传入参数是String对象
public String(String original) {
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}
//传入参数是char数组(拷贝value数组内容,String对象中是新的数组。原数据修改不影响String)
public String(char value[]) {
this(value, 0, value.length, null);
}
//传入参数是byte数组(拷贝bytes数组内容,不传解码字符集,按照默认默认字符集解码。可能影响内容)
public String(byte[] bytes) {
this(bytes, 0, bytes.length);
}
//传入参数是StringBuffer对象,后续StringBuffer操作不会影响当前String对象(拷贝的是数组内容)
public String(StringBuffer buffer) {
this(buffer.toString());
}
//和上一个类似
public String(StringBuilder builder) {
this(builder, null);
}
推荐使用字符串字面量创建字符串对象,避免对象冗余