为什么POJO中变量不能用is开头

本文通过实例演示和源码解析,说明了为何POJO类中的布尔类型变量不应以is开头,以防序列化时出现错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

在阿里编码规约中,有一个约定如下

【强制】POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列 化错误。

但为什么类中的field不能用is开头呢?本文将从问题演示、框架源码(本文使用的Jackson)两个方面来进行阐述。

二、问题演示

本文使用的Jackson,需要引入相关依赖,maven依赖如下,gradle配置请自行寻找。

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.0</version>
</dependency>

首先写一个简单的POJO,在这个POJO中,有三个属性,分别是isSuccess/code/msg,并自动生成了对应的get方法,这里要注意,isSuccess属性对应的get方法名也是isSuccess()。代码如下

public class Pojo {

    private boolean isSuccess;

    private int code;

    private String msg;

    private Pojo(boolean success, int code, String msg) {
        this.isSuccess = success;
        this.code = code;
        this.msg = msg;
    }

    public static Pojo buildSuccess() {
        return new Pojo(true, 2000, "success");
    }

    public boolean isSuccess() {
        return isSuccess;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    @Override
    public String toString() {
        return "Pojo{" +
                "isSuccess=" + isSuccess +
                ", code=" + code +
                ", msg='" + msg + '\'' +
                '}';
    }
}

然后再写一个类,在这个类中测试对POJO的序列化。在主函数中,会先创建一个POJO,然后将其打印,接着会使用Jackson中的ObjectMapper将对象序列化成Json字符串并打印。

public class Main {

    public static void main(String[] args) {
        ObjectMapper mapper = new ObjectMapper();

        Pojo pojo = Pojo.buildSuccess();
        System.out.println("raw pojo1: " + pojo.toString());

        try {
            System.out.println("serialize pojo1: " + mapper.writeValueAsString(pojo));
        } catch (IOException e) {
            System.out.println(e);
        }
    }
}

执行main()方法后,得到运行结果如下图:

 可以发现,使用ObjectMapper对象将POJO序列化成String后,输出的变量名是success,而原有POJO中的变量isSuccess丢失。将该Json串反序列化成对象,会出现问题。

三、源码解析

笔者水平有限,源码看的浅薄,只讲解一下核心的流程。由于Jackson扩展性很好,对各种复杂情况都进行了处理,这里只讲解最普通的情况,比如,POJO无父类,fields都是private修饰,已经生成对应的getter方法等。

主要方法是POJOPropertiesCollector#collectAll(),该方法源码如下:

    protected void collectAll()
    {
        LinkedHashMap<String, POJOPropertyBuilder> props = new LinkedHashMap<String, POJOPropertyBuilder>();

        // First: gather basic data
        _addFields(props); // note: populates _fieldRenameMappings
        _addMethods(props);
        // 25-Jan-2016, tatu: Avoid introspecting (constructor-)creators for non-static
        //    inner classes, see [databind#1502]
        if (!_classDef.isNonStaticInnerClass()) {
            _addCreators(props);
        }

        // Remove ignored properties, first; this MUST precede annotation merging
        // since logic relies on knowing exactly which accessor has which annotation
        _removeUnwantedProperties(props);
        // and then remove unneeded accessors (wrt read-only, read-write)
        _removeUnwantedAccessor(props);

        // Rename remaining properties
        _renameProperties(props);

        // and now add injectables, but taking care to avoid overlapping ones
        // via creator and regular properties
        _addInjectables(props);

        // then merge annotations, to simplify further processing
        // 26-Sep-2017, tatu: Before 2.9.2 was done earlier but that prevented some of
        //   annotations from getting properly merged
        for (POJOPropertyBuilder property : props.values()) {
            property.mergeAnnotations(_forSerialization);
        }

        // And use custom naming strategy, if applicable...
        PropertyNamingStrategy naming = _findNamingStrategy();
        if (naming != null) {
            _renameUsing(props, naming);
        }

        // Sort by visibility (explicit over implicit); drop all but first of member
        // type (getter, setter etc) if there is visibility difference
        for (POJOPropertyBuilder property : props.values()) {
            property.trimByVisibility();
        }

        // and, if required, apply wrapper name: note, MUST be done after
        // annotations are merged.
        if (_config.isEnabled(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME)) {
            _renameWithWrappers(props);
        }

        // well, almost last: there's still ordering...
        _sortProperties(props);
        _properties = props;
        _collected = true;
    }

collectAll()方法中,会调用自身的_addFields(Map<String, POJOPropertyBuilder> props)_addMethods(Map<String, POJOPropertyBuilder> props),分别获取对象的fields和methods。

_addFields()方法最终会调用AnnotatedFieldCollector#_findFields(TypeResolutionContext tc, JavaType type, Map<String, FieldBuilder> fields),方法源码如下:

