小熊奶糖(BearCandy)
小熊奶糖(BearCandy)
发布于 2025-10-11 / 0 阅读
0
0

Java包装类详解

Java包装类详解

一、包装类的作用

包装类(Wrapper Classes)是Java中一种特殊的类,它们的主要作用是:

1. 基本数据类型与对象的桥梁

  • 将基本数据类型(如int, double等)包装成对象
  • 使得基本数据类型可以像对象一样操作

2. 集合框架的必要性

  • Java集合框架(如ArrayList, HashMap等)只能存储对象
  • 包装类让基本数据类型可以存储在集合中

3. 提供实用方法

  • 提供类型转换、比较等实用方法
  • 如字符串与基本类型的相互转换

4. 支持null值

  • 基本数据类型不能为null,包装类可以表示"无值"状态

二、八种基本类型的包装类

基本类型 包装类 继承关系
byte Byte extends Number
short Short extends Number
int Integer extends Number
long Long extends Number
float Float extends Number
double Double extends Number
char Character 直接继承Object
boolean Boolean 直接继承Object

三、包装类的核心特性

1. 自动装箱与拆箱(Auto-boxing & Unboxing)

// JDK 1.5之前的手动装箱拆箱
Integer i1 = Integer.valueOf(100);  // 手动装箱
int x = i1.intValue();              // 手动拆箱

// JDK 1.5之后的自动装箱拆箱
Integer i2 = 100;      // 自动装箱:编译器转换为 Integer.valueOf(100)
int y = i2;            // 自动拆箱:编译器转换为 i2.intValue()

2. 常量池与缓存机制

public class WrapperExample {
    public static void main(String[] args) {
        // Integer缓存范围:-128 ~ 127
        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(100);
        Integer f = new Integer(100);
        System.out.println(e == f);  // false,不同对象
    }
}

四、常用方法详解

Integer类常用方法

public class IntegerMethods {
    public static void main(String[] args) {
        // 构造方法
        Integer i1 = new Integer(100);
        Integer i2 = new Integer("200");
    
        // 静态方法:字符串转int
        int num1 = Integer.parseInt("123");
        int num2 = Integer.parseInt("FF", 16);  // 按16进制解析
    
        // 静态方法:int转字符串
        String s1 = Integer.toString(456);
        String s2 = Integer.toBinaryString(10);  // 二进制字符串
        String s3 = Integer.toHexString(255);    // 十六进制字符串
    
        // 比较方法
        int compare = Integer.compare(10, 20);  // -1 (10 < 20)
    
        // 数值提取
        int value = i1.intValue();
        double dValue = i1.doubleValue();
    
        // 常量
        System.out.println("MAX_VALUE: " + Integer.MAX_VALUE);
        System.out.println("MIN_VALUE: " + Integer.MIN_VALUE);
        System.out.println("SIZE: " + Integer.SIZE);  // 32位
    }
}

Character类常用方法

public class CharacterMethods {
    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.isWhitespace(' '));// true
        System.out.println(Character.isUpperCase(ch));  // true
        System.out.println(Character.isLowerCase(ch));  // false
    
        // 字符转换
        System.out.println(Character.toLowerCase(ch));  // 'a'
        System.out.println(Character.toUpperCase('b')); // 'B'
    
        // 获取数值
        System.out.println(Character.getNumericValue('9')); // 9
    }
}

五、包装类的实际应用

1. 在集合中的使用

import java.util.*;

public class CollectionExample {
    public static void main(String[] args) {
        // List只能存储对象,不能存储基本类型
        List<Integer> list = new ArrayList<>();
        list.add(1);    // 自动装箱
        list.add(2);
        list.add(3);
    
        int first = list.get(0);  // 自动拆箱
    
        // Map中使用包装类
        Map<String, Integer> map = new HashMap<>();
        map.put("age", 25);      // 自动装箱
        map.put("score", 95);
    
        int age = map.get("age"); // 自动拆箱
    }
}

2. 处理可能为null的数值

public class NullHandling {
    public static void processScore(Integer score) {
        if (score == null) {
            System.out.println("成绩未录入");
        } else {
            System.out.println("成绩: " + score);
        }
    }
  
