String 类代表字符串。Java程序中的所有字符串文字(例如"abc" )都被实现为此类的实例。用双引号表示。字符串是常量;它们的值在创建之后不能更改。
//被final修饰不可被继承
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
//被final修饰表示为常量
private final char value[];
当我们进入String这个类时,我们可以发现String类被final修饰,而被final修饰的类是不能被继承的。
String底层使用一个字符数组来维护的。从上面的源码可以看到String类定义了一个私有的被final修饰的字符数组。private final char value[]; 它是不可改变的。所以说String是常量不可改变。
方式一:直接赋值
String str1="hello";//直接赋值的方式
方式二:通过构造器
String str2=new String("hello");//实例化的方式
那么这两种创建方式有区别吗?肯定是有区别的,它们在Java虚拟机中的创建过程是不一样的。下面是图解过程:
从上面我们可以看到直接赋值的方式,str1它首先会去方法区的常量池中找,有“hello”这个对象就直接引用,没有就创建一个新的对象“hello”,存放在常量池中。而new的方式首先会在堆中创建一个对象,然后再去常量池中找,有直接引用,没有就创建一个新的对象“hello”。直接赋值创建0或1个对象,而new的方式创建1或2个对象。为了提升Java虚拟机的性能和减少内存的开销,避免字符串的重复创建,尽量少使用new的方式创建String对象。
代码分析
public static void main(String[] args) {
//去找方法区的常量池中找,发现没有对象"hello",
//创造一个对象"hello"假设它的地址为 0x1234
String str1 = "hello";
//在堆中创建一个对象"hello"假设它的地址为 0x5678
//然后引用常量池中的"hello"的地址0x1234
String str2 = new String("hello");
//将str2的地址0x5678 赋给str3
String str3 = str2;
//直接去找方法区的常量池中找,发现有"hello",
//直接引用它的地址0x1234。
String str4 = "hello";
System.out.println(str1==str2);//false
System.out.println(str1==str3);//false
System.out.println(str3==str2);//true
System.out.println(str1==str4);//true
}
参考连接
说明:String中==比较的地址是否相同。equals比较的是内容是否相同。
上面的代码中
方式一:使用+拼接字符串
//常量拼接
String str4="hello"+"word";
//变量拼接
String str2 = "hello";
String str3=str2+"word";
方式二:使用concat方法拼接
String str5 = str2.concat("word");
当然StringBuffer和StringBuilder 的append()方法也能实现拼接,
下面我们来看一下拼接的代码实现
public static void main(String[] args) {
String str1 = "helloword";
String str2 = "hello";
//变量拼接
String str3=str2+"word";
//常量拼接
String str4="hello"+"word";
final String str6 = "hello";
String str7=str6+"word";
//concat方法拼接
String str5 = str2.concat("word");
System.out.println(str1==str3);//false
System.out.println(str1==str4);//true
System.out.println(str1==str5);//false
System.out.println(str1==str7);//true
}
为什么拼接后同样是helloword,有些相等有些不等呢?让我们来分析一下源码。
常量拼接
常量拼接和直接赋值其实差不多,拼接成 "helloword"后它首先会去方法区的常量池中找,有 "helloword"直接引用,没有就创建一个 “helloword”,存放在常量池中,然后引用。
注意:被final修饰的变量是常量,属于常量拼接。
final String str6 = "hello";
String str7=str6+"word";
变量拼接
通过debug我们可以发现,它首先调用了StringBuilder的构造器,创建StringBuilder实例
public StringBuilder() {
super(16);
}
然后调用StringBuilder的append方法,追加"hello"和"word"
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
最后调用StringBuilder的toString方法,返回new出来的String
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
通过上面的代码我们可以看出,变量拼接最终调用的是StringBuilder的toString方法,而它返回的是new出来的String对象。而new的方式首先会在堆中创建一个对象,然后再去常量池中找,有直接引用,没有就创建一个新的对象。
concat方法拼接
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);
}
通过源码我们也可以看到,经过concat方法,其实是new了一个新的String和上面的变量拼接是一个道理。