如何正确定义接口的返回值(boolean/Boolean)类型及命名(success/isSuccess)

在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提供一个RPC接口的时候,我们一般会定义一个字段表示本次请求是否成功的。
关于这个"本次请求是否成功"的字段的定义,其实是有很多种讲究和坑的,稍有不慎就会掉入坑里,作者在很久之前就遇到过类似的问题,本文就来围绕这个简单分析一下。到底该如何定一个布尔类型的成员变量。
一般情况下,我们可以有以下四种方式来定义一个布尔类型的成员变量:

boolean success
boolean isSuccess
Boolean success
Boolean isSuccess

以上四种定义形式,你日常开发中最常用的是哪种呢?到底哪一种才是正确的使用姿势呢?
通过观察我们可以发现,前两种和后两种的主要区别是变量的类型不同,前者使用的是boolean,后者使用的是Boolean。
另外,第一种和第三种在定义变量的时候,变量命名是success,而另外两种使用isSuccess来命名的。
首先,我们来分析一下,到底应该是用success来命名,还是使用isSuccess更好一点。

success 还是 isSuccess

到底应该是用success还是isSuccess来给变量命名呢?从语义上面来讲,两种命名方式都可以讲的通,并且也都没有歧义。那么还有什么原则可以参考来让我们做选择呢。
在阿里巴巴Java开发手册中关于这一点,有过一个『强制性』规定:


15449439364854.jpg

那么,为什么会有这样的规定呢?我们看一下POJO中布尔类型变量不同的命名有什么区别吧。

class Model1  {
    private Boolean isSuccess;
    public void setSuccess(Boolean success) {
        isSuccess = success;
    }
    public Boolean getSuccess() {
        return isSuccess;
    }
 }

class Model2 {
    private Boolean success;
    public Boolean getSuccess() {
        return success;
    }
    public void setSuccess(Boolean success) {
        this.success = success;
    }
}

class Model3 {
    private boolean isSuccess;
    public boolean isSuccess() {
        return isSuccess;
    }
    public void setSuccess(boolean success) {
        isSuccess = success;
    }
}

class Model4 {
    private boolean success;
    public boolean isSuccess() {
        return success;
    }
    public void setSuccess(boolean success) {
        this.success = success;
    }
}

以上代码的setter/getter是使用Intellij IDEA自动生成的,仔细观察以上代码,你会发现以下规律:

  • 基本类型自动生成的getter和setter方法,名称都是isXXX()和setXXX()形式的。
  • 包装类型自动生成的getter和setter方法,名称都是getXXX()和setXXX()形式的。
    既然,我们已经达成一致共识使用基本类型boolean来定义成员变量了,那么我们再来具体看下Model3和Model4中的setter/getter有何区别。我们可以发现,虽然Model3和Model4中的成员变量的名称不同,一个是success,另外一个是isSuccess,但是他们自动生成的getter和setter方法名称都是isSuccess和setSuccess。

Java Bean中关于setter/getter的规范

关于Java Bean中的getter/setter方法的定义其实是有明确的规定的,根据JavaBeans(TM) Specification规定,如果是普通的参数propertyName,要以以下方式定义其setter/getter:

public <PropertyType> get<PropertyName>();
public void set<PropertyName>(<PropertyType> a);

但是,布尔类型的变量propertyName则是单独定义的:

public boolean is<PropertyName>();
public void set<PropertyName>(boolean m);
15449455942045.jpg

通过对照这份JavaBeans规范,我们发现,在Model4中,变量名为isSuccess,如果严格按照规范定义的话,他的getter方法应该叫isIsSuccess。但是很多IDE都会默认生成为isSuccess。
那这样做会带来什么问题呢。
在一般情况下,其实是没有影响的。但是有一种特殊情况就会有问题,那就是发生序列化的时候。

序列化带来的影响

关于序列化和反序列化请参考Java对象的序列化与反序列化。我们这里拿比较常用的JSON序列化来举例,看看看常用的fastJson、jackson和Gson之间有何区别:

public class BooleanMainTest {

    public static void main(String[] args) throws IOException {
        //定一个Model3类型
        Model3 model3 = new Model3();
        model3.setSuccess(true);

        //使用fastjson(1.2.16)序列化model3成字符串并输出
        System.out.println("Serializable Result With fastjson :" + JSON.toJSONString(model3));

        //使用Gson(2.8.5)序列化model3成字符串并输出
        Gson gson =new Gson();
        System.out.println("Serializable Result With Gson :" +gson.toJson(model3));

        //使用jackson(2.9.7)序列化model3成字符串并输出
        ObjectMapper om = new ObjectMapper();
        System.out.println("Serializable Result With jackson :" +om.writeValueAsString(model3));
    }

}

class Model3 implements Serializable {

    private static final long serialVersionUID = 1836697963736227954L;
    private boolean isSuccess;
    public boolean isSuccess() {
        return isSuccess;
    }
    public void setSuccess(boolean success) {
        isSuccess = success;
    }
    public String getHollis(){
        return "hollischuang";
    }
}

以上代码的Model3中,只有一个成员变量即isSuccess,三个方法,分别是IDE帮我们自动生成的isSuccess和setSuccess,另外一个是作者自己增加的一个符合getter命名规范的方法。
以上代码输出结果:

Serializable Result With fastjson :{"hollis":"hollischuang","success":true}
Serializable Result With Gson :{"isSuccess":true}
Serializable Result With jackson :{"success":true,"hollis":"hollischuang"}

