包装类这颗语法糖,其实并不甜

2023-11-12

历史文章推荐:

  1. 你真的了解时间吗
  2. 细数ThreadLocal三大坑,内存泄露仅是小儿科
  3. Java 8 ConcurrentHashMap源码中竟然隐藏着两个BUG
  4. ConcurrentHashMap中有十个提升性能的细节,你都知道吗?
  5. HashMap面试,看这一篇就够了
  6. 七种方式教你在SpringBoot初始化时搞点事情

包装类在Java 5中和泛型一起引入,引入包装类的原因有两点:

  1. 解决无法创建基本类型泛型集合的问题
  2. 加入对基本类型为null这个语义的支持

并提供boxingunboxing的语法糖,让编译器支持基本类型和包装类的自动转化,减少开发者的工作量。但是经常有同学因为误用包装类导致惨烈的线上问题,在使用包装类的时候务必需要注意一下四点:

  1. 与基础类截然不同的==equals语义
  2. 糟糕的性能
  3. 不易察觉的NPE问题
  4. 令人疑惑的API设计

1. 相等还是不相等?这是个问题

比如以下代码片段

class Biziclop {
    public static void main(String[] args) {
        System.out.println(new Integer(5) == new Integer(5)); // false
        System.out.println(new Integer(500) == new Integer(500)); // false

        System.out.println(Integer.valueOf(5) == Integer.valueOf(5)); // true
        System.out.println(Integer.valueOf(500) == Integer.valueOf(500)); // false
    }
}

第一个和第二个语句返回false是比较容易理解的,因为对于Java中的对象调用=其实是在比较对象在堆上的地址,由于两个对象都是新建的,所以地址肯定不等,返回false。比较令人疑惑的是第三个语句,按照我们前面的分析,应该也返回false才对,但其实Integer.valueOf(5) == Integer.valueOf(5)比较的结果是true,这是因为JVM缓存了-128-127的整数,所以当数值在这个区间的时候,返回的对象都是同一个的。第四个语句因为数值已经不再-128-127的区间范围,所以返回了false

上面的这几个例子都是比较经典的例子,大家比较熟悉,一般也比较难掉坑里,但是下面的几个例子就比较有迷惑性了

class Biziclop {
    	public static void main(String[] args) {
        List<Long> list = new ArrayList<>();
        list.add(Long.valueOf(200));
        System.out.println(list.contains(200)); // false
        
        Long temp = 0L;
				System.out.println(temp.equals(0)); // false
       System.out.orintln(0==0L); // true
	}
}

原因在于

public boolean equals(Object obj) {
    if (obj instanceof Long) {
      return value == ((Long)obj).longValue();
    }
    return false;
}

包装类重写了equals方法,导致包装类即便是调用equals方法比较大小,也会和基本类型出现不一致的结果。与基础类截然不同的==equals语义经常会导致代码走到非期望的分支,再配上JVM对数字独特的缓存策略,极容易出现测试环境和正式环境不一样的运行结果。

2. 糟糕的性能

Effective Java》中有如下的例子:

