开心一刻

一老农养猪,母的,怎么配也怀不上小猪,于是找兽医
兽医来到他家看了猪一眼说:不行就人工授精吧
老农绕着猪走了三圈,点燃一根烟,貌似下了很大决心,说到:行倒是行,就怕生下来像我

被网友吐槽

都说了布尔类型的变量不要加 is 前缀,非要加,这不是坑我了嘛一经发布,吐槽不断

有吐槽 框架

有吐槽 IDE

有吐槽 Java

有吐槽楼主落后,不用 Lombok

也有吐槽 水文

说实话,前面的吐槽不会让我有任何波澜,但是这个 水文 让我内心泛起了 涟漪

作为一个严谨的博主,怎能让 水文 出现在我的写作生涯中?特来补充、完善下

JavaBeans 规范

关于 JavaBean 的属性该如何读写,sun 官方给出了规范:JavaBeans Spec,其中有这么一段

与本文密切相关,我给你们翻译下

  1. 简单属性

    默认情况下,通过识别特定格式的方法(方法命名模式)来定位属性:

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

    如果我们发现一对方法:get<PropertyName>()set<PropertyName>set 方法的入参类型与 get 方法的返回类型相同,那么我们将这对方法视为 <propertyName> 的读写属性。我们将使用 get<PropertyName> 方法获取属性值,并使用 set<PropertyName> 方法设置属性值。这对方法可以位于同一个类中,也可以一个位于基类中,另一个位于派生类中。

    如果我们只找到了这对方法中的某个方法,我们则认为只定义了 <propertyName> 的只读属性或只写属性

    默认情况下,我们预设属性既非绑定属性,亦非约束属性

    绑定属性与约束属性与本文无关,不展开

    因此,一个简单的可读写属性 foo 通常会通过以下方法对表示:

    public Wombat getFoo();
    public void setFoo(Wombat w);
  2. 布尔属性

    此外,对于布尔属性,我们允许 getter 方法匹配以下模式:

    public boolean is<PropertyName>();

    我们可以用is<PropertyName> 方法来替换 get<PropertyName> 方法,也可用 is<PropertyName> 方法补充 get<PropertyName> 方法。无论哪种情况,只要布尔属性存在 is<PropertyName> 方法,则使用 is<PropertyName> 方法获取属性值。

    一个布尔属性示例可能如下:

    public boolean isMarsupial();
    public void setMarsupial(boolean m);

至此,我相信大家对 JavaBeansettergetter 规范有了个基本了解了,我再总结下

  1. 简单属性(非布尔属性),通过 get<PropertyName>()set<PropertyName> 来获取、设置属性值
  2. 布尔属性,通过 set<PropertyName> 设置属性值,通过 get<PropertyName>is<PropertyName> 来获取属性值,如果两个获取方法同时存在,使用 is<PropertyName> 来获取属性值

规范,官方是制定了,但实现者是不是按规范实现的呢,我们以 IntelliJ IDEALombok 为例,来看看它们是否遵循了规范

  1. IDEA

    版本:IntelliJ IDEA 2023.3.2

    生成的 settergetter 方法如下

    /**
    * @author 青石路
    */
    public class JavaBeanEntity {
    private String id;
    private int age;
    private boolean enabled; public String getId() {
    return id;
    } public void setId(String id) {
    this.id = id;
    } public int getAge() {
    return age;
    } public void setAge(int age) {
    this.age = age;
    } public boolean isEnabled() {
    return enabled;
    } public void setEnabled(boolean enabled) {
    this.enabled = enabled;
    }
    }

    属性 idage 的类型不是布尔类型,其 settergetter 方法符合简单属性的规范;属性 enabled 的类型是布尔类型,其 getter 方法是 isEnabledsetter 方法是 setEnabled,符合布尔属性的规范

    也就是说 IDEA 遵循了 JavaBeans 属性规范

  2. Lombak

    我们再来看看 Lombok,版本:1.18.30

    属性 idage 的类型不是布尔类型,其 settergetter 方法符合简单属性的规范;属性 enabled 的类型是布尔类型,其 getter 方法是 isEnabledsetter 方法是 setEnabled,符合布尔属性的规范

    也就是说 Lombok 也遵循了 JavaBeans 属性规范

