字符串对象在JVM中可能有两个存放的位置:字符串常量池或堆内存。
-
使用常量字符串初始化的字符串对象,它的值存放在字符串常量池中;
-
使用字符串构造方法创建的字符串对象,它的值存放在堆内存中;
String提供了一个API, java.lang.String.intern(),这个API可以手动将一个字符串对象的值转移到字符串常量池中。
在1.7之前,字符串常量池是在PermGen区域,这个区域的大小是固定的——不能在运行时根据需要扩大,也不能被垃圾收集器回收,因此如果程序中有太多的字符串调用了intern方法的话,就可能造成OOM。
在1.7以后,字符串常量池移到了堆内存中,并且可以被垃圾收集器回收,这个改动降低了字符串常量池OOM的风险。
案例分析
1. String对象的两种创建方式:
String s1="javaadu";
String s2="javaadu";
String s3=new String("javaadu");
System.out.println(s1==s2); //true
System.out.println(s1==s3); //false
String s4=s3.intern();
System.out.println(s1==s4); //true
System.out.println(s3==s4); //false
-
直接使用双引号声明出来的String对象会直接存储在常量池中。
-
如果不是用双引号声明的String对象,可以使用String提供的intern方法。String.intern() 是一个Native方法,它的作用是:如果常量池中已经包含一个等于此String对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
2. String字符串拼接
String str1 = "str";
String str2 = "ing";
String str3 ="str"+"ing"; //常量池中的对象
String str4 = str1 + str2; //在堆上创建的新对象
String str5 = "string";
System.out.println(str3 == str4); //flase
System.out.println(str3 == str5); //true
System.out.println(str4 == str5); //false
String s1 = new String("abc");这句话创建了几个对象?
创建了两个对象。或者一个
如果如果常量池没有,就是两个;如果常量池已经有,就是一个。
String str1=new String("abc");
String str2="abc";
System.out.println(str1==str2); //false
解释:
先有字符串"abc"放入常量池,然后 new 了一份字符串"abc"放入Java堆(字符串常量"abc"在编译期就已经确定放入常量池,而 Java 堆上的"abc"是在运行期初始化阶段才确定),然后 Java 栈的 str1 指向Java堆上的"abc"。
3. 8种基本类型的包装类和常量池
-
Java 基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。
-
两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。
Integer aInteger=1;
Integer bInteger=2;
Integer cInteger=3;
Integer dInteger=3;
Integer eInteger=321;
Integer fInteger=321;
Long gLong=3L;
Long hLong=2L;
System.out.println(cInteger==dInteger); //true
System.out.println(eInteger==fInteger); //false
System.out.println(cInteger==(aInteger+bInteger)); //true
System.out.println(cInteger.equals(aInteger+bInteger)); //true
System.out.println(gLong==(aInteger+bInteger)); //true
System.out.println(gLong.equals(aInteger+bInteger)); //false
System.out.println(gLong.equals(aInteger+hLong)); //true
使用==:
①如果比较Integer变量,默认比较的是地址值。
②Java的Integer维护了从-128~127的缓存池。
③如果比较的某一边有操作表达式(如a+b),那么比较的是具体数值。
使用equals():
①无论Integer还是Long中的equals默认比较的是数值。
②Long的equals()方法,JDK的默认实现:会判断是否是Long类型。
int a=59; //基本类型,存储在栈中。
Integer bInteger=59; //会调用Integer的valueOf方法
/*
* valueOf这个方法就是返回一个Integer对象,返回之前,需要判断当前值是否在[-128,127]区间。
* 如果在此区间,先看IntegerCache中是否存在此对象,若存在,直接返回引用,否则创建一个新的对象。
* 若不在此区间,return new Integer(i);
*/
Integer cInteger=Integer.valueOf(59); //因为IntegerCache中存在此对象,所以直接返回引用。
Integer dInteger=new Integer(59); //直接创建一个新的对象。
System.out.println(a==bInteger); //true Integer会自动拆箱成int,然后进行值比较
System.out.println(bInteger==cInteger); //true
System.out.println(cInteger==dInteger); //false
System.out.println(a==dInteger); //true dInteger会自动拆箱,进行值比较
注意:
==比较的是地址。
但当为基本类型时,比较的是值。
如果两边有包装类型,则先将包装类型转换为基本类型再比较值是否相等。