阿里为何禁止在对象中使用基本数据类型

news2024/11/25 20:13:30

大家好,我是一航!

前两天,因为一个接口的参数问题,和一位前端工程师产生了一些分歧,需求很简单:

根据一个数值类型(type 取值范围1,2,3)来查询数据,如果没这个值,就是查询所有的数据;

这个需求很常见吧!但是在"没这个值"的问题上,想法不太一样:

  • 我定义的规范是,没值的话,那就不传这个type,我后端拿到的就是null,在MyBatis的配置里面,通过if标签,就直接根据type判空,就变成了查询所有:

    <select id="query" parameterType="java.lang.Integer" resultMap="BaseResultMap">
      select 
      <include refid="Base_Column_List" />
      from order_info where 1 = 1
      <if test="type != null">
        AND TYPE = #{type,jdbcType=INTEGER}
      </if>
    </select>
    
  • 前端工程师的意思是,没有值的话,那我就给你传默认值了,数值类型的默认值是:0,后端就需要根据type是否是0来查询所有;

    因此,MyBatis中 type 就需要加上大于0的判断

    <if test="type != null and type > 0">
        AND TYPE = #{type,jdbcType=INTEGER}
    </if>
    

虽然按着前端这样做,也确实可以实现,但是这个说法,是没有任何说服力的;因为 0 本身就是一个具体的值,并不符合 type 的取值范围,在Controler层的参数校验,就应该被干掉;如果某一天因为需求调整,将 0 也表示为某个具体类型之后,这里代码就需要做调整,同时查询所有和查询这个新增 0 的类型就会混淆,前端的展示也会受到影响;

0 和 null 对象在本质上还是有很大区别的;

在各执一词的背景下,我在技术交流群里面和各位大佬简单交流了一下,不想因为的我的执着影响到其他人;

大部分大佬的做法和我想的是一致的!最终也让前端按我的要求做了对应的调整;

那这个问题的根源,还是出在数值类型的默认值上;加上群里面几天讨论了几次相关的一些问题,这里就汇总说一下;

在阿里巴巴Java开发手册中有这样的一条规范,跟我们今天说的问题,也有一些关联:

  • 【强制】所有的 POJO 类属性必须使用包装数据类型。
  • 【强制】RPC 方法的返回值和参数必须使用包装数据类型。
  • 【推荐】所有的局部变量使用基本数据类型。

最新阿里巴巴Java开发手册(黄山版),于2022年2月3日发布,有需要的朋友可以添加微信(mbb2100)找我要

下面就通过详细的示例,来说明一下为什么阿里的开发手册会有这样的约束;

Java 基本类型与包装类的关系

首先,我们需要了解清楚Java的基本数据类型对应的包装类,以及基本类型的默认值;

包装类在不实例化的前提下,默认值都是 null

基本类型包装类字节数位数最小值最大值基本类型默认值
byteByte18-2^7(-128)2^7 - 1(127)(byte)0
shortShort216-2^152^15 - 1(short)0
intInteger432-2^312^31 - 10
longLong864-2^632^63 - 10L
floatFloat4321.4E - 4 (2^-149)3.4028235E38(2^128 - 1)0.0f
doubleDouble8644.9E - 324(2^-1074)1.7976931348623157E308(2^1024-1)0.0d
charCharacter216\u0000\uFFFF‘/uoooo’(null)
booleanBoolean180(false)1(true)false

POJO 类、RPC方法、返回值 强制使用包装类

在POJO 类、RPC方法返回值和参数需要强制使用包装类,如果使用基本数据类型,实例化出来的对象,就会初始化默认值;如果不赋值,对于使用者来说,根本就不知道这个值是因为默认值产生的,还是创建者设置的,从而带来后续的一些列问题;

