基本类型包装类

面向对象程序设计(Java)

掌握包装类的使用,让基本类型支持面向对象操作

包装类概述与继承体系

包装类的基本概念:Java并不是纯面向对象的语言,基本数据类型不是面向对象的。Java提供了基本类型包装类,使得基本类型能够支持对象操作!

基本类型与包装类对应关系:

  • byte → Byte
  • boolean → Boolean
  • short → Short
  • char → Character
  • int → Integer
  • long → Long
  • float → Float
  • double → Double

包装类继承体系

Number抽象类: Number 类是 Byte, Short, Integer, Long, Float, Double 这些数字包装类的抽象父类。

Wrapper class hierarchy

为什么需要包装类?

  • 泛型支持: 泛型参数必须使用对象类型
  • 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幻灯片)
  • 用包装类做大量数值计算

练习题与思考

基础练习题

  1. 填空题: 自动装箱是将    类型转换为    类型
  2. 判断题: Integer a = 100; Integer b = 100; a == b 的结果是?为什么?
  3. 编程题: 写一个方法,将字符串数组转换为整数列表

进阶思考题

  1. 设计题: 如何设计一个货币计算类,避免浮点数精度问题?
  2. 性能题: 在什么场景下应该优先使用基本类型?
  3. 面试题: 解释Integer.valueOf()和new Integer()的区别

实战练习

挑战任务: 实现一个BigNumberCalculator类,支持任意精度的大数运算,包括加、减、乘、除、幂运算等操作。

要求:

  • 使用BigInteger和BigDecimal
  • 提供友好的API接口
  • 包含异常处理
  • 编写单元测试
1 / X