JSON 序列化与反序列化

因为我们平时是基于 Spring Web 提供 HTTP 接口,Spring Web 默认又是使用 Jackson 完成 JavaBean 实例与 JSON 之间的转换,所以我们基于 Jackson 来验证下 JavaBean 实例与 JSON 之间的转换是否正常;验证之前,我先调整下 JavaBeanEntityisEnabled 方法

public boolean isEnabled() {
System.out.println("isEnabled 方法被调用");
return enabled;
}

增加了一行输出:isEnabled 方法被调用,方便验证 isEnabled 被调用了;我们先来看 Bean 实例转 JSON

public static void main(String[] args) throws Exception {
JavaBeanEntity entity = new JavaBeanEntity();
entity.setId("1");
entity.setAge(18);
entity.setEnabled(true); ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(entity));
}

可以看到转换是没问题的;我们接着看下 JSON 转 Bean 实例

public static void main(String[] args) throws Exception {
String jsonStr = "{\"id\":\"44\",\"age\":16,\"enabled\":false}"; ObjectMapper mapper = new ObjectMapper();
JavaBeanEntity javaBeanEntity = mapper.readValue(jsonStr, JavaBeanEntity.class);
System.out.printf("id: %s, age: %d, enabled: %b%n",
javaBeanEntity.getId(), javaBeanEntity.getAge(), javaBeanEntity.isEnabled());
}

JSON 转实例也是没问题的;如果 enabled 的 getter 方法不是以 is 开头,而是以 get 开头,会不会有问题呢,我们来看下

public boolean getEnabled() {
System.out.println("getEnabled 方法被调用");
return enabled;
}

同样没问题;如果 enabled 的 getter 方法同时存在 isget,是否如规范规定的那样:is 方法生效,我们来看看

public boolean isEnabled() {
System.out.println("isEnabled 方法被调用");
return enabled;
} public boolean getEnabled() {
System.out.println("getEnabled 方法被调用");
return enabled;
}

生效的竟然是 getEnabled 方法,不符合 JavaBeans 规范吖!!!这是为什么?

我们跟下 Jackson 的源码,版本 2.13.5,看看其实现;问题又来了:怎么跟 Jackson 的源码?切入点其实很好找,getEnabled 不是被调用了吗,直接在其方法体内打个断点

然后 debug 运行,从调用栈中切入

从上往下看,invoke 相关的就不用看了,这是反射调用,所以我们从 serializeAsField 开始寻找答案,直接鼠标左击调用栈中的 serializeAsField

此时,已经采用 getEnabled 了,说明选择 isEnabled 还是 getEnabled 的逻辑已经完成了,我们应该继续往下看调用栈中的 serializeFields

我们看下 _props 内容

此时,属性 enabled 的 getter 方法已经确定是 genEnabled,这个时候我们不能继续跟调用栈了,而是要跟 _props 的赋值过程了,跟的过程不是那么简单,我省略一部分,直接带你们看重点

两个 getter 方法目前都存在,Jackson 还未进行抉择,说明离真相很近了,我们继续跟进 removeIgnorableTypes ,一路跟进去,会来到 POJOPropertyBuilder#getGetter 方法

@Override
public AnnotatedMethod getGetter()
{
// Easy with zero or one getters...
Linked<AnnotatedMethod> curr = _getters;
if (curr == null) {
return null;
}
Linked<AnnotatedMethod> next = curr.next;
if (next == null) {
return curr.value;
}
// But if multiple, verify that they do not conflict...
for (; next != null; next = next.next) {
/* [JACKSON-255] Allow masking, i.e. do not report exception if one
* is in super-class from the other
*/
Class<?> currClass = curr.value.getDeclaringClass();
Class<?> nextClass = next.value.getDeclaringClass();
if (currClass != nextClass) {
if (currClass.isAssignableFrom(nextClass)) { // next is more specific
curr = next;
continue;
}
if (nextClass.isAssignableFrom(currClass)) { // current more specific
continue;
}
}
/* 30-May-2014, tatu: Three levels of precedence:
*
* 1. Regular getters ("getX")
* 2. Is-getters ("isX")
* 3. Implicit, possible getters ("x")
*/
int priNext = _getterPriority(next.value);
int priCurr = _getterPriority(curr.value); if (priNext != priCurr) {
if (priNext < priCurr) {
curr = next;
}
continue;
}
throw new IllegalArgumentException("Conflicting getter definitions for property \""+getName()+"\": "
+curr.value.getFullName()+" vs "+next.value.getFullName());
}
// One more thing; to avoid having to do it again...
_getters = curr.withoutNext();
return curr.value;
}

