还记得 Java八种基本数据类型及对应包装类、四种引用类型吗?如果忘记可以到这里重温复习Java数据类型(八种基本数据类型 + 四种引用类型)、数据类型转换
基本类型 | 存储大小 | 初始化默认值 | 取值范围 | 包装类型 |
---|---|---|---|---|
byte | 1字节(8位) | 0 | -128~127 | Byte |
short | 2字节(16位) | 0 | -32768~32767 | Short |
int | 4字节(32位) | 0 | -2^31 ~ 2^31 - 1 | Integer |
long | 8字节(64位) | 0L。"L"理论上不分大小写,但若写成"l"容易与数字"1"混淆,不容易分辨,所以最好大写。 | -2^63 ~ 2^63 - 1 | Long |
float | 4字节(32位) | 0.0f | 符合IEEE754标准的浮点数,1.4E-45 ~ 3.4028235E38 | Float |
double | 8字节(64位) | 0.0d | 符合IEEE754标准的浮点数,4.9E-324 ~ 1.7976931348623157E308 | Double |
char | 2字节(16位) | '\u0000' | \u0000 ~ \uffff(十进制等效值为 0~65535,本质也是数值) | Character |
boolean | 1字节(8位)/4字节(32位) | false | true/false | Boolean |
基本类型与包装类型之间的转换可以通过 自动装箱和拆箱 完成。
Integer a = 18; //自动装箱。底层执行 Integer a = Integer.valueOf(18);
a = a + 6;//自动拆箱、自动装箱,底层 a=Integer.valueOf(a.intValue()+5);
Java常量池是Java内存管理中的一个重要概念,主要用于存储字符串常量、基本类型包装类常量、类和方法的全限定名等。在Java中,当创建一个字符串、基本类型包装类或类引用时,JVM会首先检查常量池中是否已存在该对象。如果存在,则直接返回对该对象的引用;如果不存在,则在常量池中创建一个新的对象并返回引用。
Java常量池主要有以下几个优势、特点:
Java常量池主要包括以下几个部分:
需要注意的是,Java 7及以后的版本对String常量池和Integer常量池做了一些优化。例如,对于String常量池,可以通过String类的intern()方法将一个字符串加入到常量池中;对于Integer常量池,缓存范围从Java 5的-128到127扩展到了Java 8及以后版本的-128到Integer.MAX_VALUE。这些优化提高了Java程序的性能和内存使用效率。
java内存模型(JDK7之前、JDK7之后)
JDK7之前
JDK7之后
String常量池已在String、StringBuilder、StringBuffer区别;String底层详解,实例化、拼接、比较;String为什么不可变介绍过,本文重点讨论基本类型包装类常量池。
Java中八种基本数据类型的包装类大部分都实现了缓存技术,目的是为了避免重复创建对象,提供性能、节省内存。基本数据类型直接存放在栈中,包装类型作为一种引用数据类型 在堆上分配内存(具体内容存放在堆中,栈中存放的是其具体内容所在内存的地址);Java在jdk1.5后包装类常量池使用缓存实现,缓冲池也叫常量池.
包装类 | 缓冲池范围 |
---|---|
Byte | -128~127(包含边界) |
Short | -128~127 |
Integer | -128~127 |
Long | -128~127(为了节省内存、提高性能,该范围是基于经验选择的,因为该范围内的数值是最常用的) |
Character | \u0000 ~ \u007F(十进制等效值为 0~127) |
Boolean | true和false |
(1)内存中有一个java基本类型封装类的常量池。这些类包括Byte, Short, Integer, Long, Character,Boolean,Boolean只有true和false。Float和Double这两个类并没有对应的常量池。
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
(2)上面6种整型的包装类的对象是存在范围限定的;在限定范围内存在在常量池, 范围以外则在堆区进行分配。
(3)当使用new关键字创建包装类对象时,都会在堆中创建新的对象;只有使用字面量赋值、且在缓冲池范围内 才可使用对象池,否则还是会在堆中创建对象。
(4)包装类的两个变量之间的比较 推荐使用equals进行(比较的是值而非地址)。
对于Integer var = ?,在-128~127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断;但这个区间外的所有数据 都会在堆上产生,并不会复用已有对象,此时==比较的是对象地址值、即对象是否相同,这是一个大坑,推荐使用equals方法进行判断
Integer的equals方法被重写过,比较的是内部value的值,源码如下
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
public int intValue() {
return value;
}
对于valueOf方法,只有当数值在 -128~127 之间时,才会被缓存。若超出该范围 仍会创建新的对象
对于超出-128, 127范围的Integer对象,无论是通过valueOf()方法还是new关键字创建,都会在堆中创建新的对象
Integer aInteger = 5; //在缓存,即常量池。底层调用Integer aInteger = Integer.valueOf(5),里面用到IntegerCache对象池
Integer bInteger = 5; //在缓存
Integer cInteger = new Integer(5); //在堆内存中
Integer dInteger = new Integer(5); //在堆内存中
Integer eInteger = 500; //不在缓存,在堆中
Integer fInteger = 500; //不在缓存,在堆中
System.out.println((aInteger == bInteger) + ", " + (cInteger==dInteger)); //true, false
System.out.println(aInteger == cInteger); //false
System.out.println(eInteger==fInteger); //false
通过字面量的方式赋值,实际调用Integer.valueOf方法。Integer i2=5,等价于Integer i2=Integer.valueOf(5),底层用到了IntegerCache,即Integer的缓存池。先判断值是否在缓存池中,如果在 就直接返回缓冲池中的内容,如果不在就new一个对象返回,源码如下
public final class Integer extends Number implements Comparable<Integer> {
//...
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//...
}
java8中,Integer缓冲池的最小值为-128,最大值默认为127,可以通过jvm参数调整上限。在IntegerCache的静态代码段中,为-128~127的所有整数生成一个Integer对象,然后添加到cache数据中,当调用Integer.valueof()时会判断数值是否在这个区间内,如果在就直接返回已经缓存好的对象,如果不再就直接新创建一个Integer对象。
编译器在自动装箱过程中会调用valueOf()方法,因此如果多个Integer实例使用自动装箱来创建、在-128~127范围内、并且值相同,就会引用相同的对象。
而对于通过new关键字创建的对象,自然不会使用缓存池中的对象。
IntegerCache源码如下:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
Integer a = 127;
Integer b = 127;
System.out.println(a == b); //true,常量池中同一个对象
Integer c = 128;
Integer d = 128;
//由于Integer只缓存-128~127之间的值,因此128对应的数据没有被缓存
System.out.println(c == d); //false,在堆中 不同对象
Integer e = new Integer(127);
Integer f = new Integer(127);
System.out.println(e == f); //false,通过new关键字 都会创建一个新对象、在堆中
Byte,Short,Integer,Long,Character,Boolean这 6 种包装类都实现了缓存池技术,原理及使用与上面介绍的Integer类似;两种浮点数类型的包装类 Float、Double没有实现缓存池技术
当包装类和对应基本数据类型进行 ==、运算符 等计算时,包装类会出现自动拆装箱,此时即便是堆中数和常量池数运算也是true
int aint = 127;
Integer i1 = 127; //在缓存。底层调用Integer cInteger = Integer.valueOf(127),里面用到IntegerCache对象池
Integer i2 = 127; //在缓存
Integer i3 = 0; //在缓存
Integer i4 = new Integer(127); //不在缓存
Integer i5 = new Integer(127); //不在缓存
Integer i6 = new Integer(0); //不在缓存
//Integer是int的封装类,当Integer与int进行==比较时(无论Integer是直接赋值、还是new),Integer会拆箱成一个int类型,所以还是相当于两个int类型进行比较
System.out.println((aint == i1) + ", " + (aint==i4)); //true, true。
//2个不同的Integer对象,“==”会校验Integer地址是否相同
System.out.println((i1 == i2) + ", " + (i1==i4) + ", " + (i4==i5)); //true, false, false
System.out.println(i1 == i2 + i3); //true
//堆创建,但Integer对象无法直接计算 故拆箱,比较数值
System.out.println(i4 == i5 + i6); //true
System.out.println(127 == i5 + i6); //true 同上
//Boolean类也实现了对象池技术
Boolean bool1 = true;
Boolean bool2 = true;
System.out.println(bool1 == bool2); //true
//浮点类型的包装类Float、Double没有实现对象池技术
Double d1 = 2.0;
Double d2 = 2.0;
System.out.println(d1 == d2); //false
i1、i2、i3都是常量池中的对象,i4、i5、i6是堆中的对象。
i4 == i5 + i6 返回true是因为,涉及运算 i5、i6会进行自动拆箱操作、数值相加,即i4 == 127。Integer对象无法直接与数值进行比较,故i4同样自动拆箱为int值127,最终转为 127==127 进行数值比较。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。