Java的包装类

Java的包装类

为什么需要包装类

Java 作为典型的面向对象(Object-Oriented Programming, OOP)编程语言,其语法体系中包含 8 种基本数据类型(byte、short、int、long、float、double、char、boolean)。这些基本数据类型属于值类型,并非对象实例,它们无自身的属性和方法,也不具备继承、多态等面向对象核心特性,因此无法直接参与面向对象范式下的操作。

Java 标准库中的大量核心组件均基于对象设计,仅支持处理对象实例,无法直接存储或操作基本数据类型。为填补这一适配性缺口,Java 提供了包装类:包装类将每种基本数据类型封装为对应的类对象,使基本数据类型能够以引用类型的形式参与面向对象的操作体系。

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

所有包装类均位于 java.lang 包下,无需手动导入,对应关系如下:

每个基本数据类型都有其对应的引用数据类型包装类:

基本数据类型 包装类 特殊说明
byte Byte 全缓存(-128~127)
short Short 缓存 - 128~127
int Integer 缓存 - 128~127(可配置上限)
long Long 缓存 - 128~127
float Float 无缓存
double Double 无缓存
char Character 缓存 0~127(ASCII 字符)
boolean Boolean 缓存 TRUE/FALSE 两个常量

除 Integer/Character 外,其余包装类名称均为基本类型首字母大写

装箱与拆箱

自动拆箱: Java 编译器自动将包装类对象转换为其对应的基本数据类型的过程

java 复制代码
Integer numObj = 10; 
int num = numObj;

int num = numObj; 编译器实际做的是 int num = numObj.intValue();,调用的是 Integer.intValue(); 方法

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

java 复制代码
int num = 10;
Integer numObj = num; 

Integer numObj = 10; 编译器实际做的是 Integer numObj = Integer.valueOf(10);,调用的是 Integer.valueOf(); 方法

极大简化了在基本类型和包装类型之间转换的代码,使代码更简洁、易读,它模糊了基本类型和对象类型在代码中的界限。

包装类属于引用类型,其引用变量可以被赋值为 null(无指向任何对象实例);而自动拆箱的底层机制是 JVM 自动调用包装类对应的实例方法(如 Integer 调用 intValue()、Double 调用 doubleValue()、Boolean 调用 booleanValue() 等,统称 xxxValue() 方法)。若包装类引用变量的值为 null,意味着该引用未指向任何包装类对象实例,此时调用 xxxValue() 这一实例方法会直接触发 NullPointerException。

不可变性

Java 中所有包装类均属于不可变类(Immutable Class),即包装类对象实例一旦创建完成,其内部承载的核心数值便无法被修改。

包装类的不可变性通过以下核心设计机制严格保障:

首先是包装类用于存储数值的核心成员变量均被 private final 双重关键字修饰

如 java.lang.Integer 的源码定义:

java 复制代码
public final class Integer extends Number implements Comparable<Integer> {
    // 核心数值成员
    private final int value;

    // 构造方法
    public Integer(int value) {
        this.value = value;
    }
}

private 修饰:核心数值成员仅允许在包装类内部访问,外部代码无法直接读取或修改该数值;

final 修饰:对于基本类型的 final 成员变量,其值在通过构造方法完成初始化后便永久固定,不存在被重新赋值的可能;

无公共修改接口:包装类未提供任何如 setValue() 之类的公共方法,不存在通过方法修改核心数值的路径。

其次是类的不可继承性

所有包装类均被 final 关键字修饰(如 final class Integer),使其无法被继承,这一设计避免了子类通过重写方法篡改核心数值的逻辑,进一步加固了包装类的不可变性。

第三是引用重定向而非对象修改

当对指向包装类对象的引用变量执行赋值操作时,并非改变原有包装类对象的核心数值,而是完成以下过程:

创建一个承载新数值的包装类对象实例,将原引用变量的指向从原有对象切换至该新对象,原有包装类对象若不再被任何引用变量指向,会被 Java 垃圾回收机制标记为可回收,最终被清理。

128陷阱

"128 陷阱" 是 Java 中 Integer 包装类因缓存机制导致的典型现象:

当通过自动装箱(底层调用 Integer.valueOf())创建值为 -128~127 的 Integer 对象时,JVM 会复用缓存池中的对象;而创建值为 128(及超出 -128~127 范围)的 Integer 对象时,会新建对象实例。此时用 ==(比较引用地址)比较值为 128 的两个 Integer 对象,会出现数值相等但引用不等,返回 false 的结果,这一现象被称为 "128 陷阱"。

Integer 类中的源码如下:

java 复制代码
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            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);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

从源码看出:

Integer 类内部维护了一个静态的 Integer 对象缓存池,这个缓存池默认缓存了 -128 到 127 之间的所有整数对象。Integer.valueOf(int i) 是创建 Integer 对象的核心静态方法,其执行逻辑完全依赖缓存池,如果 i 的值在 -128 到 127 的范围内,方法会直接返回缓存池中预先创建好的对应的 Integer 对象的引用。如果 i 的值超出了这个范围,方法会 new 一个全新的 Integer 对象并返回这个新对象的引用。

equals()

Integer 内重写了 equals() 方法,源码如下:

由源码我们可以知道,Integer 的 equals() 方法是将其拆箱为基本数据类型,比较数值相等性

我们看这样一份代码:

下面分析输出结果:

  • System.out.println(a == b); → true:a 和 b 是基本类型 int,== 比较值的相等性;
  • System.out.println(a1 == b1); → true:a1 和 b1 通过自动装箱赋值相当于 Integer.valueOf(10),由于 10 在 Integer 缓存范围内(-128~127),所以 a1 和 b1 指向同一个缓存对象,== 比较对象引用地址;
  • System.out.println(a2 == b2); → false,new Integer(10) 显式创建新对象,无论值是否在缓存范围内,a2 和 b2 指向堆中两个不同对象,== 比较引用地址;
  • System.out.println(a1 == a); → true:a1 是 Integer 对象,a 是基本类型 int,当包装类与基本类型用 == 比较时,触发自动拆箱 a1.intValue(),实际比较值的相等性;
  • System.out.println(a1.equals(a)); → true:a1.equals(a) 调用 Integer.equals() 方法,触发自动拆箱 a1.intValue(),equals() 比较值的相等性;
  • System.out.println(a1 == a2); → false:a1 指向缓存对象,a2 指向显式创建的堆对象,== 比较对象引用地址;
  • System.out.println(a == a2); → true:a 是基本类型,a2 是包装类对象,触发自动拆箱 a2.intValue(),实际比较值相等。
相关推荐
aloha_7892 小时前
python基础面经八股
开发语言·python
李少兄2 小时前
从一篇IDEA笔记开始,我走出了自己的技术创作路
java·笔记·intellij-idea
鹿角片ljp2 小时前
力扣26.有序数组去重:HashSet vs 双指针法
java·算法
雾岛听蓝2 小时前
C++:模拟实现string类
开发语言·c++
SweetCode2 小时前
汉诺塔问题
android·java·数据库
superman超哥2 小时前
Rust Cargo Run 与 Cargo Test 命令:开发工作流的双引擎
开发语言·后端·rust·cargo run·cargo test·开发工作流·双引擎
p&f°2 小时前
Java面试题(全)自用
java·开发语言
爬山算法2 小时前
Hibernate(9)什么是Hibernate的Transaction?
java·后端·hibernate
Craaaayon2 小时前
深入浅出 Spring Event:原理剖析与实战指南
java·spring boot·后端·spring