String str=new String("aaa"); 函数
这行代码究竟建立了几个String对象呢?答案是2个,而不是3个。因为new String("aaa")至关于"aaa"与一个就是建立出来的放在堆时原实例对象,而另外一个就是放在常量池中的 "aaa" 对象,固然这里的str自己只是一个引用,放在栈里,用来指向堆中建立出来的对象。
常量池(constant pool)指的是在编译期被肯定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。
测试
String str="aaa"; 操作系统
只建立1个对象。这里涉及到字符串常量池,在JVM中有一个字符串池,它用来保存不少能够被共享的String对象,这样若是咱们在使用一样字面字符串时,它就使用字符串池中同字面的字符串。固然咱们可使用String对象的intern()方法来访问String对象在字符串池中所对应的常量对象。orm
上面这行代码被执行的时候,JVM先到字符串池中查找,看是否已经存在值为"aaa"的对象,若是存在,则再也不建立新的对象,直接返回已存在对象的引用;若是不存在,则先建立这个对象,而后把它加入到字符串池中,再将它的引用返回。
对象
String str1="aaa"; 内存
String str2="aaa"; 文档
也只建立1个对象。能过上面的解释这个就更清楚了,在执行第二行代码时,aaa字符串对象在池中已存在,因此直接返回池中已存在的那个字符串对象。
String str="aaa"+"bbb";
仍是只建立1个对象。因为常量字符串是在编译的时候就也被肯定的,又因"aaa"和"bbb"都是常量,所以变量str的值在编译时就能够肯定。这行代码编译后的与String str="aaabbb";是同样的,这与咱们平时好像不太同样啊?通常使用“+”链接两个字符串都会产生另外一个新的字符对象。下面咱们看一下例子就明白了:
String str1 = "aaa";
String str2 = "bbb";
String str3 = "aaabbb";
String str4 = "aaa" + "bbb";//不会产生新的字符串对象
System.out.println(str3 == str4);//true
str4 = str1 + "bbb";//会产生新的字符串对象
System.out.println(str3 == str4);//false
str4 = str1 + str2;//会产生新的字符串对象
System.out.println(str3 == str4);//false
String str5=str1+"bbb";
System.out.println(str5==str4);//false
从上面例子咱们就能够得出:使用“+”链接的两个字符串自己就是字面常量字符串时,若是池中存在这样链接后的字符串,则是不会从新建立对象,而是直接引用池中的字符串对象;若是“+”链接的两字符串中只要有一个不是字面常量串(即定义过的),是会产生新的字符串对象。
凡事也有例外,这个也不例外:若是“+”链接的字符串中两个或一个不是“字面常量”,但若是定义成常量字符串时,状况又有变化:
final String str1 = "aaa";
final String str2 = "bbb";
String str3 = "aaabbb";
/*
* 由于str1与str2都定义成了常量,因此编译时就能肯定,编译时就会将常量替换,等同于
* str4 = "aaa"+"bbb",所以不产生新对象
*/
String str4 = str1 + str2;
System.out.println(str3 == str4);//true
但若是先定义final字符串,但未在定义处初始化,而初始化在块中,以下:
//此时str1与str2至关于变量,而不是常,由于块是在运行时才能肯定,在编译时不能肯定
final static String str1;
final static String str2;
static {
str1 ="aaa";
str2 ="bbb";
}
public static void main(String[] args){
String str3 = str1 + str2;
String str4 ="aaabbb";
System.out.println(str3==str4); //输出为false
}
String str=" ";与String str=new String();
str=" "会放入池中,但new String()不会放入池中。
String的intern()方法
“当调用 intern 方法时,若是池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法肯定),则返回池中的字符串;不然,将此 String 对象添加到池中,而且返回此 String 对象的引用。它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true”,这是jdk文档原文注解。
如今咱们回到最开头的那个例子,为何String str=new String("aaa");会产生2个对象?一个是"aaa"又做为字符串构造函数的参数,但"aaa"本身就是一个字符串,在传进构造函数前就已建立了一个字符对象,实质上与就比如是第二个实例:String str="aaa"; ,它建立的字符串对象会放入到池中,而且引用的也是池中的那个字符串对象;另外一个就是经过new String()构造函数建立的。因此new String("aaa")会产生两个对象,也就是说经过此种方式建立字符串对象时,会先将字符串参数对象放入对象池,而后另外建立一个字符串对象。
理解完new String("aaa")为何会产生两个对象,咱们再来看看new String(char value[])这样的方式建立时,又会不会把字符串对象放入到池中呢?答案是不会的。由于传递给构造函数的是一个字符数组,而不是像前面传递的是一个字面常量字符串参数那样,将字符串参数自己放入池中。那么咱们如今若是证实new String(char value[])未将字符串对象放入池中,咱们能够写一个简单的测试,运行时打开XP的任务管理器,查看操做系统的内存使用状况就能够初步确认:
int size = 10000000;
char c[] = new char[size];
for (int i = 0; i < size; i++) {
c[i] = 'a';
}
//使用带字符数组参数构造函数建立字符串时,字符串对象不会放入字符串池
String str1 = new String(c);
System.out.println("String字符串对象建立完毕...");
Thread.sleep(5000);
str1.intern();//到里会看见内存增长
System.out.println("第一次调用intern()完毕...");
Thread.sleep(5000);
str1.intern();//再过5秒将看不到内存增加,由于池中有了,不会再放入,因此内存无变化
System.out.println("第二次调用intern()完毕...");
Thread.sleep(5000);
因此建立字符串对象放入并放入池中有二种方式:第一种就是直接使用字面常量定义字符串时,如 String str="aaa"; ,str会引用放入池中的对象;第二种就是使用带字符串参数的字符串构造函数,而且此时传入的参数值要是字符串常量形式,而不能是变量的形式,也就是说只能是 String str=new String("aaa");形式,而不能是先定义 String s = "aaa",而后再使用 String str=new String(s);来建立对象,new String(s);此时只建立一个对象,但若是池中不存在时咱们可使用intern方法将它放入池中。固然上面放入池中的前提是池中还不存在这些字符串对象。其实,发现放入池中只实质上只存在一种时机,那就是:直接使用字面常量字符串时。上面所说的两种时机实质上就是直接使用了字面常的字符串而将其放入池中的。上面多处提到了JVM中的堆栈,下面小结一下各自原做用:栈用来保存基本类型与对象的引用的,基本型在建立前会查看Stack中是否已经有, 有则指向, 没有则建立。String内部是以字符串数组来存储字符串的,所以能够认为与char[]等同, String a= "abc",首先在Heap中创一个对象,再到Stack中找char[]是否存在,有则指向该地址, 无则在Stack中建立数组。new出来的都是在Heap中,堆是用于存储对象的。