public static void main(String[] args) {
    Long sum = 0L; // uses Long, not long
    for (long i = 0; i <= Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println(sum);
}

这段代码的耗时比使用基本类型long的版本慢6倍(声明变量sum 类型为 Long 的耗时是43秒, 如果声明变量sum为基本类型long,则耗时6.8秒)。导致这样的原因是包装类要经过在堆上开辟内存空间,初始化,内存寻址以及数据载入寄存器的过程,性能差也就不足为奇了。因此Joshua Bloch对开发者的建议是:Avoid creating unnecessary objects.

在经典的JMH workbench上跑的包装类和基础类性能对比如下图所示:
在这里插入图片描述

可以看到与基础类相比,包装类普遍要慢不少。

图片来源地址:https://www.baeldung.com/java-primitives-vs-objects

3. 不易察觉的NPE

不同于基本类型,作为对象的包装类是可能为null的,这就意味着一个指向null的包装类unboxing的时候会抛出NPE异常,比如以下代码:

Integer in = null;
...
...
int i = in; // NPE at runtime

这段代码也是比较明显的,但是如果包装类遇到三元运算符,则会出现更复杂的NPE

class Biziclop {
    public static void main(String[] args) {
      Boolean b = true ? returnsNull() : false; // NPE on this line.
      System.out.println(b);
    }
  	public static Boolean returnsNull() {
    	return null;
		}
}

这跟Java中三元运算符类型的判定有关系,有一条判定规则是,

如果三元运算符的第二个或者第三个参数是基本类型T,并且另一个是相应的包装类型的话,那么三元运算符的返回类型就是这个基本类型T

所以在上面的代码中,returnsNull的返回值还要进行一次unboxing,因此抛出了NPE.

4. 令人疑惑的API

Long这个类中,有一个apigetLong,其声明如下:


    /**
     * Determines the {@code long} value of the system property
     * with the specified name.
     */
    public static Long getLong(String nm) {
        return getLong(nm, null);
    }

这个api的作用是获取JVM中的属性值的,并且转换为Long 类型,比如:

class Biziclop {
    	public static void main(String[] args) {
        System.setProperty("22", "22");
        System.setProperty("23", "hello world!");
        System.out.println(Long.getLong("22")); // 22
        System.out.println(Long.getLong("23")); // null
        System.out.println(Long.getLong("24")); // null
		}
}

这个api的设计妥妥是一个反例,经常有同学误用,把它当成Long.valueOf或者是Long.parseLong,结果返回不符合期望的值。

5. 最佳实践

《阿里巴巴Java编程手册》对包装类的使用有以下三条建议:

  1. 所有POJO类属性使用包装类
  2. RPC方法的返回值和参数使用包装类
  3. 所有的局部变量使用基本数据类型

说明:POJO类属性没有初值是提醒使用在在需要使用时,必须自己显式的进行赋值,任何NPE问题,或者入库检查,都有使用者来保证。

正例:数据库的查询结果可能是null,因为自动拆箱,用基本数据类型接受有NPE的风险

反例:某业务的交易报表上显式成交额涨跌情况,即x%,x为基本数据类型,调用的HSF服务,调用不成功时,返回的是默认值,页面展示0%,这是不合理的,应该展示成中划线-,所以包装类的null值,能够表示额外的信息,如:远程调用失败,异常退出。
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

包装类这颗语法糖,其实并不甜 的相关文章

  • 如何使用 Apache POI API 将图像添加到 pptx 中添加的图像占位符?

    我已经预定义了带有文本和图像占位符的 pptx 模板 我如何从模板访问和修改这些占位符 我可以使用 POI pptx API 直接将图像和文本添加到幻灯片中 但如何将其添加到模板的占位符中 请参阅链接以了解如何添加占位符来创建固定模板 ht
  • Java中字符串中特殊字符的替换

    Java中如何替换字符串 E g String a adf sdf 如何替换和避免特殊字符 您可以删除除此之外的所有字符可打印的 ASCII 范围 http en wikipedia org wiki ASCII ASCII printab
  • 使用 JPA Criteria API 进行分页的总行数

    我正在系统中为实体实现 高级搜索 功能 以便用户可以使用该实体的属性上的多个条件 eq ne gt lt 等 来搜索该实体 我正在使用 JPA 的 Criteria API 动态生成 Criteria 查询 然后使用setFirstResu
  • OpenCV 中的 Gabor 内核参数

    我必须在我的应用程序中使用 Gabor 过滤器 但我不知道这个 OpenCV 方法参数值 我想对虹膜进行编码 启动 Gabor 过滤器并获取特征 我想对 12 组 Gabor 参数值执行此操作 然后我想计算 Hamming Dystans
  • OSGi:如果不取消服务会发生什么

    这是我获取 OSGi 服务的方式 ServiceReference reference bundleContext getServiceReference Foo class getName Foo foo Foo bundleContex
  • 如何在不超过最大值的情况下增加变量?

    我正在为学校开发一个简单的视频游戏程序 我创建了一个方法 如果调用该方法 玩家将获得 15 点生命值 我必须将生命值保持在最大值 100 并且由于我目前的编程能力有限 我正在做这样的事情 public void getHealed if h
  • 如何安全地解决这个 Java 上下文类加载器问题?

    我的数百名用户中只有一位在启动我的 Java 桌面应用程序时遇到问题 他只有大约三分之一的时间开始 另外三分之二的时间在启动时抛出 NullPointerException Exception in thread AWT EventQueu
  • 我需要什么库才能在 Java 中访问这个 com.sun.image.codec.jpeg?

    我正在用java创建一个图像水印程序 并导入了以下内容 import com sun image codec jpeg JPEGCodec import com sun image codec jpeg JPEGEncodeParam im
  • 我可以使用子接口重新编译公共 API 并保持二进制兼容性吗?

    我有一个公共 API 在多个项目中多次使用 public interface Process
  • 在 Netbeans 8 上配置 JBoss EAP 的问题

    我已经下载了 JBoss EAP 7 并正在 Netbeans 8 上配置它 我已经到达向导 实例属性 其中要求从选择框中选择 域 当我打开选择框时 它是空的 没有什么可以选择的 因此 完成 按钮也处于非活动状态 这使得无法完成配置 我通过
  • Calendar.getInstance(TimeZone.getTimeZone("UTC")) 不返回 UTC 时间

    我对得到的结果真的很困惑Calendar getInstance TimeZone getTimeZone UTC 方法调用 它返回 IST 时间 这是我使用的代码 Calendar cal Two Calendar getInstance
  • Java整数双除法混淆[重复]

    这个问题在这里已经有答案了 方案1 int sum 30 double avg sum 4 result is 7 0 not 7 5 VS 方案2 int sum 30 double avg sum 4 0 Prints lns 7 5
  • 如何知道抛出了哪个异常

    我正在对我们的代码库进行审查 有很多这样的陈述 try doSomething catch Exception e 但我想要一种方法来知道 doSomething 抛出了哪个异常 在 doSomething 的实现中没有 throw 语句
  • Java中的Object类是什么?

    什么是或什么类型private Object obj Object http download oracle com javase 6 docs api java lang Object html是Java继承层次结构中每个类的最终祖先 从
  • 为什么C++代码执行速度比java慢?

    我最近用 Java 编写了一个计算密集型算法 然后将其翻译为 C 令我惊讶的是 C 的执行速度要慢得多 我现在已经编写了一个更短的 Java 测试程序和一个相应的 C 程序 见下文 我的原始代码具有大量数组访问功能 测试代码也是如此 C 的
  • Trie 数据结构 - Java [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 是否有任何库或文档 链接提供了在 java 中实现 Trie 数据结构的更多信息 任何帮助都会很棒 Thanks 你可以阅读Java特里树
  • 在 RESTful Web 服务中实现注销

    我正在开发一个需要注销服务的移动应用程序 登录服务是通过数据库验证来完成的 现在我陷入了注销状态 退一步 您没有提供有关如何在应用程序中执行身份验证的详细信息 并且很难猜测您在做什么 但是 需要注意的是 在 REST 应用程序中 不能有会话
  • GUI Java 程序 - 绘图程序

    我一直试图找出我的代码有什么问题 这个想法是创建一个小的 Paint 程序并具有红色 绿色 蓝色和透明按钮 我拥有我能想到的让它工作的一切 但无法弄清楚代码有什么问题 该程序打开 然后立即关闭 import java awt import
  • 如何修复:“无法解析类型 java.lang.CharSequence。它是从所需的 .class 文件间接引用的”消息? [复制]

    这个问题在这里已经有答案了 我正在尝试使用这个字符串 amountStr amountStr replace replace replace 但我收到一条错误消息 我知道我收到的错误消息是因为我刚刚发布的字符串已过时 所以我想知道该字符串的
  • 如何在 JFreeChart 中设置多个系列的线条粗细?

    我创建了很多图表 在他们每个人中我都需要打电话 renderer setSeriesStroke i new BasicStroke 2 0f 对于每个系列 renderer is chart getXYPlot getRenderer 我

随机推荐

  • 权限认证。。

    链接 手摸手 带你用vue撸后台 系列二 登录权限篇 掘金 juejin cn 前端权限控制 一 前端权限管理及动态路由配置方案 ONEO阿喔哟的博客 CSDN博客 检查员工是否具有特权 param requestTokenBO 请求令牌B
  • 如何快速算出一个数有多少个因子(c++)

    如何快速算出一个数有多少个 多少种 因子 c int count int n int sum 1 for int i 2 i i lt n i if n i 0 int tmp 0 while n i 0 n i tmp sum sum t
  • Piecewise混沌映射/PWLCM混沌映射(含MATLAB代码)

    一 Piecewise混沌映射 PWLCM混沌映射 混沌映射是生成混沌序列的一种方法 常见的混沌映射方式有 Logistic映射 Tent映射 Circle映射 而 Piecewise映射作为混沌映射的典型代表 数学形式简单 具有遍历性和随
  • python文件操作(with open)——读取行、写操作

    一 基础语法 1 打开文件 这里只介绍一种常用方式 但是打开文件方式有很多种 掌握一种最适合自己的即可 推荐使用这种方式 因为不需要close 具体原因往下看 看到示例就懂了 打开文件的模式有很多种 r 读 w 写等 此处不做详细介绍 采用
  • sublime text3取消自动换行!

    菜单栏中取消view gt word wrap的勾选也可以取消其代码的自动换行 菜单栏选择preferences gt Setting User中添加 word wrap false 即可
  • 膜拜(离散化差分模板题)

    题目描述 小鱼有 n 名优秀的粉丝 粉丝们得知小鱼将会在一条直线上出现 打算去膜他 为了方便 粉丝们在这条直线上建立数轴 第 i 名粉丝有一个侦查区间 li ri 如果小鱼在 j li j ri 处出现 这名粉丝将立刻发现并膜他 小鱼希望膜
  • python 3 中文URL编码转换问题

    链接里面含中文 转成URL编码 先引入模块 from urllib request import quote gt gt gt ff 摄像头 gt gt gt ff quote ff gt gt gt ff E6 91 84 E5 83 8
  • sql 还原数据库 错误3154

    在SQL Server2005及以下版本做数据库备份还原时 需要首先建立数据库 然后才能进行数据库还原操作 而在SQL Server2005以上版本做数据库还原时 不需要建立数据库 可以直接进行数据库还原操作 否则执行数据库还原操作时会报3
  • 求阶乘之和(循环版)(利用阶乘函数)

    请编写函数 用循环方法求阶乘之和 SumFac n 0 1 2 3 n include
  • uniapp uview 登录页

  • DETRs Beat YOLOs on Real-time Object Detection论文详解

    论文题目 DETRs Beat YOLOs on Real time Object Detection 论文地址 https arxiv org abs 2304 08069 论文代码 mirrors facebookresearch Co
  • jmeter:linux环境运行jmeter并生成报告

    是一个java开发的利用多线程原理来模拟并发进行性能测试的工具 一般来说 GUI模式只用于创建脚本以及用来debug 执行测试时建议使用非GUI模式运行 这篇博客 介绍下在linux环境利用jmeter进行性能测试的方法 以及如何生成测试报
  • matplotlib绘图与可视化2

    文章目录 前言 一 使用pandas和seaborn绘图 1 1 折线图 1 2 柱状图 1 3 直方图和密度图 1 4 散点图或点图 1 5 分面网格和分类数据 总结 前言 matplotlib是一个相当底层的工具 你可以从其基本组件中组
  • java ioc依赖注入,Spring bean的实例化和IOC依赖注入详解

    前言 我们知道 IOC是Spring的核心 它来负责控制对象的生命周期和对象间的关系 举个例子 我们如何来找对象的呢 常见的情况是 在路上要到处去看哪个MM既漂亮身材又好 符合我们的口味 就打听她们的电话号码 制造关联想办法认识她们 然后
  • 【带头结点的单链表】

    带头结点的单链表 前言 一 带头结点的单链表结构体设计 1 带头结点的单链表 2 结构体声明 二 函数实现 1 初始化 2 申请新节点 3 头插 4 尾插 5 按位置插入 6 头删 7 尾删 8 销毁 总结 前言 单链表的概念 单链表是一种
  • CS162 操作系统HW2(使用Liunx内核链表以及多线程实现WordCounter)

    心得体会 IDE自动提示补全真的特别重要 大大提高开发效率 通过IDE自动搜索库函数API GDB调试能力要加强 使用前面提供的list h来改写wordCount程序 头文件的实现相当有技巧 将使用外部list库 多线程都用宏定义到同一份
  • Could not load dynamic library ‘libcupti.so.10.0‘; dlerror: libcupti.so.10.0...

    环境 Ubuntu 16 04 CUDA 10 0 CUDNN 7 6 5 nvcc NVIDIA R Cuda compiler driver Copyright c 2005 2018 NVIDIA Corporation Built
  • ESP32 /ESP8266在VS Code and PlatformIO上传文件系统 (SPIFFS)

    ESP32 ESP8266在VS Code and PlatformIO上传文件系统 SPIFFS 学习如何上传文件到ESP32板文件系统 SPIFFS 使用VS Code与PlatformIO IDE扩展 快速和简单 使用ESP32的文件
  • 【计算机毕业设计】课堂考勤微信小程序 基于微信小程序的课堂考勤管理系统

    毕设帮助 源码交流 技术解答 见文末 一 前言 在目前国内的高校课堂考勤中 传统的到场点名方式耗费了教师大量的时间和精力 随着课堂人数的增加 学生群体呈现多样性 这种点名考勤方式将不再适合日常使用 而且传统的点名考勤无法避免代人答到现象 极
  • 包装类这颗语法糖,其实并不甜

    历史文章推荐 你真的了解时间吗 细数ThreadLocal三大坑 内存泄露仅是小儿科 Java 8 ConcurrentHashMap源码中竟然隐藏着两个BUG ConcurrentHashMap中有十个提升性能的细节 你都知道吗 Hash