程序员新人上午使用 isXxx 形式定义布尔类型,下午就被劝退?

沉默王二

共 5595字,需浏览 12分钟

 ·

2022-06-21 02:54

二哥编程知识星球 (戳链接加入)正式上线了,来和 260 多名 小伙伴一起打怪升级吧!这是一个 Java 学习指南 + 编程实战的私密圈子,你可以向二哥提问、帮你制定学习计划、跟着二哥一起做实战项目,冲冲冲。

Java 程序员进阶之路网址:https://tobebetterjavaer.com

小二是新来的背锅侠,哦,不不不,新来的实习生。面试过程中表现得非常不错,各种问题对答如流,老板和我都倍感欣慰。

这么优秀的人,绝不能让他浪费一分一秒,于是很快,我就给他安排了一个非常简单的任务。

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

关于这个”本次请求是否成功”的字段的定义,我见过很多不同的开发者,定义的方式都不同,尤其是在属性的命名上,有人用 success,有人用 isSuccess 表示。

从语义上面来讲,两种命名方式都可以讲的通,并且也都没有歧义。

这不,小二就采用了 isSuccess 的形式来定义布尔类型的变量,于是下午 review 的时候就被眼尖的老板揪了出来。老板毕竟是在阿里待过的,对这些细节扣的很细。

于是大发雷霆,准备当场劝退小二,被我拦了下来。毕竟自己面试的人,不看僧面看佛面,是吧?于是老板答应我说再试用一个月看看。

isSuccess 会有什么问题呢?小二很是不解。

那根据 JavaBeans 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);

success 方法的 getter 应该是 isSuccess/getSuccess,而 isSuccess 的 getter 应该是 isIsSuccess/getIsSuccess。

但是很多人,在使用 isSuccess 作为属性名的时候,还是会采用 isSuccess/getSuccess 作为 getter 方法名,尤其是现在的很多 IDE 在默认生成 getter 的时候也是会生成 isSuccess。

在一般情况下,其实是没有影响的。但是有一种特殊情况就会有问题,那就是发生序列化的时候可能会导致参数转换异常。

我们先来定义一个 JavaBean:

class Model 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 getWanger(){
       return "hollis";
   }
}

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

我们分别使用不同的 JSON 序列化工具来对这个类的对象进行序列化和反序列化:

public class BooleanMainTest {
     public static void main(String[] args) throws IOException {
         //定一个Model类型
         Model model = new Model();
         model.setSuccess(true);
         
         //使用fastjson(1.2.16)序列化model成字符串并输出
         System.out.println("Serializable Result With fastjson :" + JSON.toJSONString(model));
         
         //使用Gson(2.8.5)序列化model成字符串并输出
         Gson gson =new Gson();
         System.out.println("Serializable Result With Gson :" +gson.toJson(model));
         
         //使用jackson(2.9.7)序列化model成字符串并输出
         ObjectMapper om = new ObjectMapper();
         System.out.println("Serializable Result With jackson :" +om.writeValueAsString(model));
     }
}

以上代码输出结果:

Serializable Result With fastjson :{"wanger":"hollis","success":true}

Serializable Result With Gson :{"isSuccess":true}

Serializable Result With jackson :{"success":true,"wanger":"hollis"}

在 fastjson 和 jackson 的结果中,原来类中的 isSuccess 字段被序列化成 success,并且其中还包含 wanger 值。而 Gson 中只有 isSuccess 字段。

我们可以得出结论:fastjson 和 jackson 在把对象序列化成 json 字符串的时候,是通过反射遍历出该类中的所有 getter 方法,得到 getHollis 和 isSuccess,然后根据 JavaBeans 规则,他会认为这是两个属性 hollis 和 success 的值。直接序列化成 json:

{“wanger”:”沉默王二”,”success”:true}

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

{“isSuccess”:true}

可以看到,由于不同的序列化工具,在进行序列化的时候使用到的策略是不一样的,所以,对于同一个类的同一个对象的序列化结果可能是不同的。那么,如果我们把一个对象使用 fastjson 进行序列化,再使用 Gson 反序列化会发生什么呢?

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

以上代码,输出结果:

Model[isSuccess=false]

这和我们预期的结果完全相反,原因是因为 JSON 框架通过扫描所有的 getter 后发现有一个 isSuccess 方法,然后根据 JavaBeans 的规范,解析出变量名为 success,把 model 对象序列化成字符串后内容为{"success":true}。

根据{"success":true}这个 json 串,Gson 框架在通过解析后,通过反射寻找 Model 类中的 success 属性,但是 Model 类中只有 isSuccess 属性,所以,最终反序列化后的 Model 类的对象中,isSuccess 则会使用默认值 false。

但是,一旦以上代码发生在生产环境,这绝对是一个致命的问题

所以,作为开发者,我们应该想办法尽量避免这种问题的发生。

所以,建议大家使用 success 而不是 isSuccess 这种形式。 这样,该类里面的成员变量时 success,getter 方法是 isSuccess,这是完全符合 JavaBeans 规范的。无论哪种序列化框架,执行结果都一样。就从源头避免了这个问题。


没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟

推荐阅读

浏览 33
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报