前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 对象字段基本类型和包装类型的选择以及 null 处理的一些思考

Java 对象字段基本类型和包装类型的选择以及 null 处理的一些思考

原创
作者头像
Lorin 洛林
修改2024-02-04 17:02:57
2280
修改2024-02-04 17:02:57
举报
文章被收录于专栏:Java 技术小屋Java 技术小屋

版本

  • JDK 8

起因

  • 最近合并代码中发现了一个有意思的报错:Lombok builder is missing non nullable fields,大意是说 Lombok 构建器缺少对非空字段的处理,下面是对情况复现 demo:
代码语言:java
复制
public class TestMain {

    public static void main(String[] args) {
        // Lombok builder is missing non nullable fields
        Student student = Student.builder().name("xiaoming").build();
        System.out.println(student.getAge());
    }
}


@Builder
@Data
class Student {
    @NonNull
    private String name;
    private int age;
}
  • 先说结论:其实这里是 IDE 在提示我们 age 字段不为 null,你应该对字段进行处理初始化和赋值。

解决方案

方案一

  • 理论上不用解决,因为这个提示只是 IDE 层面的提示而编译器并不会实际进行检查,程序是可以正常运行的。

方案二

  • 设置 ageInteger,表示允许字段为 null
代码语言:java
复制
private Integer age;

方案三

  • Builder.Default 设置默认值
代码语言:java
复制
@Builder.Default
private int age = 0;

聊聊几种方案的优劣

  • 上文提到, IDE 实际上是在提示我们 age 字段不为 null,你应该对字段进行处理初始化和赋值。换句话说,你要么允许字段为 null,要么你应该就行初始化或赋值而不是使用基本类型的默认值。

方案一禁止使用

  • 方案一相当于忽略这个提醒,那么你的字段在没有赋值的情况下则会使用基本类型的默认值,代码可维护性和可读性都是极差的。

方案二建议使用

  • 方案二是直接使用包装类型的方式,允许字段为 null,当然我们也可以结合 @NonNull 注解来保证字段不为 null
代码语言:java
复制
    @NonNull
    private Integer age;

        // 编译后的 class 文件
        public StudentBuilder name(@NonNull String name) {
            if (name == null) {
                throw new NullPointerException("name is marked non-null but is null");
            } else {
                this.name = name;
                return this;
            }
        }
  • 除此之外,当字段需要默认值时我们还可以结合 @Builder.Default 使用。
代码语言:java
复制
    @Builder.Default
    @NonNull
    private Integer age=3;
    
        // 编译后的 class 文件
        public StudentBuilder age(@NonNull Integer age) {
            if (age == null) {
                throw new NullPointerException("age is marked non-null but is null");
            } else {
                this.age$value = age;
                this.age$set = true;
                return this;
            }
        }

        public Student build() {
            Integer age$value = this.age$value;
            if (!this.age$set) {
                age$value = Student.$default$age();
            }

            return new Student(this.name, age$value);
        }

方案三依据实际情况选择

  • 方案三如果字段不允许为空且有较高性能要求,我们可以考虑选择此方案。
  • 此方案还有一个优势就是避免了 NullPointerException 的处理。
代码语言:java
复制
    @Builder.Default
    private int age=3;
    
        // 编译后的 class 文件 
        public StudentBuilder age(int age) {
            this.age$value = age;
            this.age$set = true;
            return this;
        }

        public Student build() {
            int age$value = this.age$value;
            if (!this.age$set) {
                age$value = Student.$default$age();
            }

            return new Student(this.name, age$value);
        }

方案二 NullPointerException 的处理

  • 当字段不为 null 时使用 @NonNull@Builder.Default 避免此问题。
  • 当字段可为 null 时则需要处理 NullPointerException 问题,下面是一个建议方案:使用 Optional 进行包装,除了可以使用函数式编程的思想使代码更简洁以外,还可以让使用者明确知道该值可能为空,你需要强制进行处理,具备更好的可读性。
代码语言:java
复制
@Builder
@Getter
class Student {
    @NonNull
    private String name;
    private Optional<Integer> age;
}

public class TestMain {

    public static void main(String[] args) {
        Student.StudentBuilder builder = Student.builder().name("Ming");
        builder.age(Optional.of(22));
        Student student = builder.build();
        System.out.println(student.getAge().orElseThrow(() -> new IllegalStateException("Age is not present")));
    }
}

总结

  • 一般情况我们可以直接使用方案二(包装类型) + Optional 的组合,但在一些有性能要求的场景我们可以适当考虑方案三,减少频繁的拆箱和装箱带来的性能损耗以及减少内存占用。
  • 需要注意的是 Optional 不是 Serializable,作为类字段时如果需要序列化则需要进行特殊处理,比如使用 Jackson,它会把空对象看作 null,而有值的对象则把其值看作对应域的值。

个人简介

? 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

? 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

? 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

? 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

? 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

? 保持关注我的博客,让我们共同追求技术卓越。

我正在参与2024腾讯技术创作特训营第五期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 版本
  • 起因
  • 解决方案
    • 方案一
      • 方案二
        • 方案三
        • 聊聊几种方案的优劣
          • 方案一禁止使用
            • 方案二建议使用
              • 方案三依据实际情况选择
              • 方案二 NullPointerException 的处理
              • 总结
              • 个人简介
              相关产品与服务
              云数据库 MySQL
              腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
              http://www.vxiaotou.com