最近对String有一些好奇觉得它十分有趣,会涉及到什么常量池呀,堆内存呀,还有它复写的equals方法。现在让我们对String来一个初步了解吧。
1.String(字符串)有三种创建方式:一是通过 " " 来进行创建叫直接定义,如String str = "hello";这样创建的字符串会在常量池中创建,时期是在编译期间;二是通过new的方式来创建,如String str = new String("hello");这么一来就会在堆内存中开辟空间,并且也会在常量池中创建,时期是在运行期间;三是串联生成,即:String s1 = “haha” + “xixi”;
2.常量池(constant pool)指的是在编译期被确定,并被保存在已编译的。class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。
3.用new String() 创建的字符串不是常量它算是对象的引用,不能在编译期就确定,它们有自己的地址空间。
4.Java对String类型重载了+操作符,可以直接使用+对两个字符串进行连接。而常量的连接结果还是在常量池当中,而引用和常量的连接结果会放在堆内存中。
5.String.intern():存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用。当调用intern方法时,如果String Pool中已经包含一个等于此String对象的字符串(用 equals(Object)方法确定),则返回池中的字符串.否则,将此String对象添加到池中,并返回此String对象在String Pool中的引用。
6.在String当中复写了Object当中的equals方法,使得在String中equals比较的是两个字符串的内容,而 == 比较的是两个字符串的地址值。
上代码:
public class TestString {
public static void main(String[] args) {
String s1 = "abc"; // 常量池
String s2 = new String("abc"); // 常量池 堆内存
System.out.println(s1 == s2); // 两者地址不同所以false
System.out.println(s1.equals(s2)); // 两者内容相同所以true
String s3 = "haha";
String s4 = s3 + "xixi";
String s5 = "hahaxixi";
// 因为s3是变量 所以s3+"xixi"会在堆内存中创建,而字符串s5是常量会在常量池中创建,因此比较结果是false
System.out.println(s4 == s5); // false
System.out.println(s4.equals(s5)); // true 两者内容相同所以为true
/*
* s4其实没有存放到常量池中,而是存放到堆里面去了。放在常量池中需要在赋值的时候像s3两边都是双引号,或者是像String str =
* "a" + "b" + "c" + "d"; 一样常量相加 更或者是将s3定义为final类型的值, final String s3 =
* "haha";
*/
final String s6 = "haha";
String s7 = s6 + "xixi";
System.out.println(s5 == s7); // true
String str = "a" + "b" + "c" + "d";
String str2 = "abcd";
/*
* 这里面的str和str2是一样的 反编译时 编译器会将String str = "a" + "b" + "c" +
* "d";转变为String str = "abcd"; 因为两者在常量池中引用对象相同,所以会返回为true
*/
System.out.println(str == str2); // true
}
}
当fina修饰String类型时,就会在编译期间能知道它的确切值,编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。
在上面由于变量s6被final修饰,因此会被当做编译器常量,所以在使用到s6的地方会直接将变量s6替换为它的值。而对于变量s3的访问却需要在运行时通过链接来进行。
通过反编译可以看出final修饰的字符串haha和xixi在编译时期转变为了hahaxixi
而String str = "a" + "b" + "c" + "d";也转变为了 String str7 = "abcd"; 由此很容易加深对String的理解。
补充:
public class Test {
public static void main(String[] args) {
String str = "abcd";
String str2 = str.intern();
System.out.println(str==str2); // true
}
}
intern这个方法是把常量池中的str的引用赋给str2所以结果是false。
因此 我们从上面可以看出String既可以作为一个对象来使用,又可以作为一个基本类型来使用。
针对String作为一个基本类型来使用:
1.如果String作为一个基本类型来使用,那么我们视此String对象是String常量池所拥有的。
2.如果String作为一个基本类型来使用,并且此时String常量池内不存在与其指定值相同的String对象,那么此时虚拟机将为此创建新的String对象,并存放在String常量池内。
3.如果String作为一个基本类型来使用,并且此时String常量池内存在与其指定值相同的String对象,那么此时虚拟机将不为此创建新的String对象,而直接返回已存在的String对象的引用。
针对String作为一个对象来使用:
如果String作为一个对象来使用,那么虚拟机将为此创建一个新的String对象,即为此对象分配一块新的内存堆,并且它并不是String常量池所拥有的,即它是独立的。
String在jdk中定义时是public final 所以会有很多人认为String一旦定义 内容就是常量,不可发成改变,其实这是中误解,不可改变的其实是String创建的引用对象,而不是指对象中的内容。