在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提供一个RPC接口的时候,我们一般会定义一个字段表示本次请求是否成功的。

关于这个”本次请求是否成功”的字段的定义,其实是有很多种讲究和坑的,稍有不慎就会掉入坑里,在很久之前就遇到过类似的问题,本文就来围绕这个简单分析一下。到底该如何定一个布尔类型的成员变量。

一般情况下,我们可以有以下四种方式来定义一个布尔类型的成员变量:

  1. boolean success
  2. boolean isSuccess
  3. Boolean success
  4. Boolean isSuccess

以上四种定义形式,你日常开发中最常用的是哪种呢?到底哪一种才是正确的使用姿势呢?

通过观察我们可以发现,前两种和后两种的主要区别是变量的类型不同,前者使用的是boolean,后者使用的是Boolean。

另外,第一种和第三种在定义变量的时候,变量命名是success,而另外两种使用isSuccess来命名的。

首先,我们来分析一下,到底应该是用success来命名,还是使用isSuccess更好一点。

success 还是 isSuccess

到底应该是用success还是isSuccess来给变量命名呢?从语义上面来讲,两种命名方式都可以讲的通,并且也都没有歧义。那么还有什么原则可以参考来让我们做选择呢。

在阿里巴巴Java开发手册中关于这一点,有过一个『强制性』规定:

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

  1. class Model1 {
  2. private Boolean isSuccess;
  3. public void setSuccess(Boolean success) {
  4. isSuccess = success;
  5. }
  6. public Boolean getSuccess() {
  7. return isSuccess;
  8. }
  9. }
  10.  
  11. class Model2 {
  12. private Boolean success;
  13. public Boolean getSuccess() {
  14. return success;
  15. }
  16. public void setSuccess(Boolean success) {
  17. this.success = success;
  18. }
  19. }
  20.  
  21. class Model3 {
  22. private boolean isSuccess;
  23. public boolean isSuccess() {
  24. return isSuccess;
  25. }
  26. public void setSuccess(boolean success) {
  27. isSuccess = success;
  28. }
  29. }
  30.  
  31. class Model4 {
  32. private boolean success;
  33. public boolean isSuccess() {
  34. return success;
  35. }
  36. public void setSuccess(boolean success) {
  37. this.success = success;
  38. }
  39. }

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

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

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

既然,我们已经达成一致共识使用基本类型boolean来定义成员变量了,那么我们再来具体看下Model3和Model4中的setter/getter有何区别。

我们可以发现,虽然Model3和Model4中的成员变量的名称不同,一个是success,另外一个是isSuccess,但是他们自动生成的getter和setter方法名称都是isSuccesssetSuccess

Java Bean中关于setter/getter的规范

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

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

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

  1. public boolean is<PropertyName>();
  2. public void set<PropertyName>(boolean m);

通过对照这份JavaBeans规范,我们发现,在Model4中,变量名为isSuccess,如果严格按照规范定义的话,他的getter方法应该叫isIsSuccess。但是很多IDE都会默认生成为isSuccess。

那这样做会带来什么问题呢。

在一般情况下,其实是没有影响的。但是有一种特殊情况就会有问题,那就是发生序列化的时候。

序列化带来的影响

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

  1. public class BooleanMainTest {
  2.  
  3. public static void main(String[] args) throws IOException {
  4. //定一个Model3类型
  5. Model3 model3 = new Model3();
  6. model3.setSuccess(true);
  7.  
  8. //使用fastjson(1.2.16)序列化model3成字符串并输出
  9. System.out.println("Serializable Result With fastjson :" + JSON.toJSONString(model3));
  10.  
  11. //使用Gson(2.8.5)序列化model3成字符串并输出
  12. Gson gson =new Gson();
  13. System.out.println("Serializable Result With Gson :" +gson.toJson(model3));
  14.  
  15. //使用jackson(2.9.7)序列化model3成字符串并输出
  16. ObjectMapper om = new ObjectMapper();
  17. System.out.println("Serializable Result With jackson :" +om.writeValueAsString(model3));
  18. }
  19.  
  20. }
  21.  
  22. class Model3 implements Serializable {
  23.  
  24. private static final long serialVersionUID = 1836697963736227954L;
  25. private boolean isSuccess;
  26. public boolean isSuccess() {
  27. return isSuccess;
  28. }
  29. public void setSuccess(boolean success) {
  30. isSuccess = success;
  31. }
  32. public String getHollis(){
  33. return "hollischuang";
  34. }
  35. }

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

