关于java:当匈牙利命名法遇到-JAVA-会怎么样

43次阅读

共计 3695 个字符,预计需要花费 10 分钟才能阅读完成。

先点赞再看,养成好习惯

背景

之前在做一个老我的项目重构的时候,因为数据库不能改变,所以还是持续沿用之前的老数据库。保险公司嘛,哪怕加了个互联网保险的 title,业务和零碎还是偏传统的,数据模型不会轻易的更新;所以这个零碎年代比拟长远,而且它的数据库表命名形式采纳的还是 匈牙利命名法,导致在重构时因为这个命名形式恶心了我良久……

匈牙利命名法

匈牙利命名法(Hungarian notation),由 1972 年至 1981 年在施乐帕洛阿尔托钻研核心工作的 - 程序员 查尔斯·西蒙尼创造,这位前辈前面 成了微软的总设计师

这个命名法的特点是,在命名后面减少 类型的前缀,就像这样:

  • c_name – 姓名,字符串(Char)类型
  • n_age – 年龄,数字(Number)类型
  • t_birthday – 生日,日期 / 工夫(Time)类型

可不要小看这个命名法,当年可是很风行的,而且直到明天还是有一些零碎依然在沿用这个命名规范,比方微软的 Win32 API:

况且这个命名法,也不是一无是处,还是有肯定的长处的,至多我一眼就能够看出这个字段的类型。​

只不过在明天看起来有点怪怪的,不太合乎当今的设计格调,如果放到人名上就更有意思了:

  • 赵四
  • 谢大脚
  • 刘能

当匈牙利命名法遇到 JAVA

咱们这次的重构指标,是要放弃老零碎表不动的状况下,齐全重写。可是新零碎是 Java 语言来开发,Java 可是驼峰命名规范的,当这个匈牙利命名法的表迁徙到驼峰命名法的 Java 语言会怎么样?

比方 c_name 这个命名,到 Java 里之后,是改为 CName 呢,还是 cName 呢?如同怎么都有点奇怪,不过最初咱们还是抉择了 CName,将类型的前缀齐全大写,至多看着略微失常一点,没那么反人类

  • c_name -> CName
  • n_age -> NAge
  • t_birthday -> TBirthday

序列化的问题

刚确定了命名形式,还没开心多久,我就遇到了一个十分好受的问题…… ​

因为是 Spring 全家桶,Web 层应用的也是 Spring MVC。Spring MVC 的默认 JSON 解决库是 Jackson,在 Web 层返回 JSON 后,数据就成了这个样子:

{
    "nid":1,
    "ctitle":"Effective JAVA"
}

可我这个 POJO 类是将匈牙利命名法的字段转了大写,它长这样啊:

public class Book {
    private Integer NId;
    private String CTitle;

    public Integer getNId() {return NId;}

    public void setNId(Integer NId) {this.NId = NId;}

    public String getCTitle() {return CTitle;}

    public void setCTitle(String CTitle) {this.CTitle = CTitle;}
}

大写字段名,在转 JSON 之后变成了小写……要是把这个小写的字段给了前端,前端命名必定会用小写,那前端在发送到后端时后肯定也是小写。​

那因为咱们出入参序列化都是 Jackson,对于 Jackson 来说,怎么出就怎么进,还是能解析进去的,看似也没啥问题,只是恶心了一点,前后端一个大写一个小写。​

不过……事件并没有那么简略。后端不会将所有的报文都作为 POJO 的字段,会存在一些动静的字段,用 Map 存储。可 Map 存储的这些字段,Jackson 在输入时不会转为小写,还是保留原有的大写模式,这样就会导致给前端的字段,尽管都是匈牙利格调,但有些大写有些小写……尽管前端不晓得打不打人,但我可不敢这么玩

不同序列化库的解决机制不同

Jackson 的匈牙利命名法解决

其实 Jackson 序列化之后转小写的起因也很好解释,Java 的设计规范,就是 Bean 中的属性用 private 润饰,而后提供 getter/setter 来提供读取 / 写入。那么在序列化时,Jackson 通过字段的 Getter 来拜访属性值,甚至用 Getter 办法来解析属性名。​

Java 的 getter 办法命名规范是,将小写驼峰转大写驼峰,Jackson 在通过 getter 办法名解析字段名时,将 getNID 解析为 nid 了,所以导致最终输入的字段名为小写的 nid。