    private Map<String,FieldBuilder> _findFields(TypeResolutionContext tc,
            JavaType type, Map<String,FieldBuilder> fields)
    {
        // First, a quick test: we only care for regular classes (not interfaces,
        //primitive types etc), except for Object.class. A simple check to rule out
        // other cases is to see if there is a super class or not.
        JavaType parent = type.getSuperClass();
        if (parent == null) {
            return fields;
        }
        final Class<?> cls = type.getRawClass();
        // Let's add super-class' fields first, then ours.
        fields = _findFields(new TypeResolutionContext.Basic(_typeFactory, parent.getBindings()),
                parent, fields);
        for (Field f : cls.getDeclaredFields()) {
            // static fields not included (transients are at this point, filtered out later)
            if (!_isIncludableField(f)) {
                continue;
            }
            // Ok now: we can (and need) not filter out ignorable fields at this point; partly
            // because mix-ins haven't been added, and partly because logic can be done
            // when determining get/settability of the field.
            if (fields == null) {
                fields = new LinkedHashMap<>();
            }
            FieldBuilder b = new FieldBuilder(tc, f);
            if (_collectAnnotations) {
                b.annotations = collectAnnotations(b.annotations, f.getDeclaredAnnotations());
            }
            fields.put(f.getName(), b);
        }
        // And then... any mix-in overrides?
        if (_mixInResolver != null) {
            Class<?> mixin = _mixInResolver.findMixInClassFor(cls);
            if (mixin != null) {
                _addFieldMixIns(mixin, cls, fields);
            }
        }
        return fields;
    }

_findFields()方法中,getRawClass()获取Class信息,getDeclaredFields()反射获取所有的字段,并去除属于类的变量(即static修饰变量),最后按照方法堆栈顺序一步步将fields信息返回。_addFields()内获取到fields后进行遍历,进行一系列校验如是否有@JsonValue @JsonAnySetter,是否有隐式名、是否需要重命名、是否可见等,最后将校验后的field添加到POJOPropertyBuilder#_fields属性中

四、结论

通过上述论证,我们不难发现,类中的属性用is开头会导致对象序列化后产生意想不到的结果。所以在平时编码的过程中,我们不能在类变量中用is开头如: "isSuccess",这样容易造成程序错误。

如果情况特殊,也不必墨守成规,只是需要预先判断可能产生的影响、影响范围、能否监控等问题。更特殊的情况,你可以将变量名定义为isIsSuccess,或者将isSuccess定义为public。

阿里巴巴Java开发规范手册为Java开发者提供了详尽的命名指导,以确保代码的一致性和可维护性。在遵循这些规范的前提下,正确的命名实践包括但不限于以下几个方面: 参考资源链接:[阿里巴巴Java编程规范详解](https://wenku.youkuaiyun.com/doc/646dbdf9543f844488d81454?spm=1055.2569.3001.10343) 首先,关于命名,应遵循UpperCamelCase风格,即每个单词的首字母大写,且不要使用下划线或美元符号。例如,名应该像`UserAccount`或`OrderService`这样的格式。 其次,对于方法名、参数名、成员变量和局部变量的命名,建议使用lowerCamelCase风格,即首字母小写,每个后续单词首字母大写。例如,方法名可以是`getUserInfo()`,参数名可以是`userId`,局部变量可以是`userRole`。 再次,对于常量的命名,应该全部使用大写字母,并用下划线来分隔不同的单词,以增强可读性。例如,常量名可以是`MAXIMUM_CONNECTIONS`。 在命名抽象时,应该以`Abstract`或`Base`作为前缀,而异常则应该以`Exception`结尾。例如,抽象可以命名为`AbstractUserManager`,异常可以命名为`ValidationException`。 关于数组的命名,应该将中括号紧跟在类型后,而不是变量名后。例如,数组应该命名如`String[] bookTitles`,而不是`String bookTitles[]`。 最后,对于POJO中的布尔变量,不应该以`is`开头,以免在某些框架中引发序列化问题。例如,布尔变量可以命名为`active`而不是`isActive`。 而在命名实践中应当避免的做法包括但不限于: - 避免使用下划线或美元符号开头和结尾的命名方式,如`_name`或`$Object`。 - 不应使用混合拼音和英文的方式命名,如`getPingfenByName`。 - 禁止使用中文命名,如直接使用`用户`代替`User`。 - 不要使用特定于某个数据库表或临时变量的命名,如`user1`、`user2`等。 - 避免使用带有偏见的性别化语言或不明确的缩写命名,如`Man`或`Boy`等。 通过遵循这些命名规范,开发者可以编写出更为规范和清晰的Java代码,增强代码的可读性和团队协作的效率。若需要更全面地理解和应用这些规范,推荐详细阅读《阿里巴巴Java编程规范详解》这一资料,它将为你的编码实践提供深度指导。 参考资源链接:[阿里巴巴Java编程规范详解](https://wenku.youkuaiyun.com/doc/646dbdf9543f844488d81454?spm=1055.2569.3001.10343)
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值