Lombok ——超酷的Java库

Lombok

lombok 是什么

Lombok 是一个超酷的 Java 库,旨在通过减少样板代码来提升开发效率。

主要作用
  • 减少样板代码
  • 提高代码可读性
  • 节省开发时间
  • 减少代码出错的机会
常用注解
  • @Getter / @Setter
  • @ToString
  • @EqualsAndHashCode
  • @NoArgsConstructor / @AllArgsConstructor / @RequiredArgsConstructor
  • @Data
  • @Builder
  • @Value
  • @Slf4j
  • @SneakyThrows
注意
  • 局限性:Lombok基于注解处理器和字节码操作,某些IDE、框架、Java版本升级时可能会导致Lombok无法正常工作,需要额外的配置或更新
  • Lombok注解生成的代码不可见,过度什么事可能会导致新成员理解代码逻辑困难
  • 代码在调试时不可见,可能会影响对代码执行流程的理解
使用方法

Maven项目 - pom.xml中添加依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version> <!-- 注意查看最新的 Lombok 版本,并替换此处的版本号 -->
    <scope>provided</scope>
</dependency>

**TIP: ** scope设置为provided,表示仅编译时使用Lombok,而在项目打包时,并不会将Lombok依赖打包进去,可减少最终项目Jar 包的体积大小。

使用Gradle - build.gradle文件添加

dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.20' // 编译时依赖
    annotationProcessor 'org.projectlombok:lombok:1.18.20' // 注解处理
}