举个例子

  • 用户对象

    @Data
    public class User {
        /**
         * 姓名
         */
        private String name;
    
        /**
         * 年龄
         */
        private Integer age;
    
        /**
         * 0:女 1:男
         */
        private byte gender;
    }
    

    age 使用包装类,gender 性别用的基本数据类型(0表示女,1表示男)

  • 接口

    /**
     * 添加用户
     *
     * @param user
     * @return
     */
    @PostMapping("/add")
    private User add(@RequestBody User user) {
        log.info("添加的用户:{}", user);
        return user;
    }
    
  • 测试

    分别按以下的两种方式传参

    当所有的请求参数都必传的话(右侧传参示例),不管属性是包装类、还是基本数据类型都没啥问题;

    如果是左边的传参方式,只有名字必传,其他都没值,性别使用的是byte基础数据类型,User 对象创建之后,就会赋上默认值:0;0 表示女,这时候就可能让一个帅小伙儿无缘无故变成了女孩子;

RPC 方法及返回值的参数强制使用包装类的原因和上面是差不多的

ORM 关系映射对象必须使用包装类

数据库查询的映射对象,如果使用基础数据类型,就可能出现 NPE(空指针)异常对象构建异常数据错误的问题;

继续看示例:

  • 映射对象

    数据库表:

    映射对象:

    @TableName(value = "user_info")
    @Data
    @AllArgsConstructor
    public class UserInfo implements Serializable {
        @TableField(exist = false)
        private static final long serialVersionUID = 1L;
        /**
         * 主键ID
         */
        @TableId(value = "id", type = IdType.AUTO)
        private Integer id;
        /**
         * 用户名
         */
        @TableField(value = "user_name")
        private String userName;
        /**
         * 年龄
         */
        @TableField(value = "age")
        private int age;
        /**
         * 来源
         */
        @TableField(value = "source")
        private Byte source;
    }
    
  • 查询方法

    @Test
    void getById() {
        UserInfo userInfo = userInfoService.getById(1);
        log.info("根据ID查询用户信息:{}", userInfo);
    }
    
  • 问题一:对象构建异常

    当映射对象包含 @AllArgsConstructor(Lombok的注解) 时,会自动生成带所有属性的构造方法:

    public UserInfo(final Integer id, final String userName, final int age, final Byte source) {
        this.id = id;
        this.userName = userName;
        this.age = age;
        this.source = source;
    }
    

    查询 id 为 1 的数据时,由于 age 为 null,当通过以上构造方法实例化对象时,将一个 null 对象赋给一个基础数据类型,就会出现 IllegalArgumentException异常:

    org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Error instantiating class com.ehang.mysql.mybatis.plus.generator.user.demain.UserInfo with invalid types (Integer,String,int,Byte) or values (1,一行Java 1,null,1). Cause: java.lang.IllegalArgumentException
    
  • 问题二:数据错误

    当对象中,没有 @AllArgsConstructor 注解,只带有 @Data 注解时,会生成所有属性的 Getter、Setter 方法;查询的结果会通过各个属性 Setter 方法基础赋值;

    ==>  Preparing: SELECT id,user_name,age,source FROM user_info WHERE id=?
    ==> Parameters: 1(Integer)
    <==    Columns: id, user_name, age, source
    <==        Row: 1, 一行Java 1, null, 1
    <==      Total: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1152bcd]
    2022-10-30 12:57:09.308  INFO 504100 --- [           main] com.ehang.mysql.mybatis.plus.GetTest     : 根据ID查询用户信息:UserInfo [Hash = 1795612732, id=1, userName=一行Java 1, age=0, source=1, serialVersionUID=1]
    

    以上日志可以看出,id 为 1 的 age 数据库中查出来的是 null,不会调用对象的 Setter 方法赋值,可由于对象中的 age 是 int 基础数据类型,在对象创建之后,就赋予了初始值 0,最终造成使用者拿到的 UserInfo 对象和数据库中的结果不一致的问题,这是绝对不允许的。

  • 解决办法

    把基本数据类型换成包装类就好了;

局部变量推荐使用基础数据类型

【推荐】所有的局部变量使用基本数据类型。

