【小家Java】Lombok的使用详解(最详尽的解释,覆盖讲解所有可用注解),解决@Builder.Default默认值问题(下)

简介: 【小家Java】Lombok的使用详解(最详尽的解释,覆盖讲解所有可用注解),解决@Builder.Default默认值问题(下)

@Delegate 注释的属性,会把这个属性对象的公有非静态方法合到当前类


代理模式,把字段的方法代理给类,默认代理所有方法。注意:公共 非静态方法


public class Demo extends Parent {
    private final int finalVal = 10;
    @Delegate
    private String xxxName;
    private int age;
}


编译后:把String类的公共 非静态方法全拿来了 个人觉得很鸡肋有木有


public class Demo extends Parent {
    private final int finalVal = 10;
    private String xxxName;
    private int age;
    public Demo() {
    }
    public int length() {
        return this.xxxName.length();
    }
    public boolean isEmpty() {
        return this.xxxName.isEmpty();
    }
    public char charAt(int index) {
        return this.xxxName.charAt(index);
    }
    public int codePointAt(int index) {
        return this.xxxName.codePointAt(index);
    }
.
.
.


备注:它不能用于基本数据类型字段比如int,只能用在包装类型比如Integer

参数们:


1.types:指定代理的方法


2.excludes:和types相反


@NonFinal 设置不为Final,@FieldDefaults和@Value也有这功能

@SuperBuilder 本以为它是支持到了父类属性的builder构建,但其实,我们还是等等吧 目前还不好使

@UtilityClass 工具类 会把所有字段方法static掉,没啥用

@Wither 生成withXXX方法,返回类实例 没啥用,因为还有bug


@Builder和@NoArgsConstructor一起使用冲突问题


当我们这么使用时候:


image.png

编译报错:


Error:(17, 1) java: 无法将类 com.sayabc.groupclass.dtos.appoint.TeaPoolLogicalDelDto中的构造器 TeaPoolLogicalDelDto应用到给定类型;
  需要: 没有参数
  找到: java.lang.Long,java.lang.Long,java.lang.Long,java.lang.Integer
  原因: 实际参数列表和形式参数列表长度不同


其实原因很简单,自己点进去看编译后的源码一看便知。

只使用@Builder会自动创建全参构造器。而添加上@NoArgsConstructor后就不会自动产生全参构造器

两种解决方式:


1.去掉@NoArgsConstructor


2.添加@AllArgsConstructor(建议使用这种,毕竟无参构造最好保证是有的)


but,枚举值建议这样来就行了,不要加@NoArgsConstructor


我认为这也是Lombok的一个bug,希望在后续版本中能够修复


image.png


@builder注解影响设置默认值的问题


例子如下,本来我是想给age字段直接赋一个默认值的:

没有使用lombok,我们这么写:


  public static void main(String[] args) {
        Demo demo = new Demo();
        System.out.println(demo); //Demo{id=null, age=10}
    }
    private static class Demo {
        private Integer id;
        private Integer age = 10; //放置默认值年龄
        //省略手动书写的get、set、方法和toString方法
        @Override
        public String toString() {
            return "Demo{" +
                    "id=" + id +
                    ", age=" + age +
                    '}';
        }
    }


们发现,这样运行没有问题,默认值也生效了。但是,但是我们用了强大的lombok,我们怎么可能还愿意手写get/set呢?关键是,我们一般情况下还会用到它的@buider注解:


 public static void main(String[] args) {
        Demo demo = new Demo();
        System.out.println(demo); //Demo{id=null, age=10}
        //采用builder构建  这是我们使用最多的场景吧
        Demo demo2 = Demo.builder().build();
        System.out.println(demo2); //PeriodAddReq.Demo(id=null, age=null)
    }
    @Getter
    @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    private static class Demo {
        private Integer id;
        private Integer age = 10; //放置默认值年龄
    }


代码简洁了不少。但是我们却发现一个问题。new出来的对象默认值仍然没有问题,但是buider构建出来的demo2对象,默认值却没有设置进去。这是一个非常隐晦的问题,一不小心,就可能留下一个惊天大坑,所以需要注意


其实在执行编译的时候,idea开发工具已经警告我们了:


image.png


Warning:(51, 25) java: @Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final.


方案一:


从它的建议可以看出,把字段标为final就ok了(亲测好用)。但很显然,绝大多数我们并不希望他是final的字段。


因此我们采用第二个方案:


@Getter
    @Setter
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    private static class Demo {
        private Integer id;
        @Builder.Default
        private Integer age = 10; //放置默认值年龄
    }


lombok考虑到了这种现象,因此我们只需要在需要设置默认值的字段上面加上 @Builder.Default注解就ok了