FastJson 的匈牙利命名法解决

原本是想定制化一下 Jackson 的命名解决的,但想了一下感觉没必要,毕竟是咱们命名不规范,何必去改这个命名解决机制呢,划不来。​

所以我又尝试着换一种 JSON 库去解决这个命名问题,先试试阿里的 FastJSON,看看这个国产库的解决怎么样:

{
    "cTitle":"Effective JAVA",
    "nId":1
}

看到这个序列化后果时,我差点把我键盘上的 Backspace 按断了……同样是通过 getter 办法解析属性名,两个库的解析规定还能不一样…… ​

在 FastJson 里,c 是小写了,可 Title 里的 T 还是大写,@#¥%……& 此处省略 100 字…… ​

沉着一下之后,心里默念了几遍:“不怪他人,是咱们本人的命名问题,不符合标准人家怎么解析都不关你事……”​

不过 JAVA 的生态这么好,JSON 库也不止这两个,再换一个就是,Google 的 Gson 也很不错嘛!

Gson 的匈牙利命名法

于是我又换成了 Gson,配置完 Spring MVC Gson Converter 之后,输入后果:

{
    "NId":1,
    "CTitle":"Effective JAVA"
}

终于换到一个能失常显示原始字段名的 JSON 库了,不过它既然能放弃原有字段名,而不是 getter 里解析的属性名,那么它必定不是解析 getter 办法名的 ​

于是我又去翻了下 Gson 的源码,发现它是间接 getDeclaredFields(),而后 makeAccessible,最初间接通过 Field.getValue 的形式间接获取属性值的。​

相比 Jackson 和 FastJson 里通过 getter 获取属性列表,而后通过调用 getter 办法来获取属性值的办法来说,强制拜访公有属性这种做法还是太暴力了,不过我喜爱,至多它能轻松解决了我的问题

其余的序列化问题

除了 JSON 这种文本模式的序列化之外,一些二进制的序列化也会有这个难堪的问题,获取属性列表 / 属性值,到底是用解析 getter 办法的形式,还是间接 makeAccessible 暴力拜访公有属性呢?​

这个我测试了一下,比方在 Dubbo 的默认序列化形式(Dubbo 简化的 Hession2)中,依然是 getDeclaredFields,而后拜访公有属性 ​

在 JDK 的内置序列化 ObjectOutputStream 中,也是 getDeclaredFields,而后拜访公有属性。​

不过这种 getDeclaredFields,而后拜访公有属性值的形式,也会有一些劣势。比方在遇到代码混同时,公有属性的值会被全副打乱,而 public 的办法却不会,所以在遇到混同的代码时,这种形式就会乱套了,而通过 getter 办法解析的形式就不会有问题。​

所以吧,这个获取属性的形式并没有对错,怎么都能够,不过我认为还是应该通过 getter/setter 的形式来操作,合乎 JAVA 的标准。

补充

感激 @用户 3323102545477 的揭示,Jackson 很弱小,反对配置属性的获取形式,能够配置 Visibility 来实现只通过 Field 而不通过 getter 来获取属性:

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY,
        getterVisibility = JsonAutoDetect.Visibility.NONE)
public class Book {
    private Integer NId = 1;

    private String CName = "John";
}

全局配置更不便:

ObjectMapper objectMapper = new ObjectMapper();

// 配置 field 为可见
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);

// 配置 getter 不可见
objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE);

总结

Java 里拜访公有属性值,规范的形式是通过 getter 办法,但还是提供了一个 makeAccessible 操作,能够让咱们间接拜访公有属性或者公有办法。​

始终不太明确 JDK 为什么要这么设计,既然曾经指定了标准,为什么还要开个后门呢?如果限度死了这个性能,那么所有序列化的库不就能够对立了,再也没有这种恶心的不统一问题!​

但比照以上三个序列化库,我感觉都没错,Jackson/FastJson 依照标准的形式来,老老实实的通过 getter 办法来获取,而 Gson 就有点暴力,间接拜访公有属性,各有劣势。

补充:Jackson 反对属性的获取形式,默认是通过 getter 获取,但也能够配置通过 Field 获取,配置形式见下面的 补充 局部

正文完
 0