java中的自动拆装箱

本文探讨了Java中自动装箱与拆箱的特点及潜在问题,特别是NullPointerException的产生原因,并提供了实例说明。
今天调试程序的时候,有段代码报错,日志报空指针,但是在空指针那行看了半天,都不知道哪里来的空指针。。。然后一步步调试发现是由于对包装类赋值null,在自动拆箱时会报空指针问题。

代码类似下面(Student类中id是Long类型):
public static void main(String[] args) {
        Student student = new Student();
        student.setId(null);
        student.setName("12");
        if (student == null || 0 == student.getId()) {
            System.out.println("null 返回");
            return;
        }
        System.out.println("继续执行");
    }

自动拆箱的过程是这样的 首先定义Long id =22L; 
long baseNum= id;//这里实际的操作是 long baseNum = id.longValue();如果此时id是null就会抛出NullPointerException.




以下是看到的关于自动拆装箱讲的比较详细的,记录。http://mazhuang.org/2017/08/20/java-auto-boxing-unboxing/

自动装箱与拆箱

自动装箱与拆箱是 Java 1.5 引入的新特性,是一种语法糖。

在此之前,我们要创建一个值为 10 的 Integer 对象,只能写作:Integer value = new Integer(10);

而现在,我们可以更方便地写为:Integer value = 10;

定义与实现机制

自动装箱,是指从基本数据类型值到其对应的包装类对象的自动转换。比如 `Integer value = 10;`,是通过调用 Integer.valueOf 方法实现转换的。

自动拆箱,是指从包装类对象到其对应的基本数据类型值的自动转换。比如 `int primitive = value;`,是通过调用 Integer.intValue 方法实现转换的。

基本数据类型包装类型装箱方法拆箱方法
booleanBooleanBoolean.valueOf(boolean)Boolean.booleanValue()
byteByteByte.valueOf(byte)Byte.byteValue()
charCharacterCharacter.valueOf(char)Character.charValue()
shortShortShort.valueOf(short)Short.shortValue()
intIntegerInteger.valueOf(int)Integer.intValue()
longLongLong.valueOf(long)Long.longValue()
floatFloatFloat.valueOf(float)Float.floatValue()
doubleDoubleDouble.valueOf(double)Double.doubleValue()

发生时机
自动装箱与拆箱主要发生在以下四种时机:
1. 赋值时;
2. 比较时;
3. 算术运算时;
4. 方法调用时。


常见应用场景
Case 1:
Integer value = 10; // 自动装箱(赋值时)

int primitive = value; // 自动拆箱(方法调用时)

Case 2:
Integer value = 1000;
// ...
if (value <= 1000) { // 自动拆箱(比较时)
    // ...
}

Case 3:
List<Integer> list = new ArrayList<>();
list.add(10); // 自动装箱(方法调用时)

int i = list.get(0); // 自动拆箱(赋值时)

注:集合(Collections)里不能直接放入原始类型,集合只接收对象。
Case 4:
ThreadLocal<Integer> local = new ThreadLocal<Integer>();
local.set(10); // 自动装箱(方法调用时)

int i = local.get(); // 自动拆箱(赋值时)
注:ThreadLocal 不能存储基本数据类型,只接收引用类型。
Case 5:
public void fun1(Integer value) {
    //
}

public void fun2(int value) {
    //
}

public void test() {
    fun1(10); // 自动装箱(方法调用时)

    Integer value = 10;
    fun2(value); // 自动拆箱(方法调用时)
}

Case 6:
Integer v1 = new Integer(10);
Integer v2 = new Integer(20);
int v3 = 30;

int sum = v1 + v2; // 自动拆箱(算术运算时)
sum = v1 + 30; // 自动拆箱(算术运算时)

相关知识点

比较

除 `==` 以外,包装类对象与基本数据类型值的比较,包装类对象与包装类对象之间的比较,都是自动拆箱后对基本数据类型值进行比较,所以,**要注意这些类型间进行比较时自动拆箱可能引发的 NullPointerException**。

`==` 比较特殊,因为可以用于判断左右是否为同一对象,所以两个包装类对象之间 `==`,会用于判断是否为同一对象,而不会进行自动拆箱操作;包装类对象与基本数据类型值之间 `==`,会自动拆箱。
示例代码:

Integer v1 = new Integer(10);
Integer v2 = new Integer(20);

if (v1 < v2) { // 自动拆箱
    // ...
}

