JavaSE5️⃣核心类 - 包装类型 & 任意精度类型
1、包装类型
Java 数据类型
- 基本类型:
- byte,short,int,long
- float,double
- char,boolean
- 引用类型:八种基本类型之外的所有数据类型。
- 包含所有 class 和 interface 类型。
- 可以赋值为
null
,表示空(不存在)。
1.1、包装类
包装类(Wrapper Class)
-
目的:使基本类型拥有对象(引用类型)的性质。
- 很多地方(如集合)需要使用引用类型,而不是基本类型。
- 相比基本类型,对象可以定义更复杂的操作。
-
类库:
java.lang
。定义了基本类型对应的包装类。基本类型 包装类型 byte Byte short Short int Integer long Long float Float double Double char Character boolean Boolean
1.1.1、不可变类
与
String
相同,包装类的声明、变量值均由final
修饰。对象实例化后,无法改变。
以 Integer 为例:
public final class Integer {
private final int value;
}
1.1.2、比较变量值
-
基本类型:使用
==
直接比较。 -
包装类型(属于引用类型):
-
==
:比较地址。 -
equals()
:先判断obj
是否为当前包装类型,再比较数值。// e.g. Integer的equals() public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; }
-
1.1.3、获取变量值
数值类型的包装类均继承自
Number
抽象类。定义了一系列获取数值的方法——
xxxValue()
。
public abstract class Number implements java.io.Serializable {
public abstract int intValue();
public abstract long longValue();
public abstract float floatValue();
public abstract double doubleValue();
public byte byteValue() {
return (byte)intValue();
}
public short shortValue() {
return (short)intValue();
}
private static final long serialVersionUID = -8742448824652078965L;
}
示例:
Double num = 8.88;
System.out.println(num.byteValue()); // 8
System.out.println(num.intValue()); // 8
System.out.println(num.longValue()); // 8
System.out.println(num.floatValue()); // 8.88
System.out.println(num.doubleValue()); // 8.88
1.2、拆装箱
1.2.1、含义
-
装箱:基本类型 → 包装类型,使用静态方法
包装类.valueOf(参数)
。 -
拆箱:包装类型 → 基本类型。使用实例方法
包装类实例.xxxValue()
。int n = 100; Integer num = Integer.valueOf(int); // 装箱 int num1 = num.intValue(); // 拆箱
1.2.2、自动拆装箱
Java 1.5
编译期优化(语法糖),用于简化代码。
本质:编译器自动调用 valueOf(基本类型)
和 xxxValue()
方法。
-
自动装箱(Auto Boxing):
int num = 7; Integer num1 = Integer.valueOf(num); Integer num2 = num; // 自动装箱
-
自动拆箱(Auto Unboxing):
Integer num = 100; // 自动装箱 int num1 = num.intValue(); int num2 = num; // 自动拆箱
缺点
- 耗费资源,影响性能。
- 对于使用者而言,基本类型和包装类型可以直接赋值,无需考虑其区别。
- 对于 JVM 底层,字节码文件严格区分基本类型和引用类型。
- 因此,相比对应类型的直接赋值,频繁的拆装箱操作会消耗性能。
- 自动拆箱可能报错 NPE,因为待拆箱变量可能为 null。
- 包装对象实例无法使用
==
比较(Integer 缓存区间内的对象除外)。
思考:如何验证自动拆装箱的触发时机?
答:反编译,查看字节码文件。
1.3、无符号整型转换
涉及知识:👉 计算机中的数值表示(无符号、有符号)
- Java 没有无符号整型(
Unsigned
)的基本数据类型。 - 可通过包装类型的静态方法,实现无符号整型的转换。
方法签名
-
Byte:
public static int toUnsignedInt(byte x) {} public static long toUnsignedLong(byte x) {}
-
Short:
public static int toUnsignedInt(short x) {} public static long toUnsignedLong(short x) {}
-
Integer:
public static long toUnsignedLong(int x) {} // 转换为无符号整数的字符串表示 public static String toUnsignedString(int i) {} public static String toUnsignedString(int i, int radix) {}
-
Long:
// 转换为无符号整数的字符串表示 public static String toUnsignedString(long i) {} public static String toUnsignedString(long i, int radix) {}
示例:将 byte 转换为无符号整数 int。
Hint:byte 占 2 个字节
byte x = -1;
byte y = 127;
System.out.println(Byte.toUnsignedInt(x)); // 255
System.out.println(Byte.toUnsignedInt(y)); // 127
-
负数:
-
对于有符号整数,-1 的原码是
1000 0001
,推导出补码是1111 1111
。 -
对于无符号整数,
1111 1111
对应的十进制数是 255。1 000 0001 // 原码 1 111 1110 // 反码 = 原码取反(符号位除外) 1 111 1111 // 补码 = 反码 + 1
-
-
正数:
-
对于有符号整数,127 的三码是
0111 1111
。 -
对于无符号整数,
0111 1111
对应的十进制数是 127。0111 1111 // byte 0000 0000 0111 1111 // int
-
1.4、String 转换
包装类型和 String 可以相互转换
见 👉 核心类 - String 相关类
2、Integer 缓存优化
Java 1.5
- 含义:提前创建缓存区间内的 Integer 实例,实现对象实例的复用。
- 默认区间:
[-128, 127]
。 - 作用:节省内存,提高性能。
2.1、创建实例
创建 Integer 的两种方式
- new:始终在堆内存中创建新的对象。
- valueOf(参数):提前创建对象,缓存优化(👍)。
2.1.1、new
始终创建新的实例(堆内存)。
-
方法签名:
public Integer(int value) { this.value = value; } // 重载:解析字符串 public Integer(String s) throws NumberFormatException { this.value = parseInt(s, 10); }
-
示例:不同的实例,数值相同但地址不同。
Integer num1 = new Integer(100); Integer num2 = new Integer(100); System.out.println(num1.equals(num2)); // true System.out.println(num1 == num2); // false
2.1.2、valueOf(参数)
相当于静态工厂方法,利用了缓存优化。
👉 工厂模式
-
方法源码:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) // 缓存区间:返回已创建的对象引用 return IntegerCache.cache[i + (-IntegerCache.low)]; // 非缓存区间:新建对象并返回 return new Integer(i); } // 重载方法:字符串+指定进制 public static Integer valueOf(String s, int radix) throws NumberFormatException { return Integer.valueOf(parseInt(s,radix)); } // 重载方法:字符串+10进制 public static Integer valueOf(String s) throws NumberFormatException { return Integer.valueOf(parseInt(s, 10)); }
-
示例:缓存区间内的
Integer
对象,值相同则==
比较为true
。Integer num1 = Integer.valueOf(100); Integer num2 = Integer.valueOf("100", 10); Integer num3 = Integer.valueOf("100"); System.out.println(num1 == num2); // true System.out.println(num1 == num3); // true
2.2、源码
Integer 类中,有关缓存优化的源码。
① 缓存区间
默认
[-128, 127]
。
static final int low = -128; // 缓存下限
static final int high; // 缓存上限
static final Integer cache[]; // 缓存数组
② 缓存上限
通过静态代码块
static{}
进行初始化。
-
默认上限:
h = 127
。 -
系统变量:
- 相关 JVM 参数:
-XX:AutoBoxCacheMax
。- 在 VM 初始化时会被设置并保存到
sun.misc.VM
类中。 - 可通过
java.lang.Integer.IntegerCache.high
获取到。
- 在 VM 初始化时会被设置并保存到
- 最大上限:Java 规定缓存区间不超过
Integer.MAX_VALUE
。
- 相关 JVM 参数:
-
确定上限:将最终的
h
赋值给high
,作为缓存区间上限。// 默认上限 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); // 取最小值:保证不超过最大上限 h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { } } // 赋值上限 high = h;
上限的可能取值:
- 未设置系统变量:127
- 设置了系统变量:
- 值 <= 127:127
- 127 < 值 < 最大上限:变量值
- > 最大上限:最大上限
③ 初始化缓存数组
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
assert IntegerCache.high >= 127;
3、任意精度类型
Java 中,CPU 原生提供的数值范围有限。
可通过 CPU 指令直接运算,速度快。
- 最大整数:
long
64 位。 - 最大浮点数:
double
64 位。
软件模拟,可表示任意精度的数值,
缺点是运算速度慢。
3.1、API - BigXxx
java.math
包提供了软件模拟的任意精度数值。
- 类型:
- BigInteger:任意精度整数(较少使用)。
- BigDecimal:任意精度的有符号十进制数(较多使用)。
- 特点:
Number
子类:实现了xxxValue()
,可转换为任意数值类型。- 不可变:运算不会修改原值,而是创建并返回新的对象(类似 String)。
底层实现
-
BigInteger:使用
int[]
数组表示任意精度整数。final int[] mag;
-
BigDecimal:使用
BigInteger
表示整数部分,使用int
类型的scale
表示小数位数。private final BigInteger intVal; private final int scale;
3.2、通用操作
3.2.1、创建实例
-
new:
BigInteger bi = new BigInteger(参数); BigDecimal bd = new BigDecimal(参数);
-
valueOf(参数):推荐使用。
BigInteger bi = BigInteger.valueOf(参数); BigDecimal bd = BigDecimal.valueOf(参数);
3.2.2、常用运算
不可变:运算不会修改原值,而是创建并返回新的对象。
- 比较大小:compareTo()
- 基础运算:
- 加:add()
- 减:substract()
- 乘:multiply()
- 除:divide()
- BigInteger:仅保留整数部分。
- BigDecimal:可指定末位小数的舍入模式。
- 取余:remainder()
- 其它:
- 最大公约数:gcd()
- 绝对值:abs()
- 取反:negate()
- 幂运算:pow()
3.2.3、类型转换
实现了
xxxValue()
,可转换为任意数值类型。
两种形式:当 BigInteger
值超出目标数值类型的表示范围时,采取不同的溢出措施。
xxxValue() | xxxValueExact() | |
---|---|---|
来历 | 实现 Number 类的方法 |
自身定义的方法 |
溢出时操作 | 丢失高位信息 | 抛 ArithmeticException 异常 |
特点 | 不报错,但结果未必精确 | 要么报错,要么精确 |
示例:将一个超出 long 范围的 BigInteger,转换成 long。
BigInteger bi1 = BigInteger.valueOf(Long.MAX_VALUE);
BigInteger bi2 = BigInteger.valueOf(1);
BigInteger bi = bi1.add(bi2); // Long.MAX_VALUE + 1
System.out.println(Long.MAX_VALUE); // 9223372036854775807
System.out.println(bi); // 9223372036854775808
System.out.println(bi.longValue()); // -9223372036854775808
System.out.println(bi.longValueExact()); // java.lang.ArithmeticException: BigInteger out of long range
分析:各个值所对应的二进制数。
最高位 | 高位部分 | 中间部分 | 低位部分 | 说明 | |
---|---|---|---|---|---|
Long.MAX_VALUE | 无 | 0111 1111 | 都是 1 | 1111 1111 | Long 最大值 |
bi | 0 | 1000 0000 | 都是 0 | 0000 0000 | Long 最大值 + 1 |
bi.longValue() | 丢弃 | 1000 0000 | 都是 0 | 0000 0000 | 溢出(丢弃高位) |
bi.longValueExact() | - | - | - | - | 溢出(抛出异常) |
3.3、BigDecimal 说明
3.3.1、使用场景
要求浮点数精确运算的场景。
- 基本数据类型中的浮点数(float 和 double)无法精确表示,其运算结果也是如此。
- 因此,基本类型的浮点数只适用于科学计算或工程计算(对精度要求不高)。
- 在商业运算中,使用
BigDecimal
可保证精确。
3.3.2、舍入模式
舍入模式(
Rounding Mode
):
- 说明:当
BigDecimal
存在多位小数时,可以指定策略来进行舍入。 - 使用时机:进行以下操作时,可指定要保留的位数和舍入模式。
- 手动舍入:
setScale()
- 除法舍入:
divide()
- 手动舍入:
八大模式:
假设保留 n 位小数,根据第 n + 1 位执行舍入策略。
模式 | 含义 | 舍入策略(根据第 n+1 位) | 说明 | |
---|---|---|---|---|
0 | UP | 进位 | 非零则进位 | |
1 | DOWN | 截断 | 直接截断 | 不受第 n + 1 位影响 |
2 | CEILING | 向上取值 | 正数 UP,负数 DOWN | |
3 | FLOOR | 向下取值 | 与 CEILING 相反 | 不会变大 |
4 | HALF_UP | 四舍五入 | >= 5 进位 | |
5 | HALF_DOWN | 五舍六入 | >5 进位 | |
6 | HALF_EVEN | - | 第 n 位是奇数则 HALF_UP 处理第 n + 1 位,偶数则 HALF_DOWN | 不是根据第 n + 1 位,而是根据第 n 位 |
7 | UNNECESSARY | 无需舍入 | 断言当前操作具有精确结果,无需舍入 | 若指定此模式且产生不精确结果,抛 ArithmeticException 异常 |
3.3.3、scale
scale:表示小数位数。
-
scale():获取小数位数。
BigDecimal d1 = new BigDecimal("123"); BigDecimal d2 = new BigDecimal("123.4"); BigDecimal d3 = new BigDecimal("123.45"); System.out.println(d1.scale()); // 0 System.out.println(d2.scale()); // 1 System.out.println(d3.scale()); // 2
-
setScale():设置小数位数,指定位数和舍入模式。
BigDecimal bd = new BigDecimal("123.45"); int newScale = 1; // 省略System.out.println(); bd.setScale(newScale, RoundingMode.UP); // 123.5(进位) bd.setScale(newScale, RoundingMode.DOWN); // 123.4(截断) bd.setScale(newScale, RoundingMode.CEILING); // 123.5(UP) bd.setScale(newScale, RoundingMode.FLOOR); // 123.4(DOWN) bd.setScale(newScale, RoundingMode.HALF_UP); // 123.5(五入) bd.setScale(newScale, RoundingMode.HALF_DOWN); // 123.4(五舍) bd.setScale(newScale, RoundingMode.HALF_EVEN); // 123.4(4是偶数,HALF_DOWN) bd.setScale(newScale, RoundingMode.UNNECESSARY);// 不精确,抛异常
3.3.4、除法
divide() | divideAndRemainder() | |
---|---|---|
含义 | 精确除法 | 整除并取余 |
运算结果 | 精确的浮点数 | 数组,元素依次为商、余数 |
说明 | 必须指定舍入模式,否则除不尽时会抛 ArithmeticException 异常 |
商一定是整数,余数是整数或浮点数 |
示例:
-
divide:
BigDecimal d1 = new BigDecimal(10); BigDecimal d2 = new BigDecimal(3); System.out.println(d1.divide(d2, 2, RoundingMode.HALF_UP)); // 3.33 System.out.println(d1.divide(d2)); // java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
-
divideAndRemainder:
// 余数是整数 BigDecimal d1 = new BigDecimal("10"); BigDecimal d2 = new BigDecimal("3"); BigDecimal[] result = d1.divideAndRemainder(d2); System.out.println(Arrays.toString(result)); // [3, 1] // 余数是浮点数 d1 = new BigDecimal("10.1"); d2 = new BigDecimal("3"); result = d1.divideAndRemainder(d2); System.out.println(Arrays.toString(result)); // [3.0, 1.1]
3.3.5、比较
若要比较数值相同,必须使用
compareTo()
。
== | equals() | compareTo() | |
---|---|---|---|
比较内容 | 实例地址 | 值、scale |
值 |
返回值 | 地址相同返回 true | 值和 scale 相同返回 true | 两个数的差值 |
实用性 | 几乎不用 | 较少使用 | 常用(👍) |
示例
BigDecimal d1 = new BigDecimal("10");
BigDecimal d2 = new BigDecimal("10.0");
System.out.println(d1 == d2); // false:不同实例
System.out.println(d1.equals(d2)); // false:小数位不同
System.out.println(d1.compareTo(d2)); // 0:数值相同