基本类型包装类
面向对象程序设计(Java)
掌握包装类的使用,让基本类型支持面向对象操作包装类概述与继承体系
包装类的基本概念:Java并不是纯面向对象的语言,基本数据类型不是面向对象的。Java提供了基本类型包装类,使得基本类型能够支持对象操作!
基本类型与包装类对应关系:
byte→Byteboolean→Booleanshort→Shortchar→Characterint→Integerlong→Longfloat→Floatdouble→Double
包装类继承体系
Number抽象类: Number 类是 Byte, Short, Integer, Long, Float, Double 这些数字包装类的抽象父类。
为什么需要包装类?
- 泛型支持: 泛型参数必须使用对象类型
- null值表示: 基本类型不能为null,包装类可以
- 工具方法: 提供丰富的静态工具方法(进制转换、字符串解析等)
- 面向对象特性: 支持继承、多态等OOP特性
- 集合框架应用: 集合只能存储对象
Number类的核心方法
Number抽象类提供了一系列用于将包装的数值转换为基本数据类型的方法:
数值转换方法:
byte byteValue()- 返回byte值short shortValue()- 返回short值int intValue()- 返回int值long longValue()- 返回long值float floatValue()- 返回float值double doubleValue()- 返回double值
设计特点:
- 所有数字包装类都继承这些方法
- 支持灵活的类型转换
- 为多态编程提供基础
- 统一了数值操作的接口
包装类基础操作与装箱拆箱
深入理解包装类的创建、自动装箱拆箱机制以及基本操作
创建包装类对象的三种方式
public class WrapperCreation {
public static void main(String[] args) {
// 方式1:使用构造函数(已废弃,不推荐)
Integer i1 = new Integer(10);
Integer i2 = new Integer("20");
// 方式2:使用valueOf()方法(推荐)
Integer i3 = Integer.valueOf(30); // 自动利用缓存
Integer i4 = Integer.valueOf("40"); // 字符串转换
// 方式3:自动装箱(最常用)
Integer i5 = 50; // 编译器自动调用 Integer.valueOf(50)
System.out.println("创建结果: " + i1 + ", " + i2 + ", " + i3 + ", " + i4 + ", " + i5);
}
}
自动装箱拆箱机制详解
自动装箱 (基本类型 → 包装类)
// 编译前
Integer num = 42;
Double d = 3.14;
// 编译后
Integer num = Integer.valueOf(42);
Double d = Double.valueOf(3.14);
// 其他装箱场景
list.add(100); // add(Integer.valueOf(100))
Integer[] arr = {1,2,3}; // {valueOf(1), valueOf(2), valueOf(3)}
自动拆箱 (包装类 → 基本类型)
// 编译前
int a = intObj;
double b = doubleObj + 1.0;
if (num > 50) { ... }
// 编译后
int a = intObj.intValue();
double b = doubleObj.doubleValue() + 1.0;
if (num.intValue() > 50) { ... }
装箱拆箱的详细过程演示
public class BoxingUnboxingProcess {
public static void main(String[] args) {
// 1. 自动装箱
Integer num1 = 10; // Integer.valueOf(10)
Integer num2 = 20; // Integer.valueOf(20)
// 2. 运算中的装箱拆箱
Integer sum = num1 + num2; // 详细过程:
// a) num1拆箱 -> 10
// b) num2拆箱 -> 20
// c) 基本运算 -> 30
// d) 结果装箱 -> valueOf(30)
// 3. 混合类型运算
int result = num1 * 2; // num1拆箱 -> 10 * 2 = 20
// 4. 条件判断
if (sum > num1) { // sum和num1都拆箱比较
System.out.println("sum > num1");
}
// 5. 方法参数传递
processNumber(sum); // sum拆箱传递给int参数
}
static void processNumber(int number) {
System.out.println("处理数字: " + number);
}
}
/* 运行结果:
sum > num1
处理数字: 30
*/
包装类内部结构与基本操作
public class WrapperOperations {
public static void main(String[] args) {
Integer num = 100; // 自动装箱
// 1. 获取基本类型值
int explicitValue = num.intValue(); // 显式拆箱
int autoValue = num; // 自动拆箱
// 2. 转换为其他数字类型(继承自Number类)
byte b = num.byteValue(); // 100
short s = num.shortValue(); // 100
long l = num.longValue(); // 100L
float f = num.floatValue(); // 100.0f
double d = num.doubleValue(); // 100.0
// 3. 对象操作
Integer another = 100;
System.out.println("equals: " + num.equals(another)); // true
// 4. 实用方法
String binary = Integer.toBinaryString(100); // "1100100"
String hex = Integer.toHexString(255); // "ff"
int parsed = Integer.parseInt("123"); // 123
System.out.println("二进制: " + binary + ", 十六进制: " + hex + ", 解析: " + parsed);
}
}
/* 运行结果:
equals: true
二进制: 1100100, 十六进制: ff, 解析: 123
*/
重要注意事项
⚠️ 常见陷阱与风险:
- NullPointerException: null值拆箱会抛出异常
Integer num = null; int value = num; // NullPointerException! - 性能开销: 频繁装箱拆箱会影响性能
- 对象比较: ==比较的是引用,equals比较的是值
- 缓存机制: 小数值可能返回相同对象
包装类比较与缓存机制
深入理解包装类的对象比较原理和各种类型的缓存机制
对象比较的基本规则
== 运算符(引用比较)
public class ComparisonTest {
public static void main(String[] args) {
// new创建的对象
Integer a = new Integer(10);
Integer b = new Integer(10);
System.out.println(a == b); // false
// 自动装箱(受缓存影响)
Integer c = 10, d = 10;
System.out.println(c == d); // true(缓存)
Integer e = 128, f = 128;
System.out.println(e == f); // false(超出缓存)
}
}
equals() 方法(值比较)
public class EqualsTest {
public static void main(String[] args) {
Integer a = new Integer(10);
Integer b = new Integer(10);
Integer c = 10;
System.out.println(a.equals(b)); // true
System.out.println(a.equals(c)); // true
System.out.println(b.equals(c)); // true
// 类型检查
Integer num = 100;
Double dbl = 100.0;
System.out.println(num.equals(dbl)); // false
}
}
包装类缓存机制详解
| 包装类 | 缓存范围 | 是否可配置 | 缓存对象数 | 特点 |
|---|---|---|---|---|
| Byte | -128~127 | 否 | 256个 | 全部值缓存 |
| Short | -128~127 | 否 | 256个 | 部分值缓存 |
| Integer | -128~127 | 是 | 默认256个 | 可扩展缓存范围 |
| Long | -128~127 | 否 | 256个 | 部分值缓存 |
| Character | \u0000~\u007F | 否 | 128个 | ASCII字符集 |
| Boolean | true, false | 否 | 2个 | 两个单例对象 |
| Float/Double | 无 | N/A | 0个 | 没有缓存机制 |
Integer缓存源码分析
// Integer类内部实现(简化版)
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// 1. 默认缓存范围
int h = 127;
// 2. 支持JVM参数配置缓存上限
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127); // 至少127
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// 配置错误时使用默认值127
}
}
high = h;
// 3. 创建缓存数组
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
}
// 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 class CacheImpactTest {
public static void main(String[] args) {
System.out.println("=== 缓存机制影响测试 ===");
// 1. Integer缓存测试
testIntegerCache();
// 2. 其他包装类缓存测试
testOtherWrappers();
// 3. 无缓存的Float/Double测试
testNoCacheWrappers();
}
static void testIntegerCache() {
System.out.println("\n--- Integer缓存测试 ---");
// 缓存范围内
Integer a = 100, b = 100;
System.out.println("100 == 100: " + (a == b) + " (缓存内)");
// 超出缓存范围
Integer c = 200, d = 200;
System.out.println("200 == 200: " + (c == d) + " (缓存外)");
// new创建不受缓存影响
Integer e = new Integer(100), f = new Integer(100);
System.out.println("new Integer(100) == new Integer(100): " + (e == f));
}
static void testOtherWrappers() {
System.out.println("\n--- 其他包装类缓存测试 ---");
// Boolean测试
Boolean t1 = true, t2 = true;
System.out.println("true == true: " + (t1 == t2));
// Character测试
char ch1 = 'A', ch2 = 'A'; // ASCII字符
Character c1 = ch1, c2 = ch2;
System.out.println("'A' == 'A': " + (c1 == c2) + " (ASCII缓存)");
// Byte测试(全部缓存)
byte b1 = 50, b2 = 50;
Byte bt1 = b1, bt2 = b2;
System.out.println("50 == 50: " + (bt1 == bt2) + " (Byte全缓存)");
}
static void testNoCacheWrappers() {
System.out.println("\n--- 无缓存包装类测试 ---");
// Float测试(无缓存)
Float f1 = 10.5f, f2 = 10.5f;
System.out.println("10.5f == 10.5f: " + (f1 == f2) + " (Float无缓存)");
// Double测试(无缓存)
Double d1 = 10.5, d2 = 10.5;
System.out.println("10.5 == 10.5: " + (d1 == d2) + " (Double无缓存)");
}
}
/* 运行结果:
=== 缓存机制影响测试 ===
--- Integer缓存测试 ---
100 == 100: true (缓存内)
200 == 200: false (缓存外)
new Integer(100) == new Integer(100): false
--- 其他包装类缓存测试 ---
true == true: true
'A' == 'A': true (ASCII缓存)
50 == 50: true (Byte全缓存)
--- 无缓存包装类测试 ---
10.5f == 10.5f: false (Float无缓存)
10.5 == 10.5: false (Double无缓存)
*/
最佳实践建议
💡 比较和缓存的使用建议:
- 对象比较:始终使用equals()方法比较包装类的值
- ==运算符:只在确定需要比较对象引用时使用
- 性能优化:对于小数值整数,利用缓存机制可以提高性能
- 避免new:优先使用自动装箱或valueOf()方法
- 理解边界:清楚各包装类的缓存范围和限制
包装类工具方法与异常处理
掌握包装类的实用工具方法和安全的异常处理技巧,编写健壮的代码
各类包装类的实用工具方法
Integer: 字符串转换与进制
// 字符串转Integer
Integer i1 = Integer.valueOf("666");
int i2 = Integer.parseInt("555");
// 进制转换工具
int decimal = 166;
String hex = Integer.toHexString(decimal); // "a6"
String binary = Integer.toBinaryString(decimal); // "10100110"
String octal = Integer.toOctalString(decimal); // "246"
// 特殊进制解析
Integer fromHex = Integer.decode("0xA6"); // 166
Integer fromBinary = Integer.decode("0b1010"); // 10
Integer fromOctal = Integer.decode("012"); // 10
Double: 浮点数处理
// 字符串转Double
Double d1 = Double.valueOf("3.14");
double d2 = Double.parseDouble("2.718");
// 特殊值判断
double nan = 0.0 / 0.0;
double inf = 1.0 / 0.0;
System.out.println(Double.isNaN(nan)); // true
System.out.println(Double.isInfinite(inf)); // true
// 数值比较
System.out.println(Double.compare(3.14, 2.71)); // 1 (大于)
System.out.println(Double.isFinite(3.14)); // true (有限)
Character: 字符处理方法
public class CharacterUtils {
public static void main(String[] args) {
char ch = 'A';
// 字符类型判断
System.out.println("是否是字母: " + Character.isLetter(ch)); // true
System.out.println("是否是数字: " + Character.isDigit(ch)); // false
System.out.println("是否是字母或数字: " + Character.isLetterOrDigit(ch)); // true
System.out.println("是否是空格: " + Character.isWhitespace(' ')); // true
// 大小写转换
System.out.println("转小写: " + Character.toLowerCase(ch)); // 'a'
System.out.println("转大写: " + Character.toUpperCase('a')); // 'A'
// Unicode工具
System.out.println("数字值: " + Character.getNumericValue('5')); // 5
System.out.println("Unicode块: " + Character.UnicodeBlock.of(ch)); // BASIC_LATIN
}
}
Boolean: 字符串转换规则
public class BooleanConversion {
public static void main(String[] args) {
// valueOf()方法:不区分大小写,只有"true"返回true
Boolean b1 = Boolean.valueOf("true"); // true
Boolean b2 = Boolean.valueOf("TRUE"); // true
Boolean b3 = Boolean.valueOf("false"); // false
Boolean b4 = Boolean.valueOf("FALSE"); // false
Boolean b5 = Boolean.valueOf("yes"); // false
Boolean b6 = Boolean.valueOf("1"); // false
Boolean b7 = Boolean.valueOf(null); // false
// parseBoolean()方法:规则相同,但返回基本类型
boolean pb1 = Boolean.parseBoolean("true"); // true
boolean pb2 = Boolean.parseBoolean("any"); // false
System.out.println("Boolean.valueOf(\"true\"): " + b1);
System.out.println("Boolean.valueOf(\"yes\"): " + b5);
System.out.println("Boolean.parseBoolean(\"1\"): " + pb2);
}
}
异常处理:安全的数字转换
public class SafeNumberConversion {
// 1. NumberFormatException安全处理
public static Integer safeParseInt(String str) {
if (str == null || str.trim().isEmpty()) {
return null;
}
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
System.err.println("无法转换 '" + str + "' 为整数: " + e.getMessage());
return null;
}
}
// 2. 带默认值的转换
public static int parseIntWithDefault(String str, int defaultValue) {
try {
return Integer.parseInt(str);
} catch (NumberFormatException | NullPointerException e) {
return defaultValue;
}
}
// 3. 多次尝试不同格式
public static Double parseFlexible(String input) {
if (input == null) return null;
// 尝试直接解析
try {
return Double.parseDouble(input);
} catch (NumberFormatException e1) {
// 尝试去除逗号等格式化字符
try {
String clean = input.replaceAll("[,\\s]", "");
return Double.parseDouble(clean);
} catch (NumberFormatException e2) {
System.err.println("无法解析数字: " + input);
return null;
}
}
}
public static void main(String[] args) {
String[] inputs = {"123", "1,234", "abc", "", null, "-999"};
System.out.println("=== 安全转换演示 ===");
for (String input : inputs) {
System.out.print("输入: '" + input + "' -> ");
Integer result = safeParseInt(input);
System.out.println("结果: " + result);
// 带默认值的转换
int safeResult = parseIntWithDefault(input, 0);
System.out.println("默认值结果: " + safeResult);
System.out.println();
}
}
}
/* 运行结果:
=== 安全转换演示 ===
输入: '123' -> 结果: 123
默认值结果: 123
输入: '1,234' -> 结果: null
默认值结果: 0
输入: 'abc' -> 无法转换 'abc' 为整数: For input string: "abc"
结果: null
默认值结果: 0
输入: '' -> 结果: null
默认值结果: 0
输入: 'null' -> 结果: null
默认值结果: 0
输入: '-999' -> 结果: -999
默认值结果: -999
*/
NullPointerException防范策略
public class NullSafetyStrategies {
// ❌ 危险的写法
public static int dangerousSum(Integer a, Integer b) {
return a + b; // NPE风险
}
// ✅ 安全的null处理方式
public static int safeSum(Integer a, Integer b) {
// 方式1:三元运算符
int intA = (a != null) ? a : 0;
int intB = (b != null) ? b : 0;
return intA + intB;
}
// ✅ 使用Objects工具类
public static int safeSumWithObjects(Integer a, Integer b) {
int intA = java.util.Objects.requireNonNullElse(a, 0);
int intB = java.util.Objects.requireNonNullElse(b, 0);
return intA + intB;
}
// ✅ 使用Optional处理
public static Optional<Integer> safeMultiply(Optional<Integer> a, Optional<Integer> b) {
return a.flatMap(valA ->
b.map(valB -> valA * valB));
}
// ✅ 工具方法:安全的拆箱
public static int safeUnbox(Integer value, int defaultValue) {
return (value != null) ? value : defaultValue;
}
// ✅ 批量处理中的null安全
public static double averageSafely(List<Integer> numbers) {
if (numbers == null || numbers.isEmpty()) {
return 0.0;
}
return numbers.stream()
.filter(java.util.Objects::nonNull) // 过滤null值
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
}
public static void main(String[] args) {
// 测试各种null安全方法
System.out.println("危险求和: " + dangerousSum(10, null)); // 抛NPE
System.out.println("安全求和: " + safeSum(10, null)); // 10
System.out.println("Objects求和: " + safeSumWithObjects(null, 20)); // 20
Optional<Integer> result = safeMultiply(Optional.of(5), Optional.of(3));
System.out.println("Optional乘法: " + result.orElse(0)); // 15
List<Integer> numbers = Arrays.asList(10, null, 20, 30, null);
System.out.println("安全平均: " + averageSafely(numbers)); // 20.0
}
}
最佳实践总结
💡 异常处理和安全编码建议:
- 输入验证:对外部输入进行null和格式检查
- 异常捕获:捕获具体的异常类型,提供有意义的错误信息
- 默认值策略:为null或无效输入提供合理的默认值
- 防御性编程:假设输入可能为null,提前做好防护
- 工具方法:创建可重用的安全转换和验证方法
- 文档说明:明确方法对null值和异常的处理方式
大整数:BigInteger
重要说明:BigInteger不是包装类,它没有对应的基本数据类型!
特点与示例:
-
任意精度整数:大小理论上只受内存限制。
// 演示:计算一个超出long范围的数 BigInteger largeNum = new BigInteger("123456789012345678901234567890"); BigInteger result = largeNum.multiply(largeNum); System.out.println(result); // 输出一个非常巨大的数字 -
不支持运算符:必须使用方法进行运算。
BigInteger a = BigInteger.valueOf(100); BigInteger b = BigInteger.valueOf(50); // BigInteger c = a + b; // 编译错误! BigInteger c = a.add(b); // 正确方式 System.out.println(c); // 输出: 150 -
不可变对象:每次运算都产生新对象。
BigInteger num = BigInteger.valueOf(10); num.add(BigInteger.valueOf(5)); // 这行代码不改变num的值 System.out.println(num); // 仍然输出: 10 BigInteger newNum = num.add(BigInteger.valueOf(5)); // 必须接收返回值 System.out.println(newNum); // 输出: 15
适用场景:需要处理超出long范围的整数计算,如加密、科学计算等。
大浮点数:BigDecimal详解
重要说明:BigDecimal不是包装类,它是专门的数学工具类,用于精确的小数计算!
1. 为什么需要BigDecimal?
public class FloatingPointProblem {
public static void main(String[] args) {
System.out.println("=== 浮点数精度问题演示 ===");
// 问题1:简单的加法运算
double a = 0.1;
double b = 0.2;
double c = a + b;
System.out.println("0.1 + 0.2 = " + c); // 输出: 0.30000000000000004
// 问题2:金融计算中的误差累积
double money = 0.0;
for (int i = 0; i < 10; i++) {
money += 0.1; // 误差会累积
}
System.out.println("10次0.1相加: " + money); // 输出: 0.9999999999999999
// 问题3:比较运算的陷阱
double d1 = 0.0;
for (int i = 0; i < 10; i++) {
d1 += 0.1;
}
System.out.println("d1 == 1.0: " + (d1 == 1.0)); // 输出: false
System.out.println("\n=== BigDecimal解决方案 ===");
// 解决方案:使用BigDecimal
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.2");
BigDecimal bd3 = bd1.add(bd2);
System.out.println("BigDecimal 0.1 + 0.2 = " + bd3); // 输出: 0.3
BigDecimal moneyBD = BigDecimal.ZERO;
BigDecimal tenCents = new BigDecimal("0.1");
for (int i = 0; i < 10; i++) {
moneyBD = moneyBD.add(tenCents);
}
System.out.println("BigDecimal 10次0.1相加: " + moneyBD); // 输出: 1.0
}
}
2. BigDecimal创建方式对比
public class BigDecimalCreation {
public static void main(String[] args) {
// 方式1:使用字符串构造(推荐)
BigDecimal bd1 = new BigDecimal("0.1"); // 精确
BigDecimal bd2 = new BigDecimal("123.456"); // 精确
// 方式2:使用valueOf方法(推荐,详见包装类基本使用)
BigDecimal bd3 = BigDecimal.valueOf(0.1); // 等价于new BigDecimal(Double.toString(0.1))
BigDecimal bd4 = BigDecimal.valueOf(123.456); // 等价于new BigDecimal("123.456")
// 方式3:直接使用double构造(不推荐,会有精度问题)
BigDecimal bd5 = new BigDecimal(0.1); // 可能不精确
BigDecimal bd6 = new BigDecimal(123.456); // 可能不精确
System.out.println("字符串构造: " + bd1); // 0.1
System.out.println("valueOf构造: " + bd3); // 0.1
System.out.println("double构造: " + bd5); // 0.1000000000000000055511151231257827021181583404541015625
// 比较精确性
System.out.println("bd1.equals(bd3): " + bd1.equals(bd3)); // true
System.out.println("bd1.equals(bd5): " + bd1.equals(bd5)); // false
}
}
3. 基本运算操作
public class BigDecimalOperations {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("10.5");
BigDecimal b = new BigDecimal("3.2");
// 加法
BigDecimal sum = a.add(b);
System.out.println("加法: " + a + " + " + b + " = " + sum); // 13.7
// 减法
BigDecimal diff = a.subtract(b);
System.out.println("减法: " + a + " - " + b + " = " + diff); // 7.3
// 乘法
BigDecimal product = a.multiply(b);
System.out.println("乘法: " + a + " × " + b + " = " + product); // 33.60
// 除法 - 需要指定精度和舍入模式
BigDecimal quotient = a.divide(b, 2, RoundingMode.HALF_UP);
System.out.println("除法: " + a + " ÷ " + b + " = " + quotient); // 3.28
// 幂运算
BigDecimal power = a.pow(2);
System.out.println("平方: " + a + "² = " + power); // 110.25
// 取绝对值
BigDecimal abs = new BigDecimal("-5.5").abs();
System.out.println("绝对值: |-5.5| = " + abs); // 5.5
// 比较运算
int compareResult = a.compareTo(b);
System.out.println("比较: " + a + " compareTo " + b + " = " + compareResult); // 1 (大于)
}
}
4. 舍入模式详解
public class RoundingModes {
public static void main(String[] args) {
BigDecimal number = new BigDecimal("3.14159");
BigDecimal divisor = new BigDecimal("1.0");
// 保留2位小数,使用不同舍入模式
System.out.println("原始数字: " + number);
System.out.println("向上舍入 (CEILING): " + number.setScale(2, RoundingMode.CEILING)); // 3.15
System.out.println("向下舍入 (FLOOR): " + number.setScale(2, RoundingMode.FLOOR)); // 3.14
System.out.println("向零舍入 (DOWN): " + number.setScale(2, RoundingMode.DOWN)); // 3.14
System.out.println("四舍五入 (HALF_UP): " + number.setScale(2, RoundingMode.HALF_UP)); // 3.14
System.out.println("五舍六入 (HALF_DOWN): " + number.setScale(2, RoundingMode.HALF_DOWN)); // 3.14
System.out.println("银行家舍入 (HALF_EVEN): " + number.setScale(2, RoundingMode.HALF_EVEN)); // 3.14
// 测试边界情况:2.5
BigDecimal edgeCase = new BigDecimal("2.5");
System.out.println("\n边界测试 - 2.5:");
System.out.println("四舍五入 (HALF_UP): " + edgeCase.setScale(0, RoundingMode.HALF_UP)); // 3
System.out.println("五舍六入 (HALF_DOWN): " + edgeCase.setScale(0, RoundingMode.HALF_DOWN)); // 2
System.out.println("银行家舍入 (HALF_EVEN): " + edgeCase.setScale(0, RoundingMode.HALF_EVEN)); // 2
}
}
5. 实际应用:货币计算
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
public class CurrencyCalculator {
// 货币计算工具类
public static class Money {
private final BigDecimal amount;
public Money(String amount) {
this.amount = new BigDecimal(amount).setScale(2, RoundingMode.HALF_UP);
}
public Money add(Money other) {
return new Money(this.amount.add(other.amount).toString());
}
public Money multiply(BigDecimal multiplier) {
BigDecimal result = this.amount.multiply(multiplier).setScale(2, RoundingMode.HALF_UP);
return new Money(result.toString());
}
public Money divide(BigDecimal divisor) {
BigDecimal result = this.amount.divide(divisor, 2, RoundingMode.HALF_UP);
return new Money(result.toString());
}
@Override
public String toString() {
return "¥" + amount;
}
}
public static void main(String[] args) {
// 购物车计算示例
Money apple = new Money("5.99");
Money banana = new Money("3.50");
Milk milk = new Money("12.80"); // 假设有Milk类
Money subtotal = apple.add(banana).add(milk);
Money tax = subtotal.multiply(new BigDecimal("0.08")); // 8%税率
Money total = subtotal.add(tax);
System.out.println("商品明细:");
System.out.println("苹果: " + apple);
System.out.println("香蕉: " + banana);
System.out.println("牛奶: " + milk);
System.out.println("小计: " + subtotal);
System.out.println("税费: " + tax);
System.out.println("总计: " + total);
// 平均分摊
BigDecimal peopleCount = new BigDecimal("3");
Money perPerson = total.divide(peopleCount);
System.out.println("每人应付: " + perPerson);
}
}
class Milk extends Money {
public Milk(String price) {
super(price);
}
}
/* 运行结果:
商品明细:
苹果: ¥5.99
香蕉: ¥3.50
牛奶: ¥12.80
小计: ¥22.29
税费: ¥1.78
总计: ¥24.07
每人应付: ¥8.02
*/
💡 BigDecimal最佳实践总结:
- 除法运算总是指定精度和舍入模式
- 金融计算使用HALF_EVEN(银行家舍入)
- 避免与基本类型混用,防止精度丢失
- 构造方法选择:优先使用字符串构造,详见创建方式对比
常见陷阱与最佳实践
❌ 常见陷阱
陷阱1:空指针异常
Integer i = null;
int a = i; // NullPointerException!
// 解决方案:先检查是否为null
if (i != null) {
int a = i;
}
陷阱2:== vs equals() 混淆
Integer a = 1000, b = 1000;
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
// 最佳实践:包装类比较总是用equals()
if (a.equals(b)) { ... }
陷阱3:自动拆箱的性能问题
// 性能较差:频繁装箱拆箱
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i; // 每次都发生装箱拆箱!
}
// 性能更好:使用基本类型
long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
性能优化专题
内存占用对比
| 类型 | 基本类型大小 | 包装类大小 | 内存倍数 |
|---|---|---|---|
| byte/int | 1/4 字节 | 16 字节 | 16x/4x |
| long/double | 8 字节 | 16 字节 | 2x |
性能优化建议
✅ 何时使用包装类:
- 需要存储在集合中时
- 使用泛型编程时
- 需要表示null值时
- 使用包装类提供的工具方法时
✅ 何时使用基本类型:
- 数值计算密集的场景
- 数组存储大量数据时
- 对内存占用敏感的场景
- 不需要null值的场景
经典面试题精讲
面试题1:Integer对象比较
public class Test {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false
Integer e = new Integer(127);
Integer f = new Integer(127);
System.out.println(e == f); // false
}
}
考点: Integer缓存机制影响对象比较(详细缓存规则见"缓存机制详解")
关键点: 自动装箱在缓存范围内返回相同对象,new总是创建新对象
面试题2:自动装箱拆箱
public static void main(String[] args) {
Integer a = null;
int b = a; // 会出现什么异常?
}
// 答案:NullPointerException
// 原因:null调用intValue()方法
实战项目:数据分析工具
项目背景: 实现一个简单的数据分析工具,统计数字列表的各种统计信息
完整代码示例
import java.util.*;
import java.util.stream.*;
public class DataAnalyzer {
private List<Double> data;
public DataAnalyzer() {
this.data = new ArrayList<>();
}
// 添加数据点
public void addDataPoint(double value) {
data.add(value); // 自动装箱:double -> Double
}
// 计算平均值
public Double getAverage() {
if (data.isEmpty()) return null; // 可以返回null
return data.stream()
.mapToDouble(Double::doubleValue) // 拆箱操作
.average()
.orElse(0.0);
}
// 获取最大值
public Double getMax() {
return Collections.max(data); // 比较时使用Double.compareTo()
}
// 找出所有大于平均值的数
public List<Double> getAboveAverage() {
Double avg = getAverage();
return data.stream()
.filter(d -> d.compareTo(avg) > 0) // 使用包装类的比较方法
.collect(Collectors.toList());
}
public static void main(String[] args) {
DataAnalyzer analyzer = new DataAnalyzer();
// 添加测试数据
double[] testData = {1.5, 2.7, 3.1, 4.8, 2.2, 5.0, 1.9};
for (double num : testData) {
analyzer.addDataPoint(num);
}
System.out.println("平均值: " + analyzer.getAverage());
System.out.println("最大值: " + analyzer.getMax());
System.out.println("高于平均值的数: " + analyzer.getAboveAverage());
}
}
包装类知识点总结
核心概念对比表
| 概念 | 基本类型 | 包装类 | BigInteger/BigDecimal |
|---|---|---|---|
| 内存占用 | 1-8字节 | 16字节 | 24+字节 |
| 是否可为null | 否 | 是 | 是 |
| 支持运算符 | 是 | 自动拆箱 | 否,需用方法 |
| 泛型支持 | 否 | 是 | 是 |
最佳实践清单
✅ 推荐做法
- 包装类比较使用equals()
- 使用valueOf()而非new
- 注意空指针检查
- BigDecimal创建使用字符串(详见BigDecimal幻灯片)
- 集合和泛型使用包装类
❌ 避免做法
- 用==比较包装类
- 循环中频繁装箱拆箱
- 忽略NumberFormatException
- BigDecimal避免使用double构造(详见BigDecimal幻灯片)
- 用包装类做大量数值计算
练习题与思考
基础练习题
- 填空题: 自动装箱是将 类型转换为 类型
- 判断题: Integer a = 100; Integer b = 100; a == b 的结果是?为什么?
- 编程题: 写一个方法,将字符串数组转换为整数列表
进阶思考题
- 设计题: 如何设计一个货币计算类,避免浮点数精度问题?
- 性能题: 在什么场景下应该优先使用基本类型?
- 面试题: 解释Integer.valueOf()和new Integer()的区别
实战练习
挑战任务: 实现一个BigNumberCalculator类,支持任意精度的大数运算,包括加、减、乘、除、幂运算等操作。
要求:
- 使用BigInteger和BigDecimal
- 提供友好的API接口
- 包含异常处理
- 编写单元测试
1 / X