在fastjson和jackson的结果中,原来类中的isSuccess字段被序列化成success,并且其中还包含hollis值。而Gson中只有isSuccess字段。
我们可以得出结论:fastjson和jackson在把对象序列化成json字符串的时候,是通过反射遍历出该类中的所有getter方法,得到getHollis和isSuccess,然后根据JavaBeans规则,他会认为这是两个属性hollis和success的值。直接序列化成json:{"hollis":"hollischuang","success":true},但是Gson并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化成json:{"isSuccess":true}。
可以看到,由于不同的序列化工具,在进行序列化的时候使用到的策略是不一样的,所以,对于同一个类的同一个对象的序列化结果可能是不同的。
前面提到的关于对getHollis的序列化只是为了说明fastjson、jackson和Gson之间的序列化策略的不同,我们暂且把他放到一边,我们把他从Model3中删除后,重新执行下以上代码,得到结果:

Serializable Result With fastjson :{"success":true}
Serializable Result With Gson :{"isSuccess":true}
Serializable Result With jackson :{"success":true}

现在,不同的序列化框架得到的json内容并不相同,如果对于同一个对象,我使用fastjson进行序列化,再使用Gson反序列化会发生什么?

public class BooleanMainTest {
    public static void main(String[] args) throws IOException {
        Model3 model3 = new Model3();
        model3.setSuccess(true);
        Gson gson =new Gson();
        System.out.println(gson.fromJson(JSON.toJSONString(model3),Model3.class));
    }
}


class Model3 implements Serializable {
    private static final long serialVersionUID = 1836697963736227954L;
    private boolean isSuccess;
    public boolean isSuccess() {
        return isSuccess;
    }
    public void setSuccess(boolean success) {
        isSuccess = success;
    }
    @Override
    public String toString() {
        return new StringJoiner(", ", Model3.class.getSimpleName() + "[", "]")
            .add("isSuccess=" + isSuccess)
            .toString();
    }
}

以上代码,输出结果:

Model3[isSuccess=false]

这和我们预期的结果完全相反,原因是因为JSON框架通过扫描所有的getter后发现有一个isSuccess方法,然后根据JavaBeans的规范,解析出变量名为success,把model对象序列化城字符串后内容为{"success":true}。
根据{"success":true}这个json串,Gson框架在通过解析后,通过反射寻找Model类中的success属性,但是Model类中只有isSuccess属性,所以,最终反序列化后的Model类的对象中,isSuccess则会使用默认值false。
但是,一旦以上代码发生在生产环境,这绝对是一个致命的问题。所以,作为开发者,我们应该想办法尽量避免这种问题的发生,对于POJO的设计者来说,只需要做简单的一件事就可以解决这个问题了,那就是把isSuccess改为success。这样,该类里面的成员变量是success,getter方法是isSuccess,这是完全符合JavaBeans规范的。无论哪种序列化框架,执行结果都一样。就从源头避免了这个问题。

Boolean还是boolean

前面我们介绍完了在success和isSuccess之间如何选择,那么排除错误答案后,备选项还剩下:

boolean success
Boolean success

那么,到底应该是用Boolean还是boolean来给定一个布尔类型的变量呢?
我们知道,boolean是基本数据类型,而Boolean是包装类型。那么,在定义一个成员变量的时候到底是使用包装类型更好还是使用基本数据类型呢?
我们来看一段简单的代码:

public class BooleanMainTest {
    public static void main(String[] args) {
        Model model1 = new Model();
        System.out.println("default model : " + model1);
    }
}

class Model {
    /**
     * 定一个Boolean类型的success成员变量
     */
    private Boolean success;
    /**
     * 定一个boolean类型的failure成员变量
     */
    private boolean failure;

    /**
     * 覆盖toString方法,使用Java 8 的StringJoiner
     */
    @Override
    public String toString() {
        return new StringJoiner(", ", Model.class.getSimpleName() + "[", "]")
            .add("success=" + success)
            .add("failure=" + failure)
            .toString();
    }
}

以上代码输出结果为:

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

可以看到,当我们没有设置Model对象的字段的值的时候,Boolean类型的变量会设置默认值为null,而boolean类型的变量会设置默认值为false。
即对象的默认值是null,boolean基本数据类型的默认值是false。
在阿里巴巴Java开发手册中,对于POJO中如何选择变量的类型也有着一些规定:


640.jpeg

这里建议我们使用包装类型,原因是什么呢?
举一个扣费的例子,我们做一个扣费系统,扣费时需要从外部的定价系统中读取一个费率的值,我们预期该接口的返回值中会包含一个浮点型的费率字段。当我们取到这个值得时候就使用公式:金额*费率=费用 进行计算,计算结果进行划扣。如果由于计费系统异常,他可能会返回个默认值,如果这个字段是Double类型的话,该默认值为null,如果该字段是double类型的话,该默认值为0.0。如果扣费系统对于该费率返回值没做特殊处理的话,拿到null值进行计算会直接报错,阻断程序。拿到0.0可能就直接进行计算,得出接口为0后进行扣费了。这种异常情况就无法被感知。
这种使用包装类型定义变量的方式,通过异常来阻断程序,进而可以被识别到这种线上问题。如果使用基本数据类型的话,系统可能不会报错,进而认为无异常。
以上,就是建议在POJO和RPC的返回值中使用包装类型的原因。
但是,个人观点,还是尽量避免在你的代码中出现不确定的null值。

总结

本文围绕布尔类型的变量定义的类型和命名展开了介绍,最终我们可以得出结论,在定义一个布尔类型的变量,尤其是一个给外部提供的接口返回值时,要使用success来命名,阿里巴巴Java开发手册建议使用封装类来定义POJO和RPC返回值中的变量。但是这不意味着可以随意的使用null,我们还是要尽量避免出现对null的处理的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355

推荐阅读更多精彩内容