    public static void main(String[] args) {
        processScore(95);    // 成绩: 95
        processScore(null);  // 成绩未录入
    }
}

3. 类型转换工具

public class TypeConversion {
    public static void main(String[] args) {
        // 字符串与基本类型的转换
        String numberStr = "123.45";
        double number = Double.parseDouble(numberStr);
    
        String boolStr = "true";
        boolean flag = Boolean.parseBoolean(boolStr);
    
        // 基本类型转字符串
        String intStr = Integer.toString(100);
        String doubleStr = Double.toString(3.14);
    
        // 更简洁的方式(推荐)
        String intStr2 = String.valueOf(100);
        String doubleStr2 = String.valueOf(3.14);
    }
}

六、注意事项和最佳实践

1. 空指针异常

public class NullPointerExample {
    public static void main(String[] args) {
        Integer num = null;
    
        // 自动拆箱时会抛出NullPointerException
        try {
            int value = num;  // NullPointerException
        } catch (NullPointerException e) {
            System.out.println("捕获到空指针异常");
        }
    
        // 安全的处理方式
        if (num != null) {
            int safeValue = num;
        }
    }
}

2. 比较时的注意事项

public class ComparisonExample {
    public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;
        Integer c = 200;
        Integer d = 200;
    
        System.out.println(a == b);  // true (在缓存范围内)
        System.out.println(c == d);  // false (超出缓存范围)
    
        // 正确的比较方式:使用equals()或intValue()
        System.out.println(a.equals(b));     // true
        System.out.println(c.equals(d));     // true
        System.out.println(a.intValue() == b.intValue());  // true
    }
}

3. 性能考虑

public class PerformanceExample {
    public static void main(String[] args) {
        // 不推荐的写法:频繁创建包装对象
        long startTime = System.currentTimeMillis();
        Long sum = 0L;
        for (int i = 0; i < 1000000; i++) {
            sum += i;  // 每次循环都创建新的Long对象
        }
        long endTime = System.currentTimeMillis();
        System.out.println("包装类耗时: " + (endTime - startTime) + "ms");
    
        // 推荐的写法:使用基本类型
        startTime = System.currentTimeMillis();
        long sum2 = 0L;
        for (int i = 0; i < 1000000; i++) {
            sum2 += i;  // 直接操作基本类型
        }
        endTime = System.currentTimeMillis();
        System.out.println("基本类型耗时: " + (endTime - startTime) + "ms");
    }
}

七、总结

包装类在Java中扮演着重要的角色:

  1. 桥梁作用:连接基本类型和对象世界
  2. 功能扩展:为基本类型提供丰富的操作方法
  3. 集合支持:使得基本类型可以存储在集合框架中
  4. null支持:可以表示"无值"状态

在使用包装类时要注意:

  • 合理使用自动装箱拆箱
  • 注意缓存机制的范围
  • 警惕空指针异常
  • 在性能敏感的场景考虑使用基本类型

包装类是Java语言设计中一个优雅的解决方案,它既保留了基本类型的高效性,又提供了面向对象的灵活性。

Java包装类缓存机制详解

一、缓存范围概述

Java为部分包装类提供了缓存机制,主要是为了提高性能和减少内存占用

各包装类的缓存范围

包装类 缓存范围 备注
Byte -128 ~ 127 (全部) 所有byte值都在缓存范围内
Short -128 ~ 127
Integer -128 ~ 127 最常用的缓存
Long -128 ~ 127
Character 0 ~ 127 (ASCII字符)
Boolean TRUE, FALSE (两个值都缓存)
Float 无缓存
Double 无缓存

二、缓存机制源码分析

Integer缓存源码

public final class Integer extends Number implements Comparable<Integer> {
  
    // 内部缓存类
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];
      
        static {
            // 默认上限是127
            int h = 127;
            // 可以通过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);
                    // 最大不能超过Integer.MAX_VALUE - (-low) -1
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch(NumberFormatException nfe) {
                    // 忽略配置错误
                }
            }
            high = h;
          
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
        }
      
        private IntegerCache() {}
    }
  
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
}

三、缓存范围验证示例