那既然基本数据类型总是有问题,我们全部用包装类不就好了;但这里为什么局部变量又推荐使用基本数据类型呢?因为一旦涉及到运算,基础数据类型可以省去拆箱、装箱的动作,提高运行效率;

  • 什么是装箱和拆箱?

    • 装箱

      就是自动将基本数据类型转换为包装器类型;原理是调用包装类的valueOf方法,如:Integer.valueOf(1)Boolean.valueOf(true)

    • 拆箱

      就是自动将包装器类型转换为基本数据类型;原理是是调用包装类的xxxValue方法(xxx表示类型),如:

      public boolean booleanValue() {
          return value;
      }
      

还是以上周,一位小伙伴儿在群里面问的关于拆、装箱的效率问题为例:

由于没有他的源码和需求,这里我写了一个新的测试用例,来论证这个问题;

  • 示例代码

    public class Main {
        public static void main(String[] args) {
            int[] nums = new int[]{1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010};
            long start = System.currentTimeMillis();
            for (int n = 0; n < 10000000; n++) {
                for (int i = 0; i < nums.length; i++) {
                    nums[i] = nums[i] + 1;
                    nums[i] = nums[i] + 2;
                    nums[i] = nums[i] + 3;
                }
            }
            System.out.printf("int 遍历10000000的耗时:%sms\n",System.currentTimeMillis()-start);
    
            start = System.currentTimeMillis();
            Integer[] nums2 = new Integer[]{1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010};
            for (int n = 0; n < 10000000; n++) {
                for (int i = 0; i < nums2.length; i++) {
                    nums2[i] = nums2[i] + 1;
                    nums2[i] = nums2[i] + 2;
                    nums2[i] = nums2[i] + 3;
                }
            }
            System.out.printf("Integer 遍历10000000的耗时:%sms\n",System.currentTimeMillis()-start);
        }
    }
    

    代码逻辑很简单,分别有一个 int 和 Integer 数组,里面的初始值一样,遍历 10000000,每次将数组中的每个值都分别 +1+2+3再放回到数组中去;

  • 测试结果

    int 遍历10000000的耗时:194ms
    Integer 遍历10000000的耗时:1965ms
    

    根据耗时,会发现,int 数组的效率差不多是 Integer 数组的10倍

  • 原因分析

    明明初始值一样,计算出来的结果也一样,为什么效率会差了10倍之多?

    主要的原因是出在以下的三行代码中:

    nums[i] = nums[i] + 1;
    nums[i] = nums[i] + 2;
    nums[i] = nums[i] + 3;
    

    如果是 int 数组,所有的值都是保存在栈中;不会有任何拆、装箱的动作;取值、计算、再赋值的过程也就是一气呵成,非常丝滑;

    但是如果是 Integer 数组,nums[i] = nums[i] + 1;这行代码,计算过程如下:

    1. 在 nums[i] 中取出 Integer2. 将取出的值拆箱;`intValue`方法;
    3. 拆箱后的 int1 做相加运算,得到计算结果;
    4. 将计算结果的 int 装箱生成 Integer 对象(主要耗时的地方);
    5. 将结果放到 nums[i] 中。
    

    由于做了3次运算,意味着每次循环都会将上面步骤重复3次;并经历了3次拆箱、3次装箱动作;那这个过程必定会带来性能上的消耗;

    因此在局部变量中,合理的使用基本类型,可以有效的提高效率;

好了,看到这里,阿里的这条约束应该就能彻底理解了;感谢你的三连…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2531.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML+CSS+JavaScript七夕情人节表白网页【樱花雨3D相册】超好看

这是程序员表白系列中的100款网站表白之一&#xff0c;旨在让任何人都能使用并创建自己的表白网站给心爱的人看。 此波共有100个表白网站&#xff0c;可以任意修改和使用&#xff0c;很多人会希望向心爱的男孩女孩告白&#xff0c;生性腼腆的人即使那个TA站在眼前都不敢向前表白…

pandas 基本数据

目录 1. pandas 简介 2. pandas 基本数据结构 2.1 Series 类型 2.1.1 索引-数据的行标签 2.1.2 值 2.1.3 切片 2.1.4 索引赋值 2.2 DataFrame 类型 1. pandas 简介 一般导入的形式&#xff1a;import pandas as pd 2. pandas 基本数据结构 python 的数据结构&#xff1a…

