【小家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的程序


相关文章
|
20天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
57 7
|
2月前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
99 43
Java学习十六—掌握注解:让编程更简单
|
25天前
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
65 5
|
1月前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
62 14
|
1月前
|
前端开发 Java
[Java]讲解@CallerSensitive注解
本文介绍了 `@CallerSensitive` 注解及其作用,通过 `Reflection.getCallerClass()` 方法返回调用方的 Class 对象。文章还详细解释了如何通过配置 VM Options 使自定义类被启动类加载器加载,以识别该注解。涉及的 VM Options 包括 `-Xbootclasspath`、`-Xbootclasspath/a` 和 `-Xbootclasspath/p`。最后,推荐了几篇关于 ClassLoader 的详细文章,供读者进一步学习。
37 12
|
1月前
|
Java 编译器
Java进阶之标准注解
Java进阶之标准注解
34 0
|
2月前
|
IDE Java 编译器
java的反射与注解
java的反射与注解
23 0
|
IDE Java API
Lombok:让JAVA代码更优雅
Lombok:让JAVA代码更优雅
112 0
Lombok:让JAVA代码更优雅
|
Java 开发工具 IDE
|
Java 开发工具 IDE
lombok 简化java代码注解
lombok 简化java代码注解 安装lombok插件 以intellij ide为例 File-->Setting-->Plugins-->搜索“lombok plugin”,安装后重启ide lombok 注解 lombok 提供的注解不多,可以参考官方视频的讲解和官方文档。
865 0