Java里面的String类有些特殊,让很多程序员会感到一丝困惑。
看起来像基本类型,但又不是基本类型。
初始化一个String类型的变量有两种方式。
方式一, String str1 = "java"
;
方式二, String str2 = new String("java")
;
方式一跟基本类型的创建方式一样,比如 int i = 3
;这是大家经常把它当作基本类型的原因,还有另一个原因就是String 变量之间的 +
操作。稍后会提到。
第一种方式,常量字符串初始化对象,它的值存放在字符串常量池中。
第二种方式,构造方法创建字符串,它的值存在堆中。
正是因为String对象存在两种不同存放对象的方式,才会让string对象显的如此特别。
例子1
String str1 = "java"; String str2 = "java"; String str3 = new String("java"); String str4 = new String("java"); //print true System.out.println(str1 == str2); //print false System.out.println(str3 == str4); //print false System.out.println(str1 == str3);
str1
跟 str2
指向同一个内存区域,因为“java”存在常量池中。当jvm发现常量池中存在”java”时,就会把 str2
直接指向’java’不会在新创建。所以返回 true
.
str3
跟 str4
是在内存堆中分别创建了两个不同的对象。所以返回 false
; str1
跟 str3
也返回 false
,因为一个在字符串常量区,一个在堆内存。
例子2
String str = "javajava"; String str5 = str1 + str2; String str6 = "java" + str1; String str7 = "java" + "java"; //print false System.out.println(str5 == str); //print false System.out.println(str6 == str); //print true System.out.println(str7 == str);
String 对象就是这么神奇,支持像基本类型一样的 +
操作,这是java给我们的语法糖。这样也经常让大家误认为它是基本类型。
当对两个String变量的引用进行操作时,无论他们时通过何种方式赋值,都会在堆内存中开辟出一个新的空间存储操作结果。所以 str5 == str
返回 false
.
当字符常量和引用类型操作时,一样会在堆内存中进行操作。所以 str6 == str
返回 false
.
当两个字符常量进行操作时,就可以直接在字符串常量区操作。操作结果如何在常量池已经存在则直接将值赋给引用变量。所以 str7 == str
返回 true
.
例子3
public static final String FINAL_STR1 = "maven_"; public static final String FINAL_STR2 = "jdk"; String finalStr = FINAL_STR1 + FINAL_STR2; String finalStr3 = "maven_jdk"; //print true System.out.println(finalStr == finalStr3);
FINAL_STR1
和 FINAL_STR2
都是用 static final
修饰的,在编译期会编译进字符串常量池,因此 finalStr
也是在编译阶段就编译进了字符串常量池。所以返回 true
例子4
public static final String FINAL_STR1; public static final String FINAL_STR2; static { FINAL_STR1 = "hdfs_"; FINAL_STR2 = "hive"; } String finalStr = FINAL_STR1 + FINAL_STR2; String finalStr4 = "hdfs_hive"; //print false System.out.println(finalStr == finalStr4);
FINAL_STR1
和 FINAL_STR2
在声明的时候没有进行初始化,所以编译阶段不能确定值,所以没有编译进字符常量池。所以返回 false
例子5
String str1 = "java" changeStr(str1); //print java System.out.println(str1); public static void changeStr(String str){ str = "python"; }
String 明明是引用传递,为什么表现出值传递的效果呢?因为String是不可变的。上面的函数调用过程可以理解为
String str1 = "java"; String str = str1; // str2----->str1, 传引用 str = "python"; // str2指向了一个新的字符串,str1并没有变。
例子6
接第一个例子
String str8 = str3.intern(); //print true System.out.println(str1 == str8);
这个 intern()
方法看起来有点儿神奇。String的 intern()
方法会查找在常量池中是否存在一份 equal
相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。它可以手动将字符对象的值转移到字符串常量池中,这样 str1
跟 str8
就指向同样的内存地址了。
总结:String 对象之所以变现的如此特殊,让很多程序员措手不及,最主要的是因为JVM有两个地方存放字符串对象,一个是堆内存,一个是字符串常量池,还有一个特殊之处就是String 是不可变对象。字符串常量池给我们带来这么多困惑,一定也有它存在的意义,它带来的好处就是
节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
节省运行时间:比较字符串时, ==
比 equals()
快。对于两个引用变量,只用 ==
判断引用是否相等,也就可以判断实际值是否相等。