
对应阿里巴巴开发手册-编码规约-命名风格第9条,规约中写的比较模糊,之前只知道这样命名可能会存在问题,这里探索一下具体什么场景会存在问题。
对上面举的反例感觉有些怪,**基本数据类型Boolean isDeleted属性的Getter方法是isDeleted()**,手册应该写的有问题,印象中基本数据类型boolean是这样的,但是Boolean包装类型并不是如此,后面会对这里进行探索,坑很大。
实例深究
JavaBean规范
首先了解下JavaBean的规范,属性应该由一组读写方法(Getter/Setter)来访问,如果操作的字段为boolean类型,此时Getter不应命名为getXXX方法,而应该是isXXX,如果boolean类型的属性名以is开头,则将属性名中的is自动去除,例如boolean isSuccess的Getter是isSuccess(),但是对于Boolean类型的属性并没有明确的规范进行约定。
以下示例基于IDEA 2023.1 JDK11
布尔类型存在基本数据类型和包装类型,所以存在四种情况
private boolean success;
private boolean isSuccess;
private Boolean success;
private Boolean isSuccess;
通过IDEA自动生成Getter/Setter,可以得到
class Result1{
private boolean success;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
}
class Result2{
private boolean isSuccess;
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
}
class Result3{
private Boolean success;
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
}
class Result4{
private Boolean isSuccess;
public Boolean getSuccess() {
return isSuccess;
}
public void setSuccess(Boolean success) {
isSuccess = success;
}
}
从IDEA自动生成的Getter/Setter可以看出
- boolean类型Getter是isXXX()形式,如果以is开头则会省略is
- Boolean类型Getter是getXXX()形式,如果以is开头则会省略is
- 两种类型的Setter是相同的
序列化
开发手册的规约中提到,is开头的属性在部分框架解析时会引起序列化的错误,那么拿常用的JSON序列化进行举例,分别使用JackJson、FastJson和Gson进行序列化以及反序列化来验证
Result1 result1 = new Result1();
result1.setSuccess(true);
Result2 result2 = new Result2();
result2.setSuccess(true);
Result3 result3 = new Result3();
result3.setSuccess(true);
Result4 result4 = new Result4();
result4.setSuccess(true);
ObjectMapper jackson = new ObjectMapper();
Gson gson = new Gson();
System.out.println("boolean success");
System.out.println("jackson: " + jackson.writeValueAsString(result1));
System.out.println("gson: " + gson.toJson(result1));
System.out.println("fastjson: " + JSON.toJSONString(result1));
System.out.println("boolean isSuccess");
System.out.println("jackson: " + jackson.writeValueAsString(result2));
System.out.println("gson: " + gson.toJson(result2));
System.out.println("fastjson: " + JSON.toJSONString(result2));
System.out.println("Boolean success");
System.out.println("jackson: " + jackson.writeValueAsString(result3));
System.out.println("gson: " + gson.toJson(result3));
System.out.println("fastjson: " + JSON.toJSONString(result3));
System.out.println("Boolean isSuccess");
System.out.println("jackson: " + jackson.writeValueAsString(result4));
System.out.println("gson: " + gson.toJson(result4));
System.out.println("fastjson: " + JSON.toJSONString(result4));
运行结果:
boolean success
jackson: {"success":true}
gson: {"success":true}
fastjson: {"success":true}
boolean isSuccess
jackson: {"success":true}
gson: {"isSuccess":true}
fastjson: {"success":true}
Boolean success
jackson: {"success":true}
gson: {"success":true}
fastjson: {"success":true}
Boolean isSuccess
jackson: {"success":true}
gson: {"isSuccess":true}
fastjson: {"success":true}
根据这些结果以及对应JSON框架的分析可以得出:
- jackson和fastjson在进行对象的序列化时,是通过反射遍历出该类中所有的Getter方法,得到
isSuccess(),他们会认为字段属性是success,然后序列化成json - Gson是通过反射遍历类中所有的属性,并将其值序列化成json
由于不同的序列化工具,在进行序列化时使用的策略并不一致,所以对于同一个对象序列化的结果可能并不一致。
反序列化
那么使用相同的json反序列时会发生什么情况呢?
{"isSuccess":true}
String jsonString = "{\"isSuccess\":true}";
ObjectMapper jackson = new ObjectMapper();
Gson gson = new Gson();
System.out.println("boolean success");
try{
Result1 result1 = jackson.readValue(jsonString, Result1.class);
System.out.println("jackson: " + result1);
}catch (Exception e){
System.out.println("jackson反序列化异常" + e.getMessage());
}
try{
Result2 result2 = gson.fromJson(jsonString, Result2.class);
System.out.println("gson: " + result2);
}catch (Exception e){
System.out.println("gson反序列化异常" + e.getMessage());
}
try{
Result3 result3 = JSON.parseObject(jsonString, Result3.class);
System.out.println("fastjson: " + result3);
}catch (Exception e){
System.out.println("fastjson反序列化异常" + e.getMessage());
}
执行结果:
jackson反序列化异常Unrecognized field "isSuccess" (class com.meifute.m2.item.BooleanNameTest$Result1), not marked as ignorable (one known property: "success"])
at [Source: (String)"{"isSuccess":true}"; line: 1, column: 18] (through reference chain: com.meifute.m2.item.BooleanNameTest$Result1["isSuccess"])
gson: Result2{isSuccess=true}
fastjson: Result3{success=true}
通过以上执行结果可以看出,jackson反序列化时异常,那么在实际项目中,我们经常会将我们的json序列化工具设置为如果没有字段自动忽略,不要抛出异常的形式,那么再将测试代码执行测试
String jsonString = "{\"isSuccess\":true}";
ObjectMapper jackson = new ObjectMapper();
jackson.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Gson gson = new Gson();
System.out.println("boolean success");
try{
Result1 result1 = jackson.readValue(jsonString, Result1.class);
System.out.println("jackson: " + result1);
}catch (Exception e){
System.out.println("jackson反序列化异常" + e.getMessage());
}
try{
Result2 result2 = gson.fromJson(jsonString, Result2.class);
System.out.println("gson: " + result2);
}catch (Exception e){
System.out.println("gson反序列化异常" + e.getMessage());
}
try{
Result3 result3 = JSON.parseObject(jsonString, Result3.class);
System.out.println("fastjson: " + result3);
}catch (Exception e){
System.out.println("fastjson反序列化异常" + e.getMessage());
}
执行结果:
boolean success
jackson: Result1{success=false}
gson: Result2{isSuccess=true}
fastjson: Result3{success=true}
可以看出,jackson在反序列化时,如果忽略不匹配的字段,boolean会存在默认值false,导致预期和匹配不符。
那么其实反序列化的这个场景,和阿里巴巴开发手册中中这条正是交响呼应(但是这条规约也是有待商榷,Boolean存在null,对于仅是否两种情况下,业务中不处理可能会存在NPE的问题)
通过上面的例子,可以看到布尔类型为什么不要使用is开头的原因,是因为各种序列化工具的策略不一样,稍有不慎有可能导致预期和结果不符的情况出现。
Getter深究
JavaBean规范对boolean类型的Getter进行了明确的约定,但是Boolean并没有明确的约定,IDEA对于is开头的Boolean属性会自动去掉is并生成getXXX样式的Getter,那么所有的开发工具或者代码生成工具都是如此处理的吗?
带着这个疑问,在eclipse中进行了试用,eclispe中对于boolean属性Getter的生成和IDEA一样是符合JavaBean规范的,但是对于is开头的Boolean属性生成的Getter是getIsXXX样式,和IDEA中不一样。
那么像经常使用lambok中又是如何处理的呢?经过测试lambok的处理方式和eclipse一致。
也就是说由于Boolean并没有明确的约定,在具体的项目和团队中,可以根据自己的编码规范和偏好进行调整和约定,那么再结合序列化工具,又有可能带来未知的问题。
为了避免使用不当可能引发的问题,干脆规约中进行字段命名的限制,也是一定的道理。