Skip to content

S03-02 核心类-包装类

[TOC]

包装类

在 Java 中,包装类(Wrapper Class) 是一个非常基础且核心的概念。Java 是一种面向对象的语言,但为了性能考量,它保留了 8 种基本数据类型(如 int, double 等),这些基本类型并不是对象。

为了让基本数据类型也能拥有对象的特征(例如参与面向对象的操作调用方法放入集合中),Java 为每一种基本数据类型都提供了一个对应的包装类

基本数据类型 vs 包装类

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

Java 的 java.lang 包中包含了 8 种基本数据类型的包装类。它们的对应关系非常简单,除了 intchar 之外,其他的只需将首字母大写即可:

基本数据类型 (Primitive Type)包装类 (Wrapper Class)父类
byteByteNumber
shortShortNumber
intIntegerNumber
longLongNumber
floatFloatNumber
doubleDoubleNumber
charCharacterObject
booleanBooleanObject

为什么需要包装类

为什么需要包装类:

既然有了基本数据类型,为什么还要多此一举设计包装类呢?主要有以下几个原因:

  1. 集合框架(Collections)的限制:Java 中的集合(如 List, Set, Map)只能存储对象,不能直接存储基本数据类型。如果你想创建一个包含整数的列表,只能写 List<Integer>,而不能写 List<int>

  2. 表示“空值”(null):基本数据类型有默认值(如 int 默认是 0),但无法表示“没有值”的状态。而在实际开发(尤其是数据库操作和 Web 请求)中,0 和 null 的意义完全不同。包装类作为对象,可以被赋值为 null

  3. 提供丰富的实用方法和常量:包装类中封装了大量有用的方法。例如,Integer.parseInt("123") 可以将字符串转为整数,Integer.MAX_VALUE 可以直接获取 int 类型的最大值。

自动装箱与自动拆箱

自动装箱(Autoboxing)与自动拆箱(Unboxing):

在 Java 5 之前,基本类型和包装类之间的转换需要手动进行,代码显得非常繁琐。从 Java 5 开始,引入了自动装箱自动拆箱机制。

  • 自动装箱:Java 编译器自动将基本数据类型转换为对应的包装类

    • 底层原理:编译器在编译时调用了包装类的 valueOf() 方法。
  • 自动拆箱:Java 编译器自动将包装类转换为对应的基本数据类型

    • 底层原理:编译器在编译时调用了包装类的 Number 超类的 xxxValue() 方法(如 intValue())。

示例

  1. Java 5 之前的写法(手动)

    java
    // Java 5 之前的写法(手动)
    Integer numObj = Integer.valueOf(100); // 装箱
    int num = numObj.intValue();           // 拆箱
  2. Java 5 之后的写法(自动装箱与拆箱)

    java
    // Java 5 之后的写法(自动装箱与拆箱)
    Integer a = 10;  // 自动装箱:等同于 Integer.valueOf(10);
    int b = a;       // 自动拆箱:等同于 a.intValue();
    
    // 甚至可以在数学运算中自动混用
    Integer x = 5; 
    Integer y = x + 5; // 先把 x 拆箱变成 5,相加得到 10,再把 10 自动装箱赋值给 y

内存结构变化

转换成包装类后,内存结构有如下变化

java
public static void main(String[] args) {
  int num = 520;
  Integer obj = new Integer(520);
}

image-20260317165958118

常量池缓存

包装类的常量池缓存机制(面试高频考点):

为了提高性能并减少内存空间的占用,Java 在 Byte, Short, Integer, Long, Character, Boolean 这几个包装类中实现了缓存机制(Cache)

以最常用的 Integer 为例,Java 内部维护了一个 IntegerCache,默认缓存了 -128 到 127 之间的 Integer 对象。

当你通过自动装箱(即 Integer.valueOf(i))创建一个在该范围内的对象时,Java 会直接从缓存池中返回已经存在的对象,而不会 new 一个新对象;如果超出这个范围,则会去堆内存中 new 一个新对象。

java
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true (都在 -128 ~ 127 范围内,指向缓存池中的同一个对象)

Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false (超出了范围,底层每次都 new 了一个新对象,内存地址不同)

注意DoubleFloat 并没有实现缓存机制,因为在任何范围内,浮点数都有无限多个。

包装类比较

包装类比较时的注意事项:

因为包装类是对象,所以在进行相等性比较时,必须非常小心:

  1. 比较值是否相等,必须使用 .equals() 方法,千万不要使用 ==(除非你在比较基本数据类型)。== 比较的是对象的内存地址,而 .equals() 被重写过,比较的是对象内部包裹的值。

    java
    Integer i1 = new Integer(10);
    Integer i2 = new Integer(10);
    System.out.println(i1 == i2);      // false (new 出来的对象地址一定不同)
    System.out.println(i1.equals(i2)); // true (值相等)
  2. 基本类型与包装类用 == 比较时,包装类会自动拆箱

    java
    int num1 = 200;
    Integer num2 = 200;
    System.out.println(num1 == num2); // true (num2 会自动拆箱为 int,然后比较数值)