if (v1 == v2) { // 不拆箱
    // ...
}

if (v1 == 10) { // 自动拆箱
    // ...
}

缓存

Java 为整型值包装类 Byte、Character、Short、Integer、Long 设置了缓存,用于存储一定范围内的值,详细如下:

在一些情况下,比如自动装箱时,如果值在缓存值范围内,将不创建新对象,直接从缓存里取出对象返回,比如:
类型缓存值范围
Byte-128 ~ 127
Character0 ~ 127
Short-128 ~ 127
Integer-128 ~ 127(可配置)
Long-128 ~ 127

在一些情况下,比如自动装箱时,如果值在缓存值范围内,将不创建新对象,直接从缓存里取出对象返回,比如:

Integer v1 = 10;
Integer v2 = 10;
Integer v3 = new Integer(10);
Integer v4 = 128;
Integer v5 = 128;
Integer v6 = Integer.valueOf(10);

System.out.println(v1 == v2); // true
System.out.println(v1 == v3); // false
System.out.println(v4 == v5); // false
System.out.println(v1 == v6); // true

缓存实现机制:

这里使用了设计模式享元模式。

以 Short 类实现源码为例:
// ...
public final class Short extends Number implements Comparable<Short> {
    // ...
    private static class ShortCache {
        private ShortCache(){}

        static final Short cache[] = new Short[-(-128) + 127 + 1];

        static {
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Short((short)(i - 128));
        }
    }

    // ...
    public static Short valueOf(short s) {
        final int offset = 128;
        int sAsInt = s;
        if (sAsInt >= -128 && sAsInt <= 127) { // must cache
            return ShortCache.cache[sAsInt + offset];
        }
        return new Short(s);
    }
    // ...
}

在第一次调用到 `Short.valueOf(short)` 方法时,将创建 -128 ~ 127 对应的 256 个对象缓存到堆内存里。
这种设计,在频繁用到这个范围内的值的时候效率较高,可以避免重复创建和回收对象,否则有可能闲置较多对象在内存中。

使用不当的情况

自动装箱和拆箱这种语法糖为我们写代码带来了简洁和便利,但如果使用不当,也有可能带来负面影响。

1. 性能的损耗
Integer sum = 0;
for (int i = 1000; i < 5000; i++) {
    // 1. 先对 sum 进行自动拆箱
    // 2. 加法
    // 3. 自动装箱赋值给 sum,无法命中缓存,会 new Integer(int)
    sum = sum + i;
}

在循环过程中会分别调用 4000 次 Integer.intValue() 和 Integer.valueOf(int),并 new 4000 个 Integer 对象,而这些操作将 sum 的类型改为 int 即可避免,节约运行时间和空间,提升性能。


2. java.lang.NullPointerException


尝试对一个值为 null 的包装类对象进行自动拆箱,就有可能造成 NullPointerException。


比如:

Integer v1 = null;
int v2 = v1; // NullPointerException

if (v1 > 10) { // NullPointerException
    // ...
}

int v3 = v1 + 10; // NullPointerException

还有一种更隐蔽的情形,感谢 @周少 补充:

public class Test {
    public static void main(String[] args) {
        long value = true ? null : 1; // NullPointerException
    }
}

这实际上还是对一个值为 null 的 Long 类型进行自动拆箱,反汇编代码:

Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: aconst_null
       1: checkcast     #2                  // class java/lang/Long
       4: invokevirtual #3                  // Method java/lang/Long.longValue:()J
       7: lstore_1
       8: return
}


