Java开发手册中为什么禁止使用isSuccess作为布尔类型变量名以及POJO中基本类型与包装类型的使用标准

发布时间 2023-06-06 11:11:17作者: 霸道流氓

场景

Java开发手册中关于POJO的布尔类型的变量名的要求是:

【强制】POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。

说明:在本文 MySQL 规约中的建表约定第一条,表达是与否的变量采用 is_xxx 的命名方式,所以,需要

在<resultMap>设置从 is_xxx 到 xxx 的映射关系。

反例:定义为基本数据类型 Boolean isDeleted 的属性,它的方法也是 isDeleted(),框架在反向解析的时候,

“误以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。

 

下面来验证下上面的结论。

在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提供一个 RPC 接口的时候,

我们一般会定义一个字段表示本次请求是否成功的。

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi

我们先新建以下四个POJO

Model1 :

public class Model1 {
    private Boolean isSuccess;

    public Boolean getSuccess() {
        return isSuccess;
    }

    public void setSuccess(Boolean success) {
        isSuccess = success;
    }
}

Model2 :

public class Model2 {
    private Boolean success;

    public Boolean getSuccess() {
        return success;
    }

    public void setSuccess(Boolean success) {
        this.success = success;
    }
}

Model3:

public class Model3 {
    private boolean isSuccess;

    public boolean isSuccess() {
        return isSuccess;
    }

    public void setSuccess(boolean success) {
        isSuccess = success;
    }
}

Model4 :

public class Model4 {
    private boolean success;

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }
}

以上四个POJO都是通过IDEA自动生成的get和set方法,可以看到:

基本类型自动生成的 getter 和 setter 方法,名称都是isXXX()和setXXX()形式的。

包装类型自动生成的getter和setter方法,名称都是getXXX()和setXXX()形式的

JavaBean 中关于 setter/getter 的规范

关于 Java Bean 中的 getter/setter 方法的定义其实是有明确的规定的,根据JavaBeans(TM) Specification规定:

https://download.oracle.com/otndocs/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/

如果是普通的参数 propertyName,要以以下方式定义其 setter/getter:
        public <PropertyType> get<PropertyName>();
        public void set<PropertyName>(<PropertyType> a);

但是,布尔类型的变量 propertyName 则是单独定义的:
        public boolean is<PropertyName>();
        public void set<PropertyName>(boolean m);

 

通过对照这份 JavaBeans 规范,我们发现,在 Model4 中,变量名为 isSuccess,如果严格按照规范定义的话,

其getter 方法应该叫 isIsSuccess。但是很多IDE 都会默认生成为 isSuccess。

Java中fastJson、jackson、Gson对于POJO的布尔变量isSuccess序列化的影响

拿常用的序列化进行举例,如果需要引入依赖,根据自己项目情况自行引入

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.9</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.12.0</version>
        </dependency>

引入依赖之后,示例代码如下:

        Model3 model3 = new Model3();
        model3.setSuccess(true);
        System.out.println("Serialiazable Result with fashjson:"+com.alibaba.fastjson.JSON.toJSONString(model3));
        com.google.gson.Gson gson = new Gson();
        System.out.println("Serialiazable Result with Gson:"+gson.toJson(model3));
        com.fasterxml.jackson.databind.ObjectMapper om = new ObjectMapper();
        System.out.println("Serialiazable Result with jackson:"+om.writeValueAsString(model3));

以上代码的输出结果为

        //Serialiazable Result with fashjson:{"success":true}
        //Serialiazable Result with Gson:{"isSuccess":true}
        //Serialiazable Result with jackson:{"success":true}

可以看到

在 fastjson 和 jackson 的结果中,原来类中的 isSuccess 字段被序列化成success。

而 Gson 中是 isSuccess 字段。

fastjson 和 jackson 在把对象序列化成 json 字符串的时候,是通过反射遍历出该类中的所有 getter 方法,

得到isSuccess,然后根据 JavaBeans 规则,他会认为这是属性success的值。直接序列化成 json:{"success":true}

但是 Gson 并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化成 json:{“isSuccess”:true}

可以看到,由于不同的序列化工具,在进行序列化的时候使用到的策略是不一样的,

所以,对于同一个类的同一个对象的序列化结果可能是不同的。

如果对于同一个对象,使用fastjson进行序列化,再使用Gson反序列化会发生什么?

测试代码如下:

 System.out.println(gson.fromJson(com.alibaba.fastjson.JSON.toJSONString(model3),Model3.class).isSuccess());

以上输出结果为false

原因是因为 JSON 框架通过扫描所有的 getter后发现有一个isSuccess方法,然后根据JavaBeans的规范,解析出变量名为success,

把 model 对象序列化城字符串后内容为{"success":true}。根据{"success":true}这个 json 串,

Gson 框架在通过解析后,通过反射寻找 Model 类中的 success 属性,

 但是 Model 类中只有 isSuccess 属性,所以,最终反序列化后的 Model 类的对象中,isSuccess 则会使用默认值 false。

所以,在定义 POJO 中的布尔类型的变量时,不要使用 isSuccess 这种形式,而要直接使用 success !

Java的POJO中布尔变量使用基本数据类型boolean还是包装数据类型Boolean

新建一个POJO

import java.util.StringJoiner;

public class Model {
    private Boolean success;
    private boolean failure;

    public Boolean getSuccess() {
        return success;
    }

    public void setSuccess(Boolean success) {
        this.success = success;
    }

    public boolean isFailure() {
        return failure;
    }

    public void setFailure(boolean failure) {
        this.failure = failure;
    }

    @Override
    public String toString() {
        return new StringJoiner(",",Model.class.getSimpleName()+"[","]")
                .add("success = "+success)
                .add("failure= "+failure)
                .toString();
    }
}

并使用Java 8的StringJoiner重写toString方法。

然后输出该对象的toString

        Model model = new Model();
        System.out.println("default model: "+model);

以上代码输出结果

default model: Model[success = null,failure= false]

可以看到,当我们没有设置 Model 对象的字段的值的时候,Boolean 类型的变量会设置默认值为null,

而 boolean 类型的变量会设置默认值为false。即对象的默认值是null,boolean 基本数据类型的默认值是false。

在 Java 开发手册中,对于 POJO 中如何选择变量的类型也有着一些规定

关于基本数据类型与包装数据类型的使用标准如下:

1) 【强制】所有的 POJO 类属性必须使用包装数据类型。

2) 【强制】RPC 方法的返回值和参数必须使用包装数据类型。

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

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

正例:

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

反例:

某业务的交易报表上显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调

用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线-。所以包装数据类型

的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。