常见类型转换

常见类型转换操作:

包装类在字符串与基本类型之间的转换中扮演着重要角色:

  1. 字符串转基本类型

    使用包装类的 parseXxx(String s) 方法(最常用):

    java
    int i = Integer.parseInt("123");
    double d = Double.parseDouble("3.14");
  2. 字符串转包装类对象

    使用 valueOf(String s) 方法:

    java
    Integer num = Integer.valueOf("123");
  3. 基本类型转字符串

    除了直接用 "" + 123 这种方式外,还可以用 String.valueOf()

    java
    String s1 = String.valueOf(123); // "123"

image-20260324152747528

自定义包装类

image-20260317170309954

练习题

练习题

利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。

提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。而向量类java.util.Vector可以根据需要动态伸缩。

1、创建Vector对象:Vector v=new Vector();

2、给向量添加元素:v.addElement(Object obj); //obj必须是对象

3、取出向量中的元素:Object obj=v.elementAt(0);
注意第一个元素的下标是0,返回值是Object类型的。

4、计算向量的长度:v.size();

5、若与最高分相差10分内:A等;20分内:B等;30分内:C等;其它:D等

Integer

在 Java 日常开发中,Integer 是使用频率极高的包装类。除了作为 int 的对象形态存在,它内部还封装了大量极其好用的静态方法和常量,涵盖了类型转换、进制转换、数值比较和位运算等场景。

常量

核心常量:

Integer 类提供了一些常量,可以让你直接获取 int 类型的物理属性,23112^{31}-1 避免硬编码:

常量名称描述实际值
Integer.MAX_VALUEint 类型能表示的最大值2147483647 (23112^{31}-1)
Integer.MIN_VALUEint 类型能表示的最小值-2147483648 (231-2^{31})
Integer.SIZEint 值在内存中占用的位数(Bit)32
Integer.BYTESint 值在内存中占用的字节数(Byte)4 (Java 8 引入)

(提示:在求数组最大值/最小值算法的初始化时,经常会用到 MIN_VALUEMAX_VALUE。)

方法

字符串转数字

这是日常开发中最频繁的操作,主要处理前端传来的字符串数据或进行日志打印。

  • int Integer.parseInt()(String s),将字符串解析为基本数据类型 int。如果字符串不是合法的数字(例如包含字母),会抛出 NumberFormatException

    java
    int num = Integer.parseInt("1024");
  • int Integer.parseInt()(String s, int radix),将指定进制的字符串解析为十进制的 int

    java
    int num = Integer.parseInt("FF", 16); // 将 16 进制的 FF 转为 10 进制,结果是 255
  • Integer Integer.valueOf()(String s),将字符串解析为包装类 Integer 对象。底层其实就是调用了 parseInt,然后再进行装箱。

数字转字符串

  • String Integer.toString()(int i),将基本类型 int 转换为字符串。

    java
    String s = Integer.toString(100); // 相当于 String.valueOf(100)

进制转换

如果你需要将一个十进制整数打印成二进制、八进制或十六进制字符串,Integer 提供了直接的静态方法,无需自己写取余算法:

  • String Integer.toBinaryString()(int i),转为二进制字符串。

  • String Integer.toOctalString()(int i),转为八进制字符串。

  • String Integer.toHexString()(int i),转为十六进制字符串。

    java
    int num = 28;
    System.out.println(Integer.toBinaryString(num)); // "11100"
    System.out.println(Integer.toHexString(num));    // "1c"

对象的创建与缓存

  • Integer Integer.valueOf()(int i),这是目前官方唯一推荐的创建 Integer 对象的方式。

    避坑指南:自 Java 9 起,构造方法 new Integer(10) 已被标记为废弃(Deprecated)。因为 valueOf 方法内部使用了前面提到的 -128 到 127 的缓存池机制,性能更好,内存占用更低。

大小比较

  • int compareTo()(Integer anotherInteger),实例方法。比较两个 Integer 对象的值。返回 0(相等)、< 0(小于)、> 0(大于)。

  • int Integer.compare()(int x, int y),静态方法。直接比较两个基本类型 int 的大小。返回值同上。

    java
    System.out.println(Integer.compare(10, 20)); // 返回 -1

数学运算@J8

为了配合 Lambda 表达式和 Stream API(如 reduce 操作),Java 8 在 Integer 中新增了几个静态算术方法:

  • int Integer.max()(int a, int b),返回两者中较大的值。

  • int Integer.min()(int a, int b),返回两者中较小的值。

  • int Integer.sum()(int a, int b),返回两数之和

底层位运算

在一些追求极致性能的算法、源码或密码学领域,经常会用到 Integer 提供的位级操作方法:

  • int Integer.bitCount()(int i),返回指定 int 值的二进制补码表示中,1 的个数。(LeetCode 常考题)。

    java
    // 7 的二进制是 0000 0111,里面有 3 个 1
    System.out.println(Integer.bitCount(7)); // 输出: 3
  • int Integer.reverse()(int i),将二进制位的顺序完全反转。

  • int Integer.highestOneBit()(int i),保留最高位的 1,其余全置为 0(常用于 HashMap 容量计算的底层逻辑中)。