python爬虫之Scrapy框架,基本介绍使用以及用框架下载图片案例

一、Scrapy框架简介 Scrapy是:由Python语言开发的一个快速、高层次的屏幕抓取和web抓取框架&#xff0c;用于抓取web站点并从页面中提取结构化的数据&#xff0c;只需要实现少量的代码&#xff0c;就能够快速的抓取。 Scrapy使用了Twisted异步网络框架来处理网络通信&#xf…

Servlet篇 —— 我的第一个Servlet程序

☕导航小助手☕ &#x1f35a;写在前面 &#x1f35c;一、Maven的介绍 &#x1f371;​二、第一个Servlet的创建 &#x1f354;&#x1f354;2.1 创建项目 &#x1f969;&#x1f969;​2.2 引入依赖 &#x1f9aa;&#x1f9aa;​2.3 创建目录 &#x1f363;&#x1f363;2.4…

没想到GoFrame的gcache天然支持缓存淘汰策略

gcache提供统一的缓存管理模块&#xff0c;提供了开发者可自定义灵活接入的缓存适配接口&#xff0c;并默认提供了高速内存缓存适配实现。 先说结论 这篇文章通过结合商业项目的使用场景&#xff0c;为大家介绍了gcache的基本使用、缓存控制以及淘汰策略。 使用gcache做缓存处…

3分钟,快速上手Postman接口测试

Postman是一个用于调试HTTP请求的工具&#xff0c;它提供了友好的界面帮助分析、构造HTTP请求&#xff0c;并分析响应数据。实际工作中&#xff0c;开发和测试基本上都有使用Postman来进行接口调试工作。有一些其他流程的工具&#xff0c;也是模仿的Postman的风格进行接口测试工…

推荐 4 个开源工具

Hi&#xff0c;艾瑞巴蒂&#xff0c;晚上好&#xff01;今天推荐 4 个登上 GitHub 热搜的开源项目&#xff0c;它们分别是&#xff1a;1. 炫酷的 UI 工具&#xff1a;glslViewer2. Textual3. ToolJet&#xff1a;开源的低代码开发框架4. Linux 命令大全搜索工具01炫酷的 UI 工…

程序人生:去了字节跳动,才知道年薪40W的测试有这么多?

今年大环境不好&#xff0c;内卷的厉害&#xff0c;薪资待遇好的工作机会更是难得。最近脉脉职言区有一条讨论火了&#xff1a; 哪家互联网公司薪资最‘厉害’&#xff1f; 下面的评论多为字节跳动&#xff0c;还炸出了很多年薪40W的测试工程师 我只想问一句&#xff0c;现在的…

vue3项目的创建,vite+vue3+ts(3)- router

vue3 有三种写法&#xff1a; 1.compostion API &#xff1a; 还是按照vue2.0写法 2.组合式API: 3. 组合式API 语法糖&#xff08;setup), 语法简洁&#xff08;推荐使用这个&#xff09; 写法&#xff1a; 4. 在.eslintrc.cjs 或者 .eslintrc.js中配置代码&#xff0c;是这个…

聊聊计算机中的寄存器

文章目录前言数据寄存器(DR)地址寄存器(AR)程序状态寄存器(PSW)累加寄存器(AC)乘商寄存器(MQ)程序计数器(PC)指令寄存器(IR)MAR、MDR小结作者&#xff1a;小牛呼噜噜 | https://xiaoniuhululu.com 计算机内功、JAVA底层、面试相关资料等更多精彩文章在公众号「小牛呼噜噜 」 前…

国内第一篇讲解减少卡顿的代码级详细文章

原文链接&#xff1a;原文链接 系统网站应用出现过卡顿&#xff0c;但却不知道如何优化。国内第一篇讲如何减少卡顿的代码级别详细文章&#xff0c;也是性能优化系列文章中的一篇&#xff0c;欢迎点赞、关注&#xff0c;也欢迎对其中的内容进行评论。 经常听人说&#xff0c;“…