部分详解

  • @Getter / @Setter : 自动为字段/类生成getter/setter方法

    参数AccessLevelPUBLIC (默认)、PROTECTEDPACKAGEPRIVATEMODULE

    import lombok.Getter;
    import lombok.Setter;
    import lombok.AccessLevel;
    
    public class User {
        @Getter(AccessLevel.PUBLIC)
        @Setter(AccessLevel.PROTECTED)
        private String name;
    
        @Getter(AccessLevel.PRIVATE)
        private int age;
    }
    
    
  • @ToString :自动生成toString( )方法

    参数exclude:用于排除不希望包含在toString( )方法的字段

    import lombok.ToString;
    
    @ToString(exclude = "password")
    public class User {
        private String name;
        private int age;
        private String password;
    }
    

    参数callSuper:是否调用父类的toString( )方法

    import lombok.ToString;
    
    @ToString(callSuper = true)
    public class Employee extends User {
        private String position;
    }
    

    参数onlyExplicitlyIncluded:用于控制仅生成包含@ToString.Include注解的字段(适用于想要手动选择部分字段,而不是默认全部生成的情况)

  • @EqualsAndHashCode:用于自动生成equals( )和hashCode( )

    参数exclude:用于指定不包含在equals( ) 和 hashCode( )方法中的字段

    import lombok.EqualsAndHashCode;
    
    @EqualsAndHashCode(exclude = "age")
    public class User {
        private String name;
        private int age;
    }
    

    参数callSuper:是否调用父类的equals( ) 和 hashCode( )方法 ——true时,先调用父类,再处理自己的字段

    参数onlyExplicitlyIncluded:用于控制 equals()hashCode() 方法仅包含那些被 @EqualsAndHashCode.Include 标记的字段(可以通过该方式精确选择哪些字段用于比较)

  • @Constructor系列注解

    • NoArgsConstructor:用于生成一个无参构造方法。

      参数force:如果类中包含final字段,@NoArgsConstructor默认无法生成构造方法,除非添加force=true参数,使final字段自动初始化为默认值。

    • @AllArgsConstructor:用于生成包含所有字段的构造方法。

    • @RequiredArgsConstructor:用于生成包含“必需”字段的构造方法。(即那些final或带有@NonNull注解的字段)

    TIP:@NoArgsConstructor和@AllArgsConstructor可以同时使用

  • @Data集合注解

    相当于同时使用如下注解:

    • @Getter / @Setter
    • @ToString
    • @EqualsAndHashCode
    • @RequiredArgsConstructor

    参数staticConstructor

    @Data(staticConstuctor = "create")
    public class User {
        private Long id; 
        private String username;
        //...
    }
    

    编译之后,实际生成的代码:

    public class User {
        private Long id; 
        private String username;
        
        private User() {
        }
        
        public static User create() {
            return new User();
        }
        
        public Long getId() {
            return this.id;
        }
        //...
    }
    

    即@Data(staticConstuctor = "create") 生成了一个名为create()静态实体类生成方法,同时私有化了无参构造器。

    注意:1.如果某个字段不想生成某些方法,使用排除参数即可;

    ​ 2.仅添加@Data注解,默认只会生成一个无参的构造器;

    ​ 同时添加@Data和@Builder注解时,仅会生成一个全参构造器。

  • @Builder:用于生成建造者模式的代码

    基本用法:

    使用@Builder注解,Lombok会自动生成一个静态的内部Builder类,包含所有字段的”链式“setter方法,并生成一个build( )方法用于创建对象实例。

    import lombok.Builder
        
    @Builder
    public class User {
        private String name; 
        private int age;
        private String email;
    }
    

    上述代码会自动生成一个User类的Builder,允许使用链式方法来设置字段,并通过build( )方法完成对象的创建,比如像下面这样:

    User user = User.builder( )
        			.name("Alice")
        			.age(25)
        			.email("alice@example.com")
        			.build( );
    

    参数builderMethodName:可以自定义生成的静态Builder方法名称。默认情况下,此方法名为builder( )

    @Builder(builderMethodName = "newUser")
    public class User {
        private String name; 
        private int age;
    }
    

    现在可以使用User.newUser( )来获取一个Builder实例:

    User user = User.newUser( ).name("Bob").age(30).build();
    

    参数buildMethodName:用于自定义生成的构建方法的名称,默认为build()

    @Builder(buildMethodName = "create")
    public class User {
        private String name;
        private int age;
    }
    

    生成的Builder实例可以调用create()方法来构建User对象:

    User user = User.builder().name("Charlie").age(40).create();
    

    参数toBuilder:允许在已有对象的基础上创建一个新的builder。它会为生成的类添加toBuilder()方法,返回一个带有当前对象状态的Builder,便于在已有对象的基础上做出小幅修改。

    import lombok.Builder;
    
    @Builder(toBuilder = true)
    public class User {
        private String name;
        private int age;
    }
    

    可以使用toBuilder()方法生成一个新的Builder,并修改部分字段:

    User originalUser = User.builder().name("Davaid").age(20).build;
    User updatedUser = originalUser.toBuilder.age(25).build();
    

    注意:@Builder默认不会使用字段的初始值。如果希望Builder生成的对象包含默认值,可以使用@Builder.Default注解指定。

  • @Value:用于简化不可变对象的创建

    不可变对象在创建后,其内部状态无法改变,具备线程安全的我,适合并发环境中共享

    相当于如下组合:

    • @Getter
    • @ToString
    • @EqualsAndHashCode
    • @AllArgsConstuctor
    • final (所有字段都是自动变为final,只能在构造时赋值

    因此非常适合用来定义数据传输对象(DTO)或不可变的核心数据结构。

    基本用法:

    ​ 在类上添加@Value注解后,Lombok会自动将所有字段设为final并生成只读的getter,禁止字段的修改方法。

    默认特性:

    1. 不可变性:所有字段自动设为final,对象创建后其状态不可更改;
    2. 私有构造方法:除非显式定义,@Value会默认生成一个私有的无参构造器。一般情况下不会用到,因为@Value的对象要求全部字段初始化。
    3. 序列化支持:@Value类默认实现 Serializable接口,非常适合在分页式环境中传输数据。

    @Value与@Data的区别

    @Value @Data
    getter()
    setter() ×
    toString()
    equals()
    hashCode()
    适合不希望被修改的数据对象 适合需要修改的普通Java Bean

    @Value与可变对象的组合

    如果字段是集合类型(如List、Map等),则无法保证集合内容的不可变性。为实现完全不可变性,可以使用Collections.unmodifiableList()等方法将集合转换为不可变的视图。

    import lombok.Value;
    import java.util.List;
    import java.util.Collections;
    
    @Value
    public class User {
        String name;
        List<String> hobbies;
    
        public User(String name, List<String> hobbies) {
            this.name = name;
            //保证hobbies字段的内容在对象创建后也不可修改
            this.hobbies = Collections.unmodifiableList(hobbies);
        }
    }
    

    在多线程和分布式环境中,通过@Value创建不可变对象,能提升数据安全性和一致性。

  • @Log:用于自动生成日志记录器实例,简化Java类中日志的定义和使用。

    Lombok支持多个日志框架,每个框架对应一个专门的注解:

    • @Log:使用java.util.logging
    • @CommonsLog:使用Apache Commons Logginng
    • @Log4j
    • @Slf4j
    • @XSlf4j:使用 SLF4J 的扩展(XSLF4J)
    • @JBossLog:使用 JBoss Logging

    每个注解都会自动在类中生成一个静态的日志记录器实例,变量名称根据框架的不同,通常是 log

    每个日志注解生成的 Logger 变量都具备以下特性:

    • 静态和只读:日志记录器实例是 static final 类型,确保整个类只会生成一个日志记录器实例
    • 命名为 log:生成的变量名称统一为 log,便于开发者识别和调用
    • 支持日志级别:根据日志框架的不同,log 变量支持相应的日志级别方法(如 infowarnerror 等)
  • @SneakyThrows:用来“偷偷地”抛出检查型异常(checked exception),而无需显式地在方法签名中声明throws或者使用try-catch。对于需要捕获或声明的异常,@SneakyThrows注解可以让方法绕过这一步。

    import java.io.UnsupportedEncodingException;
    
    public class SneakyThrowsExample {
    
        /**
         * 字节转字符串,且指定了字符编码为 UTF-8
         * @param bytes
         * @return
         */
        public static String utf8ToString(byte[] bytes) {
            try {
                return new String(bytes, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public static void main(String[] args) {
            System.out.println(utf8ToString("犬小哈教程".getBytes()));
        }
    }
    

    new String(bytes, "UTF-8")需要显示的进行try-catch异常捕获,或者如下所示,通过throws将异常抛出去,让调用者去处理:

        /**
         * 字节转字符串,且指定了字符编码为 UTF-8
         * @param bytes
         * @return
         */
        public static String utf8ToString(byte[] bytes) throws UnsupportedEncodingException {
            return new String(bytes, "UTF-8");
        }
    

    若使用 Lombok 的 @SneakyThrows 注解:

    import lombok.SneakyThrows;
    
    public class SneakyThrowsExample {
    
        /**
         * 字节转字符串,且指定了字符编码为 UTF-8
         * @param bytes
         * @return
         */
        @SneakyThrows
        public static String utf8ToString(byte[] bytes) {
            return new String(bytes, "UTF-8");
        }
    
        public static void main(String[] args) {
            System.out.println(utf8ToString("犬小哈教程".getBytes()));
        }
    }
    

    在上面的代码中,方法中没有主动 try-catch 异常,也没有在方法签名中声明 throws ,而是在方法上添加了@SneakyThrows 注解,此注解会在编译期自动将抛出异常的工作做好,从而让开发者在代码中避免显式的 try-catch 块或 throws 声明。

    但是也容易导致代码不够直观,需要谨慎使用。


Lombok实现原理

Lombok的核心是利用Java注解处理器和AST(抽象语法树)修改技术,在编译阶段动态插入代码。

注解处理器

Java的javax.annotation.processing包提供了注解处理器的支持。Lombok使用该机制,通过实现AnnotationProcessor接口来处理注解。

Lombok编译时注解处理的流程如下:

  1. 注解扫描:Java编译器在编译过程中扫描所有类上的注解,如果找到Lombok的注解(如@Getter、@Setter等),会将这些注解交给Lombok处理。
  2. 注解处理:Lombok自定义的注解处理器会在处理阶段解析这些注解,识别哪些注解需要生成代码。
  3. 修改AST:Lombok的注解处理器会读取编译器生成的AST 树,针对特定注解修改或新增相应的代码节点,例如,针对@Getter注解,Lombok会在类的AST中插入get方法。
  4. 字节码生成:经过修改的AST树会继续传递给Java编译器,编译器会根据更新后的AST生成新的字节码文件。
AST操作

AST是源代码结构的树状表达形式。Lombok使用Java编译器中不同阶段的AST解析结果来完成代码的插入操作。具体来说 ,Lombok通过对Java编译器的文气,直接操作AST来实现以下功能:

  • 方法添加:Lombok通过注解生成getter、setter等方法,将方法节点插入到AST树中;
  • 构造器生成:针对@AllArgsConstructor或@NoArgsConstructor等注解,Lombok会自动在AST树中创建构造器节点;
  • 字段操作:对于不可变对象的@Value注解,Lombok会将字段设置为final
编译器适配

Lombok需要对不同的Java编译器进行适配,会提供适配层,分别处理Eclipse和IDEA编译环境下手代码生成。

编译期间字节码插桩

Lombok通过编译器插件机制和字节码插桩实现代码的生成。它不需要依赖类加载器,也不会在运行时修改字节码,因此没有额外的性能开销。

Lombok修改的是编译期间的字节码,因此生成的.class文件中已包含所有注解生成的代码,不需要在运行引入Lombok依赖。

优缺点

优点:

  • 减少样板代码
  • 提高开发效率
  • 编译期间优化:Lombok的代码在编译期完成,不会影响运行时性能。

缺点:

  • 编译器适配:Lombok依赖编译器的深度集成和AST操作,不同的IDE和编译环境可能需要特殊配置和适配。
  • 代码可见性差
  • 依赖限制:Lombok是第三方库,尽管已经很成熟,但在某些特定环境下歌熊伟 与其他编译器插件或框架发生冲突。

常见陷阱

  1. 慎用@Data

    当需要自定义一些方法时,比如自定义equals和hashCode方法,但是由于@Data注解自动生成了这些方法,所以你的自定义方法会被覆盖。

    可以用callSuper、AccessLevel的属性来定义。

  2. 注意@ToString的循环引用问题

    在处理含有循环引用的数据结构时,要注意。例如,双向链表或者树形结构,其中的元素互相引用。

  3. @EqualsAndHashCode与懒加载

    当你使用 JPA 或 Hibernate 这样的 ORM 框架时,你可能会使用到懒加载(Lazy Loading)特性。这意味着,某些字段在第一次被访问时才会被加载。而如果你在这样的字段上使用了 @EqualsAndHashCode 注解,可能会在你并不希望加载这些字段的情况下触发它们的加载。

    为了避免这种情况,你可以使用 @EqualsAndHashCode(onlyExplicitlyIncluded = true),然后在你想要包含在 equalshashCode 方法中的字段上使用 @EqualsAndHashCode.Include

    @EqualsAndHashCode(onlyExplicitlyIncluded = true)
    public class MyEntity {
        @EqualsAndHashCode.Include
        private Long id;
    
        @OneToMany
        private List<MyOtherEntity> otherEntities;
    
        //...
    }
    
  4. 小心@SneakyThrows

    @SneakyThrows 是一个非常有用的注解,它可以让你 "偷偷" 抛出受检异常,而无需在方法签名中声明它们。但是这也可能会引起一些问题。

    首先,你的代码的调用者可能并不知道你的方法可能会抛出哪些受检异常,这可能会导致他们在处理异常时疏忽。为了解决这个问题,你应该在你的方法的文档注释中明确说明可能会抛出哪些异常。

    其次,@SneakyThrows 会将受检异常包装在 RuntimeException 中抛出,如果你的代码的调用者捕获了 RuntimeException,并且没有检查它的原因,那么他们可能会错过受检异常。为了解决这个问题,你应该尽量避免在你的方法中直接抛出 RuntimeException,而是应该抛出具体的异常类型。

  5. 不要滥用Lombok

(声明:以上学习途径来源于犬小哈教程

posted on 2025-07-10 11:39  学习code想长头发U  阅读(82)  评论(0)    收藏  举报