1. Integer缓存验证

public class IntegerCacheDemo {
    public static void main(String[] args) {
        // 在缓存范围内 (-128 ~ 127)
        Integer a = 127;
        Integer b = 127;
        Integer c = -128;
        Integer d = -128;
      
        System.out.println("=== 缓存范围内 ===");
        System.out.println("127 == 127: " + (a == b));      // true
        System.out.println("-128 == -128: " + (c == d));    // true
      
        // 超出缓存范围
        Integer e = 128;
        Integer f = 128;
        Integer g = -129;
        Integer h = -129;
      
        System.out.println("=== 超出缓存范围 ===");
        System.out.println("128 == 128: " + (e == f));      // false
        System.out.println("-129 == -129: " + (g == h));    // false
      
        // 使用valueOf方法(自动装箱调用此方法)
        Integer i = Integer.valueOf(100);
        Integer j = Integer.valueOf(100);
        Integer k = Integer.valueOf(200);
        Integer l = Integer.valueOf(200);
      
        System.out.println("=== 使用valueOf方法 ===");
        System.out.println("100 == 100: " + (i == j));      // true
        System.out.println("200 == 200: " + (k == l));      // false
    }
}

2. 各包装类缓存对比

public class AllWrapperCacheDemo {
    public static void main(String[] args) {
        System.out.println("=== Byte缓存测试 ===");
        Byte b1 = 127;
        Byte b2 = 127;
        Byte b3 = -128;
        Byte b4 = -128;
        System.out.println("Byte 127 == 127: " + (b1 == b2));    // true
        System.out.println("Byte -128 == -128: " + (b3 == b4));  // true
      
        System.out.println("\n=== Short缓存测试 ===");
        Short s1 = 127;
        Short s2 = 127;
        Short s3 = 128;
        Short s4 = 128;
        System.out.println("Short 127 == 127: " + (s1 == s2));   // true
        System.out.println("Short 128 == 128: " + (s3 == s4));   // false
      
        System.out.println("\n=== Long缓存测试 ===");
        Long l1 = 127L;
        Long l2 = 127L;
        Long l3 = 128L;
        Long l4 = 128L;
        System.out.println("Long 127 == 127: " + (l1 == l2));    // true
        System.out.println("Long 128 == 128: " + (l3 == l4));    // false
      
        System.out.println("\n=== Character缓存测试 ===");
        Character c1 = 127;
        Character c2 = 127;
        Character c3 = 128;
        Character c4 = 128;
        System.out.println("Character 127 == 127: " + (c1 == c2)); // true
        System.out.println("Character 128 == 128: " + (c3 == c4)); // false
      
        System.out.println("\n=== Boolean缓存测试 ===");
        Boolean bool1 = true;
        Boolean bool2 = true;
        Boolean bool3 = false;
        Boolean bool4 = false;
        System.out.println("Boolean true == true: " + (bool1 == bool2));   // true
        System.out.println("Boolean false == false: " + (bool3 == bool4)); // true
    }
}

四、自定义缓存上限

通过JVM参数调整Integer缓存上限

public class CustomIntegerCache {
    public static void main(String[] args) {
        // 运行前添加JVM参数:-Djava.lang.Integer.IntegerCache.high=500
      
        System.out.println("测试自定义缓存上限");
      
        // 原本超出缓存的范围,现在在缓存内
        Integer a = 300;
        Integer b = 300;
        Integer c = 500;
        Integer d = 500;
        Integer e = 501;
        Integer f = 501;
      
        System.out.println("300 == 300: " + (a == b));    // 如果设置了high=500,则为true
        System.out.println("500 == 500: " + (c == d));    // 如果设置了high=500,则为true
        System.out.println("501 == 501: " + (e == f));    // false,超出缓存上限
    }
}

运行命令:

java -Djava.lang.Integer.IntegerCache.high=500 CustomIntegerCache

五、缓存机制的实际影响

1. 性能测试

public class CachePerformanceTest {
    public static void main(String[] args) {
        int iterations = 10_000_000;
      
        // 测试缓存范围内的性能
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < iterations; i++) {
            Integer value = i % 256 - 128;  // 保持在缓存范围内
        }
        long cacheTime = System.currentTimeMillis() - startTime;
      