<think>我们正在解决升级到Java 21后,在IDEA中自动拆装箱代码出现红色波浪线(报错)的问题。根据引用内容,我们注意到一些常见的解决方法,如检查Maven配置、更新依赖、重新导入项目等,但这里的问题可能更具体地涉及到Java版本和IDEA的配置。 用户的问题背景:升级到Java 21后,自动拆装箱的代码出现红色波浪线(报错)。自动拆装箱Java的一个特性,例如将int自动装箱为Integer,或者反之。在Java 21中,这个特性仍然存在,所以问题可能在于IDEA没有正确识别Java 21的配置。 根据引用[2]中的内容,提到了一些配置步骤,例如设置项目结构中的Java版本和配置SDK。因此,我们可以尝试以下步骤: 步骤1:检查项目的Java版本设置 - 在IDEA中,打开File -> Project Structure(或者使用快捷键Ctrl+Alt+Shift+S)。 - 在Project Settings -> Project 中,确保Project SDK选择了Java 21,并且Project language level也选择了相应的版本(例如21)。 步骤2:检查模块的Java版本 - 在同一个Project Structure窗口中,选择Modules,然后检查Sources标签页下的Language level是否设置为21(或者与项目一致)。 步骤3:检查SDK设置 - 同样在Project Structure中,在Platform Settings -> SDKs,确保Java 21的SDK已经正确添加。如果没有,可以点击"+"添加JDK,并指定Java 21的安装路径。 步骤4:检查编译器设置 - 打开File -> Settings(或者Preferences)-> Build, Execution, Deployment -> Compiler -> Java Compiler。 - 确保每个模块的Target bytecode version都是21。 步骤5:重新导入项目(如果是Maven项目) - 如果项目是Maven项目,可以尝试重新导入:在Maven工具窗口中(一般在右侧),点击Reimport All Maven Projects(一个刷新图标)。 步骤6:使缓存失效并重启 - 有时候IDEA的缓存会导致问题。可以尝试File -> Invalidate Caches...,然后选择Invalidate and Restart。 步骤7:检查代码辅助设置 - 引用[1]中提到,可以通过Alt+Enter来快速修复。在报错的地方,按Alt+Enter,看看是否有选项可以解决,比如选择“Set language level to 21”或者更新依赖等。 步骤8:检查依赖冲突 - 如果项目中有多个模块,确保所有模块都使用了相同的Java版本。 步骤9:更新IDEA - 确保使用的IDEA版本支持Java 21。如果IDEA版本过旧,可能不支持Java 21的新特性,需要升级到最新版本。 步骤10:检查Java 21的安装 - 确保Java 21已经正确安装在系统中,并且环境变量(如JAVA_HOME)指向了Java 21。 如果以上步骤都不能解决问题,可能需要检查具体的报错信息,看是否与某个特定的自动拆装箱代码有关,或者是否是IDEA的某个插件导致的。 根据引用中的提示,我们还可以尝试在External Libraries中查看是否添加了Java 21的SDK。 注意:自动拆装箱是一个基础特性,在Java 21中应该完全支持。因此,问题很可能出在配置上。 希望这些步骤能帮助您解决问题。 </think>针对升级Java 21后IDEA中自动拆装箱代码爆红的问题,以下是系统化的解决方案: ### 核心解决步骤 1. **验证JDK配置** - 打开 `File > Project Structure > Project Settings > Project` - 确认 **Project SDK** 选择的是 Java 21(若缺失需添加路径) - 设置 **Project language level** 为 `21` - ```mermaid graph TD A[打开Project Structure] --> B[设置Project SDK为Java21] B --> C[Language level调整为21] ``` 2. **检查模块语言级别** - 在 `Project Structure > Modules > Sources 标签页` - 确保每个模块的 **Language level** 均为 `21` - 引用[2]案例:新下载IDEA需同步编译器版本[^2] 3. **更新编译器设置** - 进入 `Settings > Build, Execution, Deployment > Compiler > Java Compiler` - 将所有模块的 **Target bytecode version** 改为 `21` - 勾选 `--enable-preview`(若使用预览特性) 4. **重建缓存**(关键步骤) - 执行 `File > Invalidate Caches > Invalidate and Restart` - 重启后IDEA会重建索引,解决语法解析错误 5. **检查依赖兼容性** - 若使用Maven/Gradle: - 更新 `pom.xml/build.gradle` 中的 `<java.version>21</java.version>` - 执行 **Reload All Maven Projects**(Maven面板刷新按钮) - 引用[1]提示:确认本地仓库存在Java 21相关jar包[^1] ### 特殊场景处理 - **语法检查误报**: 在爆红代码处按 `Alt+Enter` > 选择 `Disable inspection` 临时关闭检查(慎用) - **SDK异常**: 打开 `Project Structure > Platform Settings > SDKs` 删除旧JDK残留项,仅保留Java 21条目 > **注意**:Java 21正式支持自动拆装箱,此问题多为IDE配置未同步导致。若仍存在报错,检查是否启用了`Lombok`等注解处理器,需同步更新至兼容Java 21的版本。 --- ### 相关问题 1. 如何验证IDEA是否正确识别了Java 21的新特性? 2. 升级Java 21后Lombok注解失效该如何解决? 3. Maven项目如何强制指定所有模块使用Java 21编译?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值