配置CentOS为ssh免密码互相通信

文章目录配置CentOS为ssh免密码互相通信配置4台CentOS的集群配置4台CentOS为ssh免密码互相通信配置CentOS为ssh免密码互相通信 配置4台CentOS的集群 修改 /etc/sysconfig/network-scripts/ifcfg-ens33 文件&#xff0c;配置虚拟机 IP&#xff0c;以其中一个虚拟机为例&#x…

html实现贪吃蛇游戏(源码)

文章目录1.实现贪吃蛇1.1 界面设计1.2 界面动态效果1.3 界面主代码2.资源目录源码下载作者&#xff1a;xcLeigh 文章说明 html实现贪吃蛇源码&#xff0c;酷炫的界面效果&#xff0c;点击开始游戏后&#xff0c;通过键盘的上下左右按键&#xff0c;操作移动方向&#xff0c;代码…

@DateTimeFormat和@JsonFormat介绍

文章目录1.DateTimeFormat注解1.1DateTimeFormat注解简介1.2DateTimeFormat注解的功能1.3DateTimeFormat注解的注意点1.4DateTimeFormat功能演示1.4.1类型转换异常情况测试1.4.2接收url路径传参格式测试1.4.3接收Form-Data数据格式测试1.4.4接收JSON数据格式测试2.JsonFormat注…

python实现基于RPC协议的接口自动化测试

什么是RPC RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用协议是一个用于建立适当框架的协议。从本质上讲&#xff0c;它使一台机器上的程序能够调用另一台机器上的子程序&#xff0c;而不会意识到它是远程的。 RPC 是一种软件通信协议&#xff0c;一个程序可…

Day1:垂直水平居中方式(至少6种,必须包含弹性盒子)

目录 垂直水平居中方式 方式1&#xff1a;弹f性盒子(1) &#xff08;推荐&#xff09; 方式2&#xff1a;弹性盒子(2) &#xff08;推荐&#xff09; 方式3&#xff1a;弹性盒子(3) 方式4&#xff1a;grid布局&#xff08;1&#xff09; &#xff08;推荐&#xff09; 方…

vs2019 编译调试 QT Creator 源码

vs2019 编译调试 QT Creator 源码 开始使用Qt Creator 5.15.2 调试编译 Qt Creator 6.0.2源码&#xff0c;对源码进行了 裁剪&#xff0c;将一些暂时用不到的文件删除&#xff0c;比如plugins里面的绝大部分文件。然后使用vs2019打开工程&#xff0c;进行编译调试。下面对这个…

IDEA2022插件:EasyCode一键生成增删改查代码

IDEA2022插件&#xff1a;EasyCode一键生成增删改查代码 文章目录IDEA2022插件&#xff1a;EasyCode一键生成增删改查代码建表下载插件IDEA连接数据源引入必要依赖配置SpringBoot数据库连接使用EasyCode生成代码生成效果启动测试小错误接口测试自行配置更好用尾述结语建表 新建…

【案例源码公开】国产AD+全志T3开发案例,为能源电力行业排忧解难!8/16通道

前 言 本文主要介绍基于全志科技T3(ARM Cortex-A7)国产处理器的8/16通道AD采集开发案例,使用核芯互联CL1606/CL1616国产AD芯片,亦适用于ADI AD7606/AD7616。CL1606/CL1616与AD7606/AD7616软硬件兼容。 备注: (1)创龙科技TL7606I模块使用AD芯片为核芯互联CL1606或ADI AD…

【C语言】初始C语言系列 代码详解 _ 编程入门 _【内附代码和图片】_ [初阶篇 _ 总结复习]

【前言】 本篇文章为初始C语言部分&#xff0c;C语言是编程的入门语言&#xff0c;所以也说是编程入门&#xff1b; 学好C语言的入门内容&#xff0c;才能真正的入门编程&#xff0c;而C语言的学习对于刚入门的同学还是有一些难度的&#xff0c;需要踏踏实实的自己去理解。 在此…