JAVA常量池和在基本数据类型包装器类及String的应用
对于基本数据类型,java内有封装的包装器类,分别对应8个基本数据类型,Boolean,Byte,Short,Integer,Long,Character,Float和Double。
其中,除了Float和Double,其他6个类型都具有常量池,即被赋值为一个常量后,只在栈中存一份,共享访问。
常量池存在一定的赋值数据限制:
Boolean:true,false
Byte:-127~128
Character:0~127
Short,Integer,Long:-128~127
Float,Double:无常量池
我们看如下代码:
Integer i1 = 10;
Integer i2 = new Integer(10);
Integer i3 = 10;
Integer i4 = new Integer(10);
int i5 = 10;
Java中,当定义一个类对象,并赋值给它,意思是在内存中开辟一段空间,然后让这个类对象作为引用指向这个空间。
我们知道,用new来构造,是在堆里建立一个内存空间,然后类引用类型指向这个空间,每个空间是相互独立的,即i2和i4,指向两个不同空间。每new一个对象,都会新在堆上开辟一个内存空间,然后引用类型指向它。
而常量池的概念,就是满足一定条件的赋值方式,如i1和i3,会共享指向同一个常量栈空间。以后对再多的包装器类赋值同样的常量,都会指向这同一个空间。这样的作用是一定程度地减少类对象的创建和销毁。
i5直接就是一个值,不是一个类,不是引用类型,按值比较。
我们可以理解为如下图(手画略丑):
图中,10外面一个方框代表对10进行封装成的包装器类Integer的一个内存块。对一个包装器类,新new一个10,则会多一个相互独立的内存块在堆里,而新等号赋值一个10,则多一个引用类型指向栈中那同一个内存块。
可以看出,
i1 == i3, true;
i2 == i4, false;
i3 == i4, false;
i1 == i5,ture;(这里比较的是值,看下文)
需要注意的是,基本变量类,在构造的时候,会被装箱成类,在运算的时候(包括==),会被开箱成数字,而类和类的==比较,比较的是地址,类和基本类型的比较,比较的是值,因为类会开箱成基本类型。
我们可以看下一段代码
import java.util.*;
public class Practice {
public static void main(String[] args) {
int i1 = 10;
Integer i2 = 10;
Integer i3 = 10;
Integer i4 = new Integer(10);
Integer i5 = new Integer(5);
Integer i6 = new Integer(5);
Integer i7 = 5;
System.out.println(i1 == i2);
System.out.println(i2 == i3);
System.out.println(i3 == i4);
System.out.println(i3 == i5 + i6);
System.out.println(i4 == i5 + i6);
System.out.println(i5 == i6);
System.out.println(i4 == i7 + i5);
}
}
得到的结果是
true
true
false
true
true
false
true
总结下来,我们也可以得到三条重要规则:
- 一旦参与运算,包装器类就会开箱,用值参与运算;
- 运算式中只要有值相加减乘除等等,那么就用值参与运算比较;
- 运算式中如果直接比较引用,没有值参与,那就是用地址参与比较。
但是值得注意的是,如果是两个类进行运算得到了另一个类,把另一个类进行比较,那还是比较的是地址,可以看如下代码:
import java.util.*;
public class Practice {
public static void main(String[] args) {
String s = "abc";
String s1 = new String("def");
String s2 = "abcdef";
String s3 = s + s1;
String s4 = "def";
String s5 = s + s4;
String s6 = "abc" + "def";
System.out.println(s + s1 == s2);
System.out.println(s + s1 == s3);
System.out.println(s + new String("def") == s3);
System.out.println(s5 == s2);
System.out.println(s6 == s2);
}
}
运行结果是
false
false
false
false
true
其中,对String有两种赋值方法,一种是直接赋值一个常量,栈分配内存;另一种是new一个String,堆分配内存。
从这里我们可以看出:
- 如s3,s5,以及第一和第二个输出语句中,只要赋值语句中涉及到的包括不是常量的量,比如一个String类实例,或者直接一个new,这些编译器不会帮忙优化,所以比较的还是地址。
- 如s6,s2这种,赋值直接是用常量赋值的,编译器则会帮忙用常量池进行优化,所以指向的是同一个地址。
- 这里我们比较的是String类引用类型,所以这段代码里面比较的所有都是地址,不是值比较,最后一个是true的原因是两个地址相同,而不是因为值相同。