1、什么是常量池技术
Java中的常量值技术是为了方便快捷的创建某些对象而出现的,当需要一个对象时,就
可以从池中取一个出来(如果没有则创建一个,创建一个比引用一个要耗时),则在需要重
复创建相等变量时节省了好多时间,常量池也就是一个内存空间,常量池存在于方法区中;
【关于常量池的修改问题】
常量池中存放的对象不能通过外界进行修改,所以Java中不允许对常量进行修改;
2、那些包装类实现了常量池
实现了常量池的包装类:Byte、Short、Character、Integer、Long、
Boolean(六个)
未实现常量池的包装类:
类型;
String引用类型:也实现了常量池;
3、常量池维护的范围
上述几种基本数据类型的包装类实现了常量池技术,但它们维护的常量仅仅是【-128-
127】这个范围的常量,如果出了这个范围,就会从堆中创建对象,不再从常量池中取;
4、 常量池技术举例
F
l
o
a
t
、
D
o
u
b
l
e
①:i = i0,基本数据类型,存储在栈中,栈中的数据可以共享;
②:常量池技术
③:Java的数学运算都是在栈中进行的,Java自动对 i2 和 i3进行拆箱操作转换为整形;
④:i4和i5是引用类型,在栈中存储指针,但是它们是new出来的,因此不再从常量池中寻
找数据,而是从堆中各自new一个对象,地址值肯定不一样;
⑤:加法运算,自动拆箱,同理3;
⑥:浮点型没有实现常量池技术,因此Double d1 = 1.0相当于Double d1 = new
Double(1.0);是从堆上new出来的,d2同理,地址不一样;
注意:
(1)常量池的维护范围
上述几种基本数据类型的包装类实现了常量池技术,但它们维护的常量仅仅是【-128-
127】这个范围的常量,如果出了这个范围,就会从堆中创建对象,不再从常量池中取;
(看下面的源码)
(2)String特殊的常量池技术
String类型也实现了常量池技术,但稍微有点不同,String类型是先检测常量池中有没
有对应的字符串,如果有则取出来,如果没有,则把当前的添加进去;
(3)误区
只有基本数据类型和对应的包装类型进行比较的时候,才会发生自
动的拆箱进行比较,两个包装类型会遵从上述的比较规格,不会发生自
动的拆箱;
而equals,是Object类的方法,Object是所有对象的父类,如果要
比较的对象没有重新equals方法,就会调用Object的方法,此方法如
下:
直接进行地址的比较,所有如果要比较的对象没有重新equals方
法,相当于==比较方式,直接比较地址;而包装类型都重写了这个方
法;
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2); //true
System.out.println(s1.equals(s2)); //true
【解析】
(1)我们知道,==比较两个引用类型的时候,比较的是地址值,而,equals只能比较
引用类型,如果此引用类型未重写equals方法,比较的也是地址值,若重写了,那么按照各
自重写的特性,string重写了此方法;比较的字符序列,区分大小写;最后一个为TRUE;
(2)关于常量池:abc作为常量,当常量进入常量池之前,先会判断常量池中有无该常
量,如果有,就不会创建新常量,反之,所以上面两个常量是常量池中的同一个常量,具有
相同的地址,所以为true;
String s1 = new String("abc"); //2个
【解析】
(1)abc是常量池中的对象,把此常量有用来初始化新的new出来的string,所以s1是
指向堆内存的对象引用;
String s1 = new String("abc");
String s2 = "abc";
System.out.println(s1 == s2); //false
System.out.println(s1.equals(s2)); //true
【解析】
(1)abc在常量池中,S1在堆内存中,地址值不同,但是字符序列形同;
String s1 = "a" + "b" + "c";
String s2 = "abc";
System.out.println(s1 == s2); //true
System.out.println(s1.equals(s2)); //true
【解析】
(1)常量优化机制:"a" + "b" + "c"被优化为"abc"进入常量池,而后"abc"进池时判
断已有,公用之;
1
.
判
断
定
义
为
S
t
r
i
n
g
类
型
的
s
1
和
s
2
是
否
相
等
2
.
下
面
这
句
话
在
内
存
中
创
建
了
几
个
对
象
?
3
.
判
断
定
义
为
S
t
r
i
n
g
类
型
的
s
1
和
s
2
是
否
相
等
4
.
判
断
定
义
为
S
t
r
i
n
g
类
型
的
s
1
和
s
2
是
否
相
等
String s1 = "ab";
String s2 = "abc";
String s3 = s1 + "c"; //常量优化机制只能优化都是常量的值,s1是变量,不能
优化,使用stringbuffer有重新创建了一个对象,地址不同;
System.out.println(s3 == s2); //false
System.out.println(s3.equals(s2)); //true
【解析】
(1)Java 语言提供对字符串串联符号("+")以及将其他对象转换为字符串的特殊支
持。字符串串联是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的。字
符串转换是通过 toString 方法实现的;
s3 = s1 + "c"先通过StringBuilder类的相应方法转换StringBuilder类,再由
tostring转换成string,这是在堆内存中完成的;地址不一样;
(2)第二个判断字符序列,相等为true;
5
.
判
断
定
义
为
S
t
r
i
n
g
类
型
的
s
1
和
s
2
是
否
相
等
一、特殊的String类型
1、String对象两种不同的存储位置:
创建String对象的方法有两种:
String s2 = "我在那"; ----------------①
String s1 = new String("我是谁");---------------②
①:在编译期已经创建好(即用双引号定义的),存储在常量池中;
②:在运行期分配空间(new出来),存储在堆中(常量池是否有看情况)
--------------------------------------------------------------------
过程分析:
(1)前三种是使用第①种方式创建的,引用在栈中,对象实体分配在常量池中,而且
只有一份;
(2)后两种是使用第②种方式创建的,过程并不是在堆中直接创建对象,而是先在常
量池中查找是否有“abc”对象,如果没有则在常量池中创建一个此字符串对象,然后堆中
对
于
e
q
u
a
l
s
相
等
的
字
符
串
,
在
常
量
池
中
是
只
有
一
份
,
在
堆
中
则
有
多
份
;
再创建一个常量池中此“abc”对象的拷贝对象,
所以,对于String str = new String("abc");
如果常量池中如果没有abc则产生两个对象(一个在常量池中,一个在堆中),否则产
生一个对象(在堆中,常量池已经创建过了);
--------------------------------------------------------------------
二、基本类型的变量和常量
过程分析:
(1)变量:变量及其引用都存储在栈中;(栈中数据具有共享性,是否底层也实现了
常量池技术????)-------
(2)常量:存储在常量池;(并且在编译时,变量名已经就替换成常量值,所以在下
面的情况中,就可以解释为什么加了final修饰符就可以访问变量a了)
(3)Integer i = 3; 在内存中i在栈上,3常量池中,把地址给i;
三、基本数据类型的包装类实现常量池(注意是包装类)
Integer i = 3
【分析】
1、赋值问题
JDK1.5开始,Integer对象的赋值(除了创建对象赋值)会自动调用
Integer类的valueOf(int i)方法:
所以i = 3,底层调用了valueOf(int i)方法,
可见,在valueOf做了判断,如果值在128到127之间,就调用IntegerCache类
的成员数组的值作为返回值;(IntegerCache是Integer的内部类),这就是上
述所说的包装类维护范围,所有实现常量池的包装类的维护范围都是128
127,超出这个范围,就会执行下面的创建对象new Integer(i);
而IntegerCache内部类有一个静态成员常量数组cache[ ],它里面保存
了128到127范围的常量,代码如下:
所以,i = 3是指向常量池中的地址;
1、常量池的重要应用
内存图-----------
【局部内部类访问外部类的局部变量的时候一定要加final修饰符修饰该变量,也就是说
必须定义为常量。】
【过程分析】
上述案例中int a = 3;是局部变量,Inner是局部内部类,可见method方法在指向
show方法的指针,所以直接无法访问,只有在局部变量前面加上final常量修饰符才可以访
问,原因是当加了final常量修饰符,在编译阶段就会把变量直接替换为这个常量值,然后直
接在常量池中查找这个常量;
【误区讲解】