代码就不分析了,相信你们都能看懂,我们来看下其中的注释

结合 _getterPriority

protected int _getterPriority(AnnotatedMethod m)
{
final String name = m.getName();
// [databind#238]: Also, regular getters have precedence over "is-getters"
if (name.startsWith("get") && name.length() > 3) {
// should we check capitalization?
return 1;
}
if (name.startsWith("is") && name.length() > 2) {
return 2;
}
return 3;
}

答案已然揭晓

三个优先级,从高到底分别是

  1. 常规 getters(get<PropertyName>()
  2. is getters(is<PropertyName>()
  3. 隐式的,可能的 getters(propertyName()

所以,生效的是 getEnabled,也就是说

Jackson 2.13.5 对 JavaBean 属性的 getter 实现,遵循了 JavaBeans 规范,但又没完全遵循

此刻,我觉得你们应该联想到其他问题

  1. 隐式的,可能的 getters(propertyName())什么时候会生效
  2. Jackson 的其他版本也是这样实现的吗
  3. Hutool 的实现又是怎样的

知识面是不是一下就打开了?

布尔包装类

我们仔细看 JavaBeans 规范对布尔类型的规定

是不是只对布尔基础类型进行了规范,并未对其包装类型进行说明?既然官方都未说明,那各个实现者就可以按自己的规则来实现了

  1. IDEA

    /**
    * @author 青石路
    */
    public class JavaBeanEntity {
    private String id;
    private Integer age;
    private Boolean enabled; public String getId() {
    return id;
    } public void setId(String id) {
    this.id = id;
    } public Integer getAge() {
    return age;
    } public void setAge(Integer age) {
    this.age = age;
    } public Boolean getEnabled() {
    return enabled;
    } public void setEnabled(Boolean enabled) {
    this.enabled = enabled;
    }
    }

    属性 enabled 的类型是 Boolean,其 getter 方法是 getEnabled

  2. Lombok

    属性 enabled 的类型是 Boolean,其 getter 方法是 getEnabled

所以 Boolean 属性与 boolean 属性的 getter 实现有所不同,各个实现者可以按自己的规则来实现 Boolean 类型属性的 getter

再带你们回顾下 Java开发手册 中的一项规约

结合这项规约来看的话,JavaBeans 规范对布尔类型属性 getter 的规定,是不是就没什么约束力了?

is 前缀的布尔属性

JavaBeans 规范并未明确规定 is 开头的布尔属性的 getter 该如何实现,那么套用规范中 boolean 属性的规则的话,getter 就会以两个 is 开头,例如 isIsEnabled,看着是不是有点反人类?所以实现者就会按自己的规则进行实现,同样以 IDEALombok 为例,我们来看看它们是怎么实现这种情况的,先看基本数据类型 boolean

  1. IDEA

    /**
    * @author 青石路
    */
    public class JavaBeanEntity {
    private String id;
    private Integer age;
    private boolean isEnabled; public String getId() {
    return id;
    } public void setId(String id) {
    this.id = id;
    } public Integer getAge() {
    return age;
    } public void setAge(Integer age) {
    this.age = age;
    } public boolean isEnabled() {
    return isEnabled;
    } public void setEnabled(boolean enabled) {
    isEnabled = enabled;
    }
    }

    getter 是 isEnabled,setter 是 setEnabled,并未遵循 JavaBean 规范

    此时 JSON 序列化,得到的字符串是

    看仔细了,key 值是 enabled,而非 isEnabled;那么 JSON 串

    {
    "id": "44",
    "age": 16,
    "isEnabled": true
    }

    反序列化得到的 JavaBeanEntity 实例,其 isEnabled 值是什么?

  2. Lombok

    getter 是 isEnabled,setter 是 setEnabled,并未遵循 JavaBean 规范

    IDEA 实现一致

再看看包装数据类型 Boolean

  1. IDEA

    /**
    * @author 青石路
    */
    public class JavaBeanEntity {
    private String id;
    private Integer age;
    private Boolean isEnabled; public String getId() {
    return id;
    } public void setId(String id) {
    this.id = id;
    } public Integer getAge() {
    return age;
    } public void setAge(Integer age) {
    this.age = age;
    } public Boolean getEnabled() {
    return isEnabled;
    } public void setEnabled(Boolean enabled) {
    isEnabled = enabled;
    }
    }

    getter 是 getEnabled,setter 是 setEnabled

    JSON序列化与反序列化的结果是什么?我相信你们能立马答出来

  2. Lombok

    getter 是 getIsEnabled,setter 是 setIsEnabled

    此时 JSON 序列化的结果

    {
    "id": "1",
    "age": 18,
    "isEnabled": true
    }

    key 值与 JavaBeanEntity 的属性名完全对应上了;那么 JSON 串

    {
    "id": "44",
    "age": 16,
    "isEnabled": true
    }

    反序列化得到的 JavaBeanEntity 实例,其 isEnabled 值是什么?

要不要加 is 前缀

关于布尔类型的属性,并且是 Boolean 类型的属性,能不能加 is 前缀,答案肯定是能的,但是不推荐,我们来看看 deepseek 是怎么说的

在你们心中,Boolean 类型的属性名,is 前缀是不是有了替代方案?

总结

  1. JavaBeans 规范,只对 boolean 属性进行了规定,并未对 Boolean 属性进行规定,不同的实现者对 Boolean 属性的 getter 的实现可能各不相同,大家不要觉得不可理解

  2. 老老实实遵循 Java开发手册,可以规避很多前人踩过的坑

    与本文相关的就是

    1. POJO 类中的任何布尔类型的变量,都不要加 is 前缀
    2. 所有的 POJO 类属性必须使用包装数据类型,RPC 方法的返回值和参数必须使用包装数据类型,所有的局部变量推荐使用基本数据类型
  3. 都说了布尔类型的变量不要加 is 前缀,非要加,这不是坑我了嘛是水文,鄙人实难苟同

关于布尔类型的变量不要加 is 前缀,被网友们吐槽了,特来完善下的更多相关文章

  1. POJO类中的任何布尔类型的变量,都不要加is

    POJO类中的任何布尔类型的变量,都不要加is,否则部分框架解析会引起序列化错误. 定义为基本数据类型boolean isSuccess:的属性,它的方法也是isSuccess(),HSF框架在反向解 ...

  2. C++中对一个布尔类型的变量按位取反结果不变

    C++中对一个bool类型的变量按位取反是无效的.例如: bool a = true; bool b = ~a; // b的值还是true

  3. 016 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 10 布尔类型和字符串的字面值

    016 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 10 布尔类型和字符串的字面值 本文知识点:字面值 关于字面值的概念,需要注意:很多地方,我们可能就把字面值 ...

  4. Python 学习 第一篇:数据类型(数字,集合,布尔类型,操作符)

    Python语言最常用的对象是变量和常量,常量的值是字面意思,其值是不可变的,变量的值是可变的,例如,123,"上海"是常量,而a=1,a=2,其中a是变量名.内置的核心数据类型有 ...

  5. POJO类中布尔类型为啥不让用isXxx命名

    源码面前,了无秘密 <阿里开发规范泰山版>(2020.04.22)-->编程规约-->(一) 命名风格-->第8条规定: [强制]POJO 类中的任何布尔类型的变量,都不 ...

  6. BOOL布尔类型

    1.BOOL数据类型,是一种表示非真即假的数据类型,布尔类型的变量只有YES和NO两个值.YES表⽰示表达式结果为真,NO表示表达式结果为假. 2.在C语言中,认为非0即为真. 3.分⽀支语句中,经常 ...

  7. Python:Base1(数据类型,print语句,变量,定义字符串,raw字符串与多行字符串,Unicode字符串,整数和浮点数运算,布尔类型运算)

    1.Python中数据类型: 计算机顾名思义就是可以做数学计算的机器,因此,计算机程序理所当然地可以处理各种数值.但是,计算机能处理的远不止数值,还可以处理文本.图形.音频.视频.网页等各种各样的数据 ...

  8. ECMAScript1.1 js书写位置 | 声明变量 | 基本数据类型 | 数据类型转换 | 操作符 | 布尔类型的隐式转换

    js书写位置 由于在写css样式时使用的时双引号,所以我们在写js代码时建议使用单引号(‘’)! 行内式 <input type="button" value="点 ...

  9. C# 类型和变量

    C# 中的类型有两种:值类型 (value type) 和引用类型 (reference type).值类型的变量直接包含它们的数据,而引用类型的变量存储对它们的数据的引用,后者称为对象.对于引用类型 ...

  10. python基础——数字&集合&布尔类型

    Python的核心数据类型 内置对象 对象类型 例子 数字 123,3.1415,3+4j,Decimal(小数),Fraction(分数) 字符串 'dodo',"guido's" ...

随机推荐

  1. odoo16里面的常用方法

    一.全局搜索与显示 def name_get(self): res = [] for order in self: name = order.name if order.draw_number: na ...

  2. 如何排查内存飙高-Linux top命令快速入门

      Linux系统出现了性能问题,一般我们可以通过 top.iostat.free.vmstat和ifstat等命令来初步定位问题.其中,top命令是Linux下常用的性能分析工具,用于实时监测系统资 ...

  3. 终极指南:Scrum中如何设置需求优先级

    需求众多不知道如何下手?总想先做简单的需求,复杂需求却一拖再拖?那么,我们是时候开始考虑如何设置需求优先级了. 本期终极指南将展示如何为需求设置有效优先级,如何有效管理工作量,让效率指数倍增长,搭配  ...

  4. k8s集群根据进程PID获取Pod名称

    简单说明 在实际的应用场景中,我们如果看到某个进程资源或服务异常,需要根据这个进程排查到底是哪个服务的Pod,这里我们介绍一种根据PID快速寻找Pod名称的方法. 实际操作 查看进程PID 这里我们以 ...

  5. 2-Tensorboard使用

    1. Tensorboard用途 ① Tensorboad 可以用来查看loss是否按照我们预想的变化,或者查看训练到某一步输出的图像是什么样. pip install tensorboard Req ...

  6. C# 读取类Class注释

    https://www.cnblogs.com/shanfeng1000/p/14972515.html 友好的注释能提高代码的可读性,几乎所有的编程语言都支持注释. 在C#中,注释不是可执行代码的一 ...

  7. 学习spring cloud记录2-在项目中使用mybatis-plus

    前言 本记录详细记录本人学习spring cloud继承mybatis plus方法,适用于初学者. 项目结构简单介绍 本系统目前新建两个dmeo服务,分别是demo-user和demo-order两 ...

  8. MCP 核心架构解析

    引言 Model Context Protocol (MCP) 是一种为连接大型语言模型(LLM)应用而设计的通信协议,它建立在灵活.可扩展的架构基础上,旨在实现LLM应用程序与各类集成之间的无缝交互 ...

  9. 前端开发系列129-进阶篇之Throttle And Debounce

    本文讨论前端开发中 函数防抖 和 函数节流,它们的应用.区别以及简单实现. 在前端开发中我们可能经常需要给(页面)标签绑定一些持续触发的事件,如 resize.scroll.input.mousemo ...

  10. 使用ipad阅读代码

    简介 使用ipad阅读代码 https://www.zhihu.com/collection/64510384 https://www.cnblogs.com/benjamin-t/p/3618787 ...