
对应阿里巴巴开发手册-编码规约-命名风格第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
布尔类型存在基本数据类型和包装类型,所以存在四种情况
1 2 3 4
| private boolean success; private boolean isSuccess; private Boolean success; private Boolean isSuccess;
|
通过IDEA自动生成Getter/Setter,可以得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| 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可以看出
序列化
开发手册的规约中提到,is开头的属性在部分框架解析时会引起序列化的错误,那么拿常用的JSON序列化进行举例,分别使用JackJson、FastJson和Gson进行序列化以及反序列化来验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| 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));
|
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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框架的分析可以得出:
由于不同的序列化工具,在进行序列化时使用的策略并不一致,所以对于同一个对象序列化的结果可能并不一致。
反序列化
那么使用相同的json反序列时会发生什么情况呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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()); }
|
执行结果:
1 2 3 4
| 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序列化工具设置为如果没有字段自动忽略,不要抛出异常的形式,那么再将测试代码执行测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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()); }
|
执行结果:
1 2 3 4
| 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并没有明确的约定,在具体的项目和团队中,可以根据自己的编码规范和偏好进行调整和约定,那么再结合序列化工具,又有可能带来未知的问题。
为了避免使用不当可能引发的问题,干脆规约中进行字段命名的限制,也是一定的道理。