以上代码输出结果:

  1. Serializable Result With fastjson :{"hollis":"hollischuang","success":true}
  2. Serializable Result With Gson :{"isSuccess":true}
  3. 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中删除后,重新执行下以上代码,得到结果:

  1. Serializable Result With fastjson :{"success":true}
  2. Serializable Result With Gson :{"isSuccess":true}
  3. Serializable Result With jackson :{"success":true}

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

  1. public class BooleanMainTest {
  2. public static void main(String[] args) throws IOException {
  3. Model3 model3 = new Model3();
  4. model3.setSuccess(true);
  5. Gson gson =new Gson();
  6. System.out.println(gson.fromJson(JSON.toJSONString(model3),Model3.class));
  7. }
  8. }
  9.  
  10.  
  11. class Model3 implements Serializable {
  12. private static final long serialVersionUID = 1836697963736227954L;
  13. private boolean isSuccess;
  14. public boolean isSuccess() {
  15. return isSuccess;
  16. }
  17. public void setSuccess(boolean success) {
  18. isSuccess = success;
  19. }
  20. @Override
  21. public String toString() {
  22. return new StringJoiner(", ", Model3.class.getSimpleName() + "[", "]")
  23. .add("isSuccess=" + isSuccess)
  24. .toString();
  25. }
  26. }

以上代码,输出结果:

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规范的。无论哪种序列化框架,执行结果都一样。就从源头避免了这个问题。