总结避坑

当你在业务代码中比较两个 Integer 对象的值是否相等时,永远使用 .equals() 方法,绝不要使用 ==。这是因为缓存池(-128 ~ 127)的存在会让 == 在小数值时表现正常,一旦超过这个范围,== 就会因为内存地址不同而返回 false,引发难以排查的 Bug。

Character

在 Java 中,Character 是基本数据类型 char 的包装类。由于 char 在 Java 中占用 2 个字节(16 位)本质上是一个无符号的整数(对应 Unicode 字符集),因此 Character 类不仅提供了对象化的包装,还封装了大量用于字符分类、判断和转换的实用静态方法。

方法

字符类型判断

在处理字符串解析、表单验证或算法题(如验证回文串)时,我们经常需要判断一个字符是字母、数字还是空格。Character 提供了一系列 isXxx() 方法:

方法声明(static int)功能描述示例与返回值
isDigit(char ch)判断是否为数字 (0-9)isDigit('5') \rightarrow true
isLetter(char ch)判断是否为字母 (包含中文字符等 Unicode 字母)isLetter('A') \rightarrow true
isLetter('中') \rightarrow true
isLetterOrDigit(char ch)判断是否为字母或数字isLetterOrDigit('!') \rightarrow false
isWhitespace(char ch)判断是否为空白字符(空格、Tab \t、换行 \n 等)isWhitespace(' ') \rightarrow true
isUpperCase(char ch)判断是否为大写字母isUpperCase('a') \rightarrow false
isLowerCase(char ch)判断是否为小写字母isLowerCase('A') \rightarrow false

注意isLetter 不仅仅识别 26 个英文字母,它基于 Unicode 标准,因此判断汉字等其他语言的字母字符时也会返回 true

大小写转换

如果需要统一字符的格式,可以使用以下两个转换方法。如果传入的字符本身不是字母,或者已经是要转换的大小写,方法会直接返回原字符:

  • char toUpperCase()(char ch),将字符转换为大写

  • char toLowerCase()(char ch),将字符转换为小写

java
char c1 = Character.toUpperCase('a'); // 返回 'A'
char c2 = Character.toLowerCase('B'); // 返回 'b'
char c3 = Character.toUpperCase('5'); // 返回 '5' (非字母不改变)

字符与数字的转换

这是一个非常常见的易错点:如果你有一个字符 '9',直接强制转换为 int,得到的是它的 ASCII 码值 57,而不是数字 9

  • int getNumericValue()(char ch),获取字符对应的真实数值。返回指定字符表示的 int 值。

    java
    char ch = '9';
    System.out.println((int) ch); // 输出 57 (这是 ASCII 码)
    System.out.println(Character.getNumericValue(ch)); // 输出 9 (这是真实的数值)

    进阶小技巧(减去 '0'):

    在算法中,更高效且常见的做法是直接利用字符的 ASCII 码差值来获取数字:

    java
    char ch = '7';
    int num = ch - '0'; // 底层是 55 - 48,结果就是 7。比调用方法更快!

缓存机制与对象创建

Character 的缓存机制与对象创建:

Integer 类似,Character 也有为了节省内存而设计的常量池缓存机制

  • 缓存范围Character 缓存了从 \u0000\u007F(即 0 到 127)的字符,这刚好覆盖了所有标准的 ASCII 字符

  • 推荐创建方式Character.valueOf(char c)

    自 Java 9 起,new Character('A') 同样被废弃,官方推荐使用 valueOf()

java
Character c1 = Character.valueOf('A'); // ASCII 为 65
Character c2 = Character.valueOf('A');
System.out.println(c1 == c2); // true (命中 0-127 的缓存池,地址相同)

Character c3 = Character.valueOf('中'); // Unicode 远超 127
Character c4 = Character.valueOf('中');
System.out.println(c3 == c4); // false (超出缓存范围,创建了新对象)
System.out.println(c3.equals(c4)); // true (值相等)

处理复杂字符

处理复杂字符(Emoji 与补充字符):

这是 Character 类较深的应用场景。标准的 char 是 16 位的,只能表示 Unicode 的基本多语言面(BMP)。但是,像 Emoji 表情(如 😭)或者某些生僻字的 Unicode 编码超过了 16 位,一个 char 是存不下的。

因此,Java 使用两个 char(称为代理对,Surrogate Pair) 来表示这些复杂的字符。

为了处理这种情况,Character 提供了支持传入 int codePoint(代码点,即真实的 Unicode 编码数值)的重载方法:

  • static boolean isSupplementaryCodePoint(int codePoint):判断是否为增补字符(如 Emoji)。
  • static int charCount(int codePoint):判断该字符需要几个 char 来表示(通常是 1 或 2)。