Java里面的String类有些特殊,让很多程序员会感到一丝困惑。
看起来像基本类型,但又不是基本类型。
初始化一个String类型的变量有两种方式。
方式一,String str1 = "java"
;
方式二,String str2 = new String("java")
;
方式一跟基本类型的创建方式一样,比如 int i = 3
;这是大家经常把它当作基本类型的原因,还有另一个原因就是String 变量之间的+
操作。稍后会提到。
第一种方式,常量字符串初始化对象,它的值存放在字符串常量池中。
第二种方式,构造方法创建字符串,它的值存在堆中。
正是因为String对象存在两种不同存放对象的方式,才会让string对象显的如此特别。
例子11
2
3
4
5
6
7
8
9
10String 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
,因为一个在字符串常量区,一个在堆内存。
例子21
2
3
4
5
6
7
8
9
10String 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
.
例子31
2
3
4
5
6public 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
例子41
2
3
4
5
6
7
8
9
10public 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
例子51
2
3
4
5
6
7String str1 = "java"
changeStr(str1);
//print java
System.out.println(str1);
public static void changeStr(String str){
str = "python";
}
String 明明是引用传递,为什么表现出值传递的效果呢?因为String是不可变的。上面的函数调用过程可以理解为1
2
3String str1 = "java";
String str = str1; // str2----->str1, 传引用
str = "python"; // str2指向了一个新的字符串,str1并没有变。
例子6
接第一个例子1
2
3String str8 = str3.intern();
//print true
System.out.println(str1 == str8);
这个intern()
方法看起来有点儿神奇。String的intern()
方法会查找在常量池中是否存在一份equal
相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。它可以手动将字符对象的值转移到字符串常量池中,这样str1
跟str8
就指向同样的内存地址了。
总结:String 对象之所以变现的如此特殊,让很多程序员措手不及,最主要的是因为JVM有两个地方存放字符串对象,一个是堆内存,一个是字符串常量池,还有一个特殊之处就是String 是不可变对象。字符串常量池给我们带来这么多困惑,一定也有它存在的意义,它带来的好处就是
节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
节省运行时间:比较字符串时,==
比equals()
快。对于两个引用变量,只用==
判断引用是否相等,也就可以判断实际值是否相等。