Java
中的数据类型
Java
有八种基本数据类型:byte
,short
,int
,long
,float
,double
,char
,boolean
。
八种基本数据类型
byte | Byte | ||||
short | Short | ||||
int | Integer | ||||
long | Long | ||||
float | Float | ||||
double | Double | ||||
char | \u0000 | \u0000 \uffff (0~65535) | Character | ||
boolean | false | true false | Boolean |
基本类型与包装类型的区别
Java
是面向对象的语言,在Java
中,一切皆对象,而八种基本数据类型却不是对象,比如我们想操作一个数字的集合,但是在Java
中集合只能存放对象而不能存储基本数据类型,存储基本数据类型只能用数组,但是数组却没有集合更丰富的API
,怎么办呢?这时使用基本数据类型对应的包装类就好了,完美地解决了这个问题。那么基本类型与包装类型究竟有哪些区别呢?
基本类型:在声明但未初始化时,它们会有默认值,默认值如上述表中所示;在做局部变量时,必须初始化才能使用;
包装类型:任何未初始化的包装类型变量的默认值为
null
;
基本类型:基本类型可以直接声明并赋值,比如:
int a = 10
;包装类型:包装类型可以通过
new
关键字创建对象,如Integer a = new Integer(10)
,也可以通过自动装箱创建,如Integer a = 10
;
基本类型:它们直接在栈内存中分配空间,存储的是具体的值;
包装类型:包装类作为类对象在堆内存中分配空间,引用变量存储在栈中,但引用指向堆中的对象;
基本类型:适用于简单的数值计算和逻辑判断;在性能敏感的场景中,基本类型通常更高效,因为它们直接存储值,没有额外的对象开销;
包装类型:提供丰富的
API
,如方法调用、类型转换和空值处理,更面相对象,适用于需要对象功能的场景,如使用集合框架等。
包装类型的缓存机制
在Java
中,包装类实际上是对基本数据类型的封装,每个包装类都包含了对应的基本数据类型的值,以及其他的一些方法(如类型转换、比较等)。为了提高性能,部分包装类型(如Integer
、Byte
、Short
、Character
、Long
、Boolean
)使用了缓存机制。这种缓存机制可以减少对象的创建次数,提高程序的运行效率。而浮点数类型的包装类(Float 和 Double)并没有实现缓存机制,主要是因为浮点数的表示范围非常大,且使用场景多样,缓存效果并不明显。
实现原理
本文以Integer
为例进行分析,Integer
类提供了一个静态内部类IntegerCache
,用于缓存一定范围内的Integer
对象。默认情况下,这个范围是从 -128 到 127。这个范围可以通过系统属性java.lang.Integer.IntegerCache.high
来调整。Integer.valueOf(int i)
方法是推荐的装箱方法,因为它会利用缓存机制,当调用Integer
的valueO(int i)
方法时,会先检查传入的数值是否在缓存范围内,如果是,则直接从缓存中返回对应的对象;否则,会创建一个新的对象并返回。Integer
的缓存核心源码如下:
public static Integer valueOf(int i) {
// 判断传入的数值是否在 IntegerCache 的缓存范围内
if (i >= IntegerCache.low && i <= IntegerCache.high)
// 命中缓存直接取缓存值返回
return IntegerCache.cache[i + (-IntegerCache.low)];
// 否则创建一个新的对象返回
return new Integer(i);
}
// 静态内部类 IntegerCache
private static class IntegerCache {
static final int low = -128; // 低值默认-128
static final int high;
static final Integer cache[]; // 缓存池
static {
// 高值可按属性配置,默认127
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);
// 缓存池最大数组大小为 Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// 处理异常情况,如果属性设置的值无法解析为 int,忽略。
}
}
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
对象时,要特别小心,应该使用equals()
方法而不是==
运算符,因为它们可能指向不同的对象。
java.lang.Integer.IntegerCache.high
来调整缓存的上限,但是并不推荐这样做,因为过大的缓存范围会占用更多的内存资源,并且会影响性能。
valueOf
方法:Integer.valueOf(int i)
方法来进行装箱操作,而不是使用new Integer(int i)
,因为valueOf()
方法会利用缓存机制,从而提高性能和减少内存消耗。
阿里开发手册中要求:
【强制】 所有整型包装类对象之间值的比较,全部使用equals
方法比较。
说明:对于Integer var = ?
在 -128 至 127 之间的赋值,Integer
对象是在IntegerCache.cache
产生,会复用已有对象,这个区间内的Integer
值可以直接使用==
进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals
方法进行判断。
什么是自动拆装箱
自动和拆装箱是Java 5.0
引入了一个特性,它允许在基本数据类型和它们的包装类之间进行自动转换,无需显式地进行转换。但是,这种自动转换可能会带来性能开销,特别是在涉及大量数据或循环时。
什么是装箱(Boxing)
装箱是指将基本数据类型转换为对应的包装类对象的过程。例如,将一个int
值转换成一个Integer
对象。在早期版本的Java
中,这个过程需要显式地创建一个新的包装类对象来完成,如下所示:
int num = 10;
Integer numObj = new Integer(num); // 显式装箱
从Java 5.0
开始,引入了自动装箱的概念,上述代码可以简化为:
Integer num = 10; // 自动装箱
什么是拆箱(Unboxing)
拆箱则是指将包装类对象转换回基本数据类型的过程。例如,将一个Integer
对象转换为int
值。同样地,在Java 5.0
之前,这需要显式地调用方法来实现:
Integer numObj = new Integer(10);
int num = numObj.intValue(); // 显式拆箱
在支持自动拆箱的 Java 版本中,可以简化为:
int num = new Integer(10); // 自动拆箱
应用场景示例
自动拆装箱在很多场景下都非常有用,尤其是在集合框架中,因为集合只能存储对象引用而不能直接存储基本数据类型。例如,使用ArrayList<Integer>
时,添加元素时会自动装箱,获取元素时则会自动拆箱:
List<Integer> list = new ArrayList<>();
list.add(10); // 自动装箱
int value = list.get(0); // 自动拆箱
最佳实践
NULL
值:null
的包装类对象时,确保先检查null
以避免(NPE
)空指针异常;
阿里面试题
public class IntegerCacheDemo {
public static void main(String[] args) {
Integer a = 100;
Integer b = 100;
Integer c = 200;
Integer d = 200;
System.out.println(a == b); // true,因为100在缓存范围内
System.out.println(c == d); // false,因为200不在缓存范围内
// 使用new关键字创建对象,总是会创建新的对象
Integer e = new Integer(100);
Integer f = new Integer(100);
System.out.println(e == f); // false
}
}
为什么a == b
的结果为true
?为什么c == d
的结果为false
?为什么e == f
的结果为false
?
想必此刻已经了然于胸了吧!
扫描下方二维码
关注
👇👇👇