  public static void main(String[] args) {
        Demo demo = new Demo();
        System.out.println(demo); //PeriodAddReq.Demo(id=null, age=null)
        //采用builder构建  这是我们使用最多的场景吧
        Demo demo2 = Demo.builder().build();
        System.out.println(demo2); //PeriodAddReq.Demo(id=null, age=10)
    }


但是我们坑爹的发现:builder默认值没问题了,但是new出来又有问题了。见鬼啊,


我认为这是lombok的一个大bug,希望后续版本中能够修复


但是我们不能因为有这么一个问题,咱们就不使用它了。本文主要提醒读者,在使用的时候留心这个问题即可。


备注:@Builder.Default会使得使用@NoArgsConstructor生成的无参构造没有默认值,自己显示写出来的也不会给你设置默认值的,需要注意。

2019年1.18日补充内容:Lombok 1.18.4版本


上面已经指出了Lombok设置默认值的bug,果不其然。官方在1.18.4这个版本修复了这个bug。各位要有版本意识:这个版本级以上版本是好用的,比这版本低的都不行。


image.png


用这个版本运行上面例子,默认值没有问题了。


Main.Demo(id=null, age=10)
Main.Demo(id=null, age=10)


我们不用自动生成空构造,显示书写出来呢?如下:


    @Getter
    @Setter
    @Builder
    @AllArgsConstructor
    @ToString
    private static class Demo {
        private Integer id;
        @Builder.Default
        private Integer age = 10; //放置默认值年龄Default
    //显示书写出空构造
        public Demo() {
        }
    }


image.png


我们发现手动书写出来的空构造,默认值是不生效的。这点需要特别注意。


这个就不说是Lombok的bug了,因为既然你都使用Lombok了,为何还自己写空构造呢?不是作死吗?


Lombok背后的自定义注解原理


作为一个Java开发者来说光了解插件或者技术框架的用法只是做到了“知其然而不知其所以然”,如果真正掌握其背后的技术原理,看明白源码设计理念才能真正做到“知其然知其所以然”。好了,话不多说下面进入本章节的正题,看下Lombok背后注解的深入原理。


可能熟悉Java自定义注解的同学已经猜到,Lombok这款插件正是依靠可插件化的Java自定义注解处理API(JSR 269: Pluggable Annotation Processing API)来实现在Javac编译阶段利用“Annotation Processor”对自定义的注解进行预处理后生成真正在JVM上面执行的“Class文件”。有兴趣的同学反编译带有Lombok注解的类文件也就一目了然了。其大致执行原理图如下:


image.png

从上面的Lombok执行的流程图中可以看出,在Javac 解析成AST抽象语法树之后, Lombok 根据自己编写的注解处理器,动态地修改 AST,增加新的节点(即Lombok自定义注解所需要生成的代码),最终通过分析生成JVM可执行的字节码Class文件。使用Annotation Processing自定义注解是在编译阶段进行修改,而JDK的反射技术是在运行时动态修改,两者相比,反射虽然更加灵活一些但是带来的性能损耗更加大。


需要更加深入理解Lombok插件的细节,自己查阅其源代码是必比可少的。


AnnotationProcessor这个类是Lombok自定义注解处理的入口。该类有两个比较重要的方法一个是init方法,另外一个是process方法。在init方法中,先用来做参数的初始化,将AnnotationProcessor类中定义的内部类(JavacDescriptor、EcjDescriptor)先注册到ProcessorDescriptor类型定义的列表中。其中,内部静态类—JavacDescriptor在其加载的时候就将 lombok.javac.apt.LombokProcessor这个类进行对象实例化并注册。在 LombokProcessor处理器中,其中的process方法会根据优先级来分别运行相应的handler处理类。Lombok中的多个自定义注解都分别有对应的handler处理类.


在Lombok中对于其自定义注解进行实际的替换、修改和处理的正是这些handler类。对于其实现的细节可以具体参考其中的代码。

Java6以后,java编译器已经有了开源的版本了。Java6提供了JSR269的标准实现,提供插入式注解处理API(Pluggable Annotation Processing API)一套标准API来处理Annotations。只要代码实现了此API,就能在javac运行的时候得到调用。Lombok就是一个实现了JSR269的程序


相关文章
|
28天前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
86 43
Java学习十六—掌握注解:让编程更简单
|
23天前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
44 14
|
23天前
|
前端开发 Java
[Java]讲解@CallerSensitive注解
本文介绍了 `@CallerSensitive` 注解及其作用,通过 `Reflection.getCallerClass()` 方法返回调用方的 Class 对象。文章还详细解释了如何通过配置 VM Options 使自定义类被启动类加载器加载,以识别该注解。涉及的 VM Options 包括 `-Xbootclasspath`、`-Xbootclasspath/a` 和 `-Xbootclasspath/p`。最后,推荐了几篇关于 ClassLoader 的详细文章,供读者进一步学习。
29 12
|
16天前
|
Java 编译器
Java进阶之标准注解
Java进阶之标准注解
28 0
|
1月前
|
JSON Java 数据库
java 常用注解大全、注解笔记
关于Java常用注解的大全和笔记,涵盖了实体类、JSON处理、HTTP请求映射等多个方面的注解使用。
35 0
java 常用注解大全、注解笔记
|
1月前
|
IDE Java 编译器
java的反射与注解
java的反射与注解
16 0
|
1月前
|
XML Java 数据格式
Java-spring注解的作用
Java-spring注解的作用
23 0
|
11天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
7天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
28 9
|
10天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####