        // 测试超出缓存范围的性能
        startTime = System.currentTimeMillis();
        for (int i = 0; i < iterations; i++) {
            Integer value = i + 1000;  // 超出缓存范围
        }
        long noCacheTime = System.currentTimeMillis() - startTime;
      
        System.out.println("缓存范围内耗时: " + cacheTime + "ms");
        System.out.println("超出缓存范围耗时: " + noCacheTime + "ms");
        System.out.println("性能差异: " + (noCacheTime - cacheTime) + "ms");
    }
}

2. 内存占用分析

public class MemoryUsageAnalysis {
    public static void main(String[] args) {
        // 创建大量Integer对象
        int size = 10000;
        Integer[] cachedIntegers = new Integer[size];
        Integer[] nonCachedIntegers = new Integer[size];
      
        // 使用缓存范围内的值
        for (int i = 0; i < size; i++) {
            cachedIntegers[i] = i % 256 - 128;  // 缓存范围内
        }
      
        // 使用超出缓存范围的值
        for (int i = 0; i < size; i++) {
            nonCachedIntegers[i] = i + 1000;  // 超出缓存范围
        }
      
        // 检查对象重用情况
        int cachedReuse = 0;
        for (int i = 1; i < size; i++) {
            if (cachedIntegers[i] == cachedIntegers[i-1]) {
                cachedReuse++;
            }
        }
      
        int nonCachedReuse = 0;
        for (int i = 1; i < size; i++) {
            if (nonCachedIntegers[i] == nonCachedIntegers[i-1]) {
                nonCachedReuse++;
            }
        }
      
        System.out.println("缓存范围内对象重用次数: " + cachedReuse);
        System.out.println("超出缓存范围对象重用次数: " + nonCachedReuse);
    }
}

六、最佳实践和注意事项

1. 正确的比较方式

public class CorrectComparison {
    public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;
        Integer c = 200;
        Integer d = 200;
      
        // 错误的比较方式(依赖缓存)
        System.out.println("a == b: " + (a == b));  // true,但依赖缓存
        System.out.println("c == d: " + (c == d));  // false,超出缓存
      
        // 正确的比较方式
        System.out.println("a.equals(b): " + a.equals(b));  // true
        System.out.println("c.equals(d): " + c.equals(d));  // true
      
        // 或者使用intValue()
        System.out.println("a.intValue() == b.intValue(): " + (a.intValue() == b.intValue()));  // true
      
        // 最佳实践:使用Objects.equals()
        System.out.println("Objects.equals(a, b): " + java.util.Objects.equals(a, b));  // true
        System.out.println("Objects.equals(c, d): " + java.util.Objects.equals(c, d));  // true
    }
}

2. 在集合中的使用

import java.util.*;

public class CollectionWithCache {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
      
        // 添加大量数据,观察内存使用
        for (int i = 0; i < 1000; i++) {
            // 使用缓存范围内的值可以节省内存
            list.add(i % 256 - 128);
        }
      
        // 检查列表中对象的唯一性
        Set<Integer> uniqueObjects = new HashSet<>();
        for (Integer num : list) {
            uniqueObjects.add(num);
        }
      
        System.out.println("列表大小: " + list.size());
        System.out.println("唯一对象数量: " + uniqueObjects.size());
        System.out.println("对象重用率: " + (1 - (double)uniqueObjects.size() / list.size()));
    }
}

七、总结

缓存机制的关键点:

  1. 范围固定:默认-128到127,Integer可通过JVM参数调整上限
  2. 性能优化:避免重复创建常用数值的包装对象
  3. 内存节省:相同数值共享对象引用
  4. 比较陷阱==比较在缓存范围内外表现不同

开发建议:

  • 始终使用 equals()方法比较包装类对象
  • 了解缓存范围,在性能敏感场景合理利用
  • 避免依赖缓存机制进行对象比较
  • 在大量使用包装类时考虑缓存范围对内存的影响

缓存机制是Java为了平衡性能和面向对象特性而设计的巧妙方案,理解其原理有助于编写更高效、健壮的代码。


评论