引用以下R大关于阿里巴巴Java开发手册这条规定的评价(https://www.zhihu.com/question/55642203):

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

Boolean还是boolean?

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

  1. boolean success
  2. Boolean success

那么,到底应该是用Boolean还是boolean来给定一个布尔类型的变量呢?

我们知道,boolean是基本数据类型,而Boolean是包装类型。关于基本数据类型和包装类之间的关系和区别请参考一文读懂什么是Java中的自动拆装箱

那么,在定义一个成员变量的时候到底是使用包装类型更好还是使用基本数据类型呢?

我们来看一段简单的代码

  1. /**
  2. * @author Hollis
  3. */
  4. public class BooleanMainTest {
  5. public static void main(String[] args) {
  6. Model model1 = new Model();
  7. System.out.println("default model : " + model1);
  8. }
  9. }
  10.  
  11. class Model {
  12. /**
  13. * 定一个Boolean类型的success成员变量
  14. */
  15. private Boolean success;
  16. /**
  17. * 定一个boolean类型的failure成员变量
  18. */
  19. private boolean failure;
  20.  
  21. /**
  22. * 覆盖toString方法,使用Java 8 的StringJoiner
  23. */
  24. @Override
  25. public String toString() {
  26. return new StringJoiner(", ", Model.class.getSimpleName() + "[", "]")
  27. .add("success=" + success)
  28. .add("failure=" + failure)
  29. .toString();
  30. }
  31. }

以上代码输出结果为:

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

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

即对象的默认值是null,boolean基本数据类型的默认值是false

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

这里建议我们使用包装类型,原因是什么呢?

举一个扣费的例子,我们做一个扣费系统,扣费时需要从外部的定价系统中读取一个费率的值,我们预期该接口的返回值中会包含一个浮点型的费率字段。当我们取到这个值得时候就使用公式:金额*费率=费用 进行计算,计算结果进行划扣。

如果由于计费系统异常,他可能会返回个默认值,如果这个字段是Double类型的话,该默认值为null,如果该字段是double类型的话,该默认值为0.0。

如果扣费系统对于该费率返回值没做特殊处理的话,拿到null值进行计算会直接报错,阻断程序。拿到0.0可能就直接进行计算,得出接口为0后进行扣费了。这种异常情况就无法被感知。

这种使用包装类型定义变量的方式,通过异常来阻断程序,进而可以被识别到这种线上问题。如果使用基本数据类型的话,系统可能不会报错,进而认为无异常。

以上,就是建议在POJO和RPC的返回值中使用包装类型的原因。

但是关于这一点,作者之前也有过不同的看法:对于布尔类型的变量,我认为可以和其他类型区分开来,作者并不认为使用null进而导致NPE是一种最好的实践。因为布尔类型只有true/false两种值,我们完全可以和外部调用方约定好当返回值为false时的明确语义。

后来,作者单独和《阿里巴巴Java开发手册》、《码出高效》的作者——孤尽 单独1V1(qing) Battle(jiao)了一下。最终达成共识,还是尽量使用包装类型。

但是,作者还是想强调一个我的观点,尽量避免在你的代码中出现不确定的null值。

null何罪之有?

关于null值的使用,我在使用Optional避免NullPointerException、9 Things about Null in Java等文中就介绍过。

null是很模棱两可的,很多时候会导致令人疑惑的的错误,很难去判断返回一个null代表着什么意思。

图灵奖得主Tony Hoare 曾经公开表达过null是一个糟糕的设计。

我把 null 引用称为自己的十亿美元错误。它的发明是在1965 年,那时我用一个面向对象语言( ALGOL W )设计了第一个全面的引用类型系统。我的目的是确保所有引用的使用都是绝对安全的,编译器会自动进行检查。但是我未能抵御住诱惑,加入了Null引用,仅仅是因为实现起来非常容易。它导致了数不清的错误、漏洞和系统崩溃,可能在之后 40 年中造成了十亿美元的损失。

当我们在设计一个接口的时候,对于接口的返回值的定义,尽量避免使用Boolean类型来定义。大多数情况下,别人使用我们的接口返回值时可能用if(response.isSuccess){}else{}的方式,如果我们由于忽略没有设置success字段的值,就可能导致NPE(java.lang.NullPointerException),这明显是我们不希望看到的。

所以,当我们要定义一个布尔类型的成员变量时,尽量选择boolean,而不是Boolean。当然,编程中并没有绝对。

if(response.isSuccess){}else{}的方式,如果我们由于忽略没有设置success字段的值,就可能导致的更多相关文章

  1. 认识CSS中css引入方式、选择器、文本样式设置以及显示模式

    前端之HTML.CSS(三) CSS CSS-Cascading Styles Sheets,层叠样式表.用于网页的表现,主要用于布局和修饰网页. CSS引入方式 CSS的三种引入方式:行内样式,内部 ...

  2. python-关键字驱动接口框架中,接口关联字段进行值替换的实现方式

    前言 编写关键字驱动的接口自动化测试框架中,通过不同的取值方式,将需要关联的字段以及取出的值放到一个空字典中,需要将关联的字段进行值替换,下面是替换的实现方式 实现思路 import re temp_ ...

  3. VS中附加进程的方式调试IIS页面,以及设置断点无效问题解决

    以前调试网站的时候都习惯是直接在解决方案中右键调试——启动新实例,后来发现这样的缺点有: 1.启动比较慢: 2.一些浏览器的request参数无法带入: 3.不特殊指定启动url的话,VS会将页面加载 ...

  4. 使用tomcat的jndi方式连接mysql的字符编码设置

    最近新项目使用tomcat中配置jndi连接mysql的方式,在使用过程中发现查询条件为中文的时候查询不出结果,经过一通折腾,发现是jndi在连接数据库的时候忘记设置字符编码. 修改之后的完整配置如下 ...

  5. [SoapUI] 通过context获取response并解析里面的某个字段的值

    import com.eviware.soapui.support.GroovyUtils def groovyUtils = new GroovyUtils( context ) def realI ...

  6. 关于Sublime Text不能在打开方式中显示并且不能被设置成默认打开方式的问题

    解决方法: 1. Windows 输入 regedit 后 回车 打开注册表 2.找到 "HKEY_CLASSES_ROOT\Applications\sublime_text.exe\sh ...

  7. 为什么一定要调用 setlocale 呢? 因为在 C/C++ 语言标准中定义了其运行时的字符集环境为 "C" ,也就是 ASCII 字符集的一个子集。使用setlocal改变整个应用程序的字符集编码方式(wcstombs使用前要设置 setlocale (LC_ALL, "chs"); )

    setlocale 配置地域化信息. 语法: string setlocale(string category, string locale); 返回值: 字符串 函数种类: 操作系统与环境   内容 ...

  8. response对象处理HTTP文件头(禁用缓存、设置页面自动刷新、定时跳转网页)

    response对象处理HTTP文件头 制作人:全心全意 禁用缓存 在默认情况下,浏览器将会对显示的网页内容进行缓存.这样,当用户再次访问相关网页时,浏览器会判断网页是否有变化,如果没有变化则直接显示 ...

  9. iconfont图标symbol引用方式,有的图标不能通过设置color样式来修改颜色的解决办法

    现象:iconfont安装后的图标,是通过symbol引用方式,有的图标不能通过color修改颜色的解决办法,有的又可以. <svg class="icon" aria-hi ...

随机推荐

  1. art-template模板判断

    1.添加模板 <script id="userinfo" type="text/template">           {{ if id == n ...

  2. 2.成产出现 max(vachar2)取值问题

    uat 测试结果正确max(9)>max(8),结果生产出现 max(9)>max(12) 原因:字符类型,默认比较第一个字符的ASCII码. 解决方式: max(to_number(va ...

  3. 提示用户输入一个1-40之间的数字,使用if语句根据输入数字的大小进行判断,如果输入的数字在

    提示用户输入一个1-40之间的数字,使用if语句根据输入数字的大小进行判断,如果输入的数字在 num_user=input('输入一个1-40之间的整数:') num_int=int(num_user ...

  4. Python3之Django的Cookie与Session的使用

    一.Cookie的使用 1.设置Cookie url.set_cookie("tile","zhanggen",expires=value,path='/' ) ...

  5. 一起学Vue之模板语法

    概述 Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据.所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循规范的浏览器和 HTM ...

  6. asp.net core 3.0 MVC JSON 全局配置

    asp.net core 3.0 MVC JSON 全局配置 System.Text.Json(default) startup配置代码如下: using System.Text.Encodings. ...

  7. 查询BPC动态表

    今天BASIS说后台有张数据表(/1CPMB/ABLBCAD)数据量已超过20亿,需要归档,但是不清楚是哪个业务模型. 有两种方式可以查询BPC动态生成的表名. (1)根据命名规则 环境前缀:apps ...

  8. 第一个MyBatis程序(博客初写者)

    第一个Mybatis程序 一.环境: 1.JDK1.8 2.MYSQL5.7 3.IDEA 4.MAVEN 3.63 二.Mybatis认识: 1.查看官方文档 https://mybatis.org ...

  9. 未来已来:云原生 Cloud Native

    作者:天知,原文链接 前言 自 2013 年容器(虚拟)技术(Docker)成熟后,后端的架构方式进入快速迭代的阶段,出现了很多新兴概念: 微服务 k8s Serverless IaaS:基础设施服务 ...

  10. threejs 绘制辅助网格

    GridHelper.js可以帮助绘制一个xz平面网格,它没有提供更多的参数,所以不能用于生成xy网格. xy网格实现代码如下: var size = 6000; var divisions = 50 ...