.
Java原始数据类型
Java中的变量类型分为两大类:原始数据类型 和 引用类型。
原始数据类型就是常说的值类型,共8种。
它们有各自的包装类型,提供了一些非常有用的基本操作。而且包装类都是不可变类(无法被继承、也不能改变内部的值)。
类型 | 含义 | 包装类 |
byte | 8-bit 有符号整数 | Byte |
short | 16-bit 有符号整数 | Short |
int | 32-bit 有符号整数 | Integer |
long | 64-bit 有符号整数 | Long |
float | 单精度 32-bit IEEE754 浮点数 | Float |
double | 双精度 64-bit IEEE754 浮点数 | Double |
boolean | 只有 true 和 flase 两个值 | Boolean |
char | 16-bit Unicode字符 | Character |
*JVM 不保证 long 和 double 更新操作的原子性。它们的更新被分为对高 32-bit 和 低 32-bit 的两个操作。
可将 long 和 double 变量声明为 volatile 或对其更新操作进行同步(AtomicLong的内部值就被声明为 volatile)。
谨慎使用包装类对象
Java 中的自动装箱/拆箱(boxing/unboxing)机制可以根据程序上下文自动转换原始数据类型与包装类型。
该操作只是语法糖,它发生在编译阶段。即,自动拆装箱与显式代码转换在字节码层面上是一样的。
但写代码时还是应该根据实际业务含义的需要选择合适的类型,而不是盲目地选择原始类型或包装类型。
如果只需原始类型,就选原始类型。拆装箱需要额外的性能开销;包装类对象占用的内存也更多。
在一些高性能非常重要的程序中,应尽量少创建对象。
-
包装类、动态数组(如,ArrayList)会需要更多的开销。
-
对象数组存储的是引用,对象分散在堆的不同位置,导致无法充分利用CPU缓存机制。
这类场景中,原始数据类型、数组是比较有优势的。甚至可以考虑用本地代码。
计数器实现方式对比示例:
Java代码
-
class Counter {
-
private final AtomicLong counter = new AtomicLong();
-
-
public void increase() {
-
counter.incrementAndGet();
-
}
-
}
-
-
class CompactCounter {
-
private volatile long counter;
-
public static final AtomicLongFieldUpdater<CompactCounter> updater
-
= AtomicLongFieldUpdater.newUpdater(CompactCounter.class, "counter");
-
-
public void increase() {
-
updater.incrementAndGet(this);
-
}
-
}
包装类的值缓存
在实际业务中,大部分数据操作集中在有限的、较小的数值范围。
为了提高效率,Byte、Short、Integer、Long、Boolean、Character都有缓存,以避免每次需要一个包装类对象时都 new 一个实例。
可通过调用这些包装类的 valueOf() 方法利用该缓存机制。
默认缓存范围
包装类 | 范围 | 说明 |
Integer | -128 ~ 127 | |
Short | -128 ~ 127 | |
Long | -128 ~ 127 | |
Byte | -128 ~ 127 | Byte 本身取值范围就是 -128 到 127,所以全都被缓存了。 |
Boolean | TRUE、FALSE | boolean 本就只有 true 和 false 两个值 |
Character | \u0000 ~ \u007F | 共128个 |
Integer 缓存范围调整
Integer 缓存比较特殊,它允许被调整(可能因为这方面的需求比较多,所以专门提供了这个机制)。
可通过设置 JVM 启动参数 -XX:AutoBoxCacheMax 调整允许被缓存的最大值