IntelliJ IDEA 插件 Lombok 使用说明

1. 官方网站

官网地址
API 文档
Lombok是一款Java开发插件,使得Java开发者可以通过其定义的一些注解来消除业务工程中冗长和繁琐的代码,尤其对于简单的Java模型对象(POJO)。在开发环境中使用Lombok插件后,Java开发人员可以节省出重复构建,诸如hashCodeequals这样的方法以及各种业务对象模型的accessorToString等方法的大量时间。对于这些方法,它能够在编译源代码期间自动帮我们生成这些方法,并不会像反射那样降低程序的性能。

2. 准备工作

2.1. 安装 Lombok 插件

我使用的开发工具是IntelliJ IDEA 2019.2版本(公司电脑上安装的版本),直接在File--->Settings--->Plugins里面搜索lombok,会看到下图所示的插件列表,Marketplace显示的是市场上的相关插件列表,Installed显示的是本地已安装的插件列表。

IntelliJ IDEA 插件 Lombok 使用说明_第1张图片

点击Install按钮安装,安装完毕该按钮会变为Installed,上图为已安装完的状态。
接下来会在Installed列表中看到已安装的Lombok插件。(安装完插件需重启 DIEA 才会生效)

IntelliJ IDEA 插件 Lombok 使用说明_第2张图片

2.2. 设置实时编译

IDEA默认状态为不自动编译,Eclipse默认为自动编译,很多朋友都是从Eclipse转到Intellij IDEA 的,这常常导致我们在需要操作class文件时忘记对java类文件进行编译从而对旧文件进行了操作。为了能实时查看编译完的class文件,我们可以将IDEA设置为实时编译,在File--->Settings--->Build,Execution,Deployment--->Compiler中,对Build project automatically选项打钩即可,注意自动编译只有在服务没有运行的时候才可用。另外,会在IDEA的下方任务栏出现Problems选项卡,该选项卡会在代码编译的时候给出进度提示和编译结果提示。

IntelliJ IDEA 插件 Lombok 使用说明_第3张图片
IntelliJ IDEA 插件 Lombok 使用说明_第4张图片

IDEA默认集成了反编译插件,通过设置实时编译,我们可以很方便的查看我们class文件的反编译结果。使用的时候只需要找到你的class文件(maven项目直接在target下找对应目录的class文件),直接双击打开即可。

2.3. 文件重新编译

也可以不用设置实时编译,直接对修改的Java文件进行手动编译,在类上鼠标右键,选择Recompile'xxxx.java'进行重新编译。或者直接使用快捷键Ctrl+Shift+F9进行重新编译。

IntelliJ IDEA 插件 Lombok 使用说明_第5张图片

重新编译完毕,找到对应的class文件,可能是没有更新的,这时可以直接在对应class文件上或者对应包上点击鼠标右键,选择Synchronize 'xxxxx'进行同步即可更新为最新的class文件。

IntelliJ IDEA 插件 Lombok 使用说明_第6张图片

2.4. 引入相关依赖

对于Maven项目,直接在pom.xml中引入Lombok的依赖。当前最新的是1.18.8版本。


    org.projectlombok
    lombok
    1.18.8

3. 注解使用

3.1. @Getter@Setter

@Getter@Setter注解为字段生成gettersetter方法。
1、注解在类或字段上,注解在类时为所有字段生成gettersetter方法,注解在字段上时只为该字段生成gettersetter方法。
2、不会对final字段生成setter方法,会生成getter方法。
3、对于boolean类型的属性,生成的getter方法遵循布尔属性的约定,例如对于属性boolean foo生成的getter方法为isFoo,而不是getFoo
4、如果使用注解的字段所属的类包含与要生成的gettersetter名称相同的方法,无论参数或返回类型是否一样,都不会生成相应的方法。
5、这两个注解通过使用可选参数AccessLevel可以指定生成的方法的访问级别。一共有六种级别:
PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE, NONE;
示例代码

public class UserInfo {
    @Getter@Setter
    private final String nid = "nid";
    @Getter(value = AccessLevel.PROTECTED)
    private String userId;
    @Setter(value = AccessLevel.PACKAGE)
    private String userName;
    @Getter@Setter(value = AccessLevel.NONE)
    private boolean sex;
}

等效代码

public class UserInfo {
    private final String nid = "nid";
    private String userId;
    private String userName;
    private boolean sex;

    public UserInfo() {}
    //final属性只生成了getter方法
    public String getNid() {
        this.getClass();
        return "nid";
    }
    //生成了protected级别的getter方法
    protected String getUserId() {
        return this.userId;
    }
    
    void setUserName(String userName) {
        this.userName = userName;
    }
    //没有生成setter方法,生成的getter方法的名字是is开头的
    public boolean isSex() {
        return this.sex;
    }
}

3.2. @NonNull

@NonNull注解用于字段上,表示对该字段进行null检查。
1、当放置在使用@Setter注解的字段上时,将在生成的setter方法中进行null检查。
2、如果使用Lombok为所属类生成构造函数,那么使用了该注解的字段将添加到生成的构造函数的参数中,并且在生成的构造函数中对该参数进行null检查。
示例代码

@RequiredArgsConstructor
public class UserInfo {
    @Getter@Setter@NonNull
    private String userId;
    private String userName;
}

等效代码

import lombok.NonNull;

public class UserInfo {
    @NonNull
    private String userId;
    private String userName;

    public UserInfo(@NonNull String userId) {
        if (userId == null) {//构造方法中生成的null检查
            throw new NullPointerException("userId is marked non-null but is null");
        } else {
            this.userId = userId;
        }
    }

    @NonNull
    public String getUserId() {
        return this.userId;
    }

    public void setUserId(@NonNull String userId) {
        if (userId == null) {//setter方法中生成的null检查
            throw new NullPointerException("userId is marked non-null but is null");
        } else {
            this.userId = userId;
        }
    }
}

3.3. @ToString

类上使用该注解会自动生成toString方法。默认情况下,任何非静态字段都将以名称-值对的形式包含在toString方法的输出中。该注解有几个可选属性,可相应控制toString的输出内容。
示例代码

@ToString(exclude = {"age","sex"})
public class UserInfo {
    private String userId;
    private String userName;
    private Integer age;
    private Boolean sex;
}

等效代码

public class UserInfo {
    private String userId;
    private String userName;
    private Integer age;
    private Boolean sex;

    public UserInfo() {
    }
    //生成的toString方法
    public String toString() {
        return "UserInfo(userId=" + this.userId + ", userName=" + this.userName + ")";
    }
}

可选属性

    //该属性设置为 false 表示输出没有属性名和等号,只有属性值,多个属性值用逗号隔开
    boolean includeFieldNames() default true;
    //该属性表示输出中不包指定字段。可指定多个字段
    String[] exclude() default {};
    //该属性表示输出中只包含指定字段。可指定多个字段
    String[] of() default {};
    //该属性设置为 true,表示输出中会包含父类的 toString 方法输出
    boolean callSuper() default false;
    //该属性设置为 true,表示输出的字段值不通过 getter 方法获取,而是直接访问字段
    boolean doNotUseGetters() default false;
    //该属性设置为 true,不输出任何字段信息,只输出了构造方法的名字
    boolean onlyExplicitlyIncluded() default false;

3.4. @EqualsAndHashCode

该注解用在类上会同时生成equalshashCode方法,因为这两个方法本质上是由hashCode契约绑定在一起的。默认情况下,这两种方法都将考虑类中不是静态或瞬态的任何字段。
(1)通过exclude参数可以指定需要排除的属性。还以通过of参数来指定所希望使用的字段。
(2)callSuper参数。将其设置为true将导致equals方法首先会调用父类的equals来验证等式。对于hashCode方法,它将会把父类的hashCode的结果合并到子类的hashCode计算中。
示例代码

@EqualsAndHashCode
public class UserInfo {
    private String userId;
    private String userName;
}

等效代码

public class UserInfo {
    private String userId;
    private String userName;

    public UserInfo() {
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof UserInfo)) {
            return false;
        } else {
            UserInfo other = (UserInfo)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
                Object this$userId = this.userId;
                Object other$userId = other.userId;
                if (this$userId == null) {
                    if (other$userId != null) {
                        return false;
                    }
                } else if (!this$userId.equals(other$userId)) {
                    return false;
                }

                Object this$userName = this.userName;
                Object other$userName = other.userName;
                if (this$userName == null) {
                    if (other$userName != null) {
                        return false;
                    }
                } else if (!this$userName.equals(other$userName)) {
                    return false;
                }

                return true;
            }
        }
    }

    protected boolean canEqual(Object other) {
        return other instanceof UserInfo;
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $userId = this.userId;
        int result = result * 59 + ($userId == null ? 43 : $userId.hashCode());
        Object $userName = this.userName;
        result = result * 59 + ($userName == null ? 43 : $userName.hashCode());
        return result;
    }
}

这里引用一段话,对equalshashCode方法的解释:

hashCode()方法和equals()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致。重写的equals()里一般比较的全面且复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高。但是,hashCode()并不是完全可靠,有时候不同的对象它们生成的 hashcode也会一样(生成hash值的公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,所以我们可以得出:
(1)equal()相等的两个对象它们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。
(2)hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。
因此,我们可以得出以下结论:
所有对于需要大量并且快速的对比的情况如果都用equals()去做显然效率太低,所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就不必再用equals()去对比了),如果hashCode()相同,这时再对比它们的equals(),如果equals()也相同,则表示这两个对象是真的相同了,这样既能大大提高效率也保证了对比的绝对正确性!

3.5. @Data

@Data注解在类上,会为类的所有属性自动生成setter/getter、equals、canEqual、hashCode、toString方法,还有一个构造方法,如为final属性,则不会为该属性生成setter方法。
(1)该注解是使用Lombok的项目中使用最频繁的注解了。它结合了@ToString@EqualsAndHashCode@Getter@Setter 的功能。实际上,在类上使用@Data等同于使用了默认的@ToString@EqualsAndHashCode注解,以及使用@Getter@Setter注释每个字段。也会触发Lombok的构造函数生成。将添加一个公共构造函数,会将@NonNullfinal字段作为参数。
(2)虽然@Data非常有用,但它不能提供与其他Lombok注解相同的控制粒度。为了覆盖默认的方法生成行为,请使用其他Lombok注解对类、字段或方法进行注释,并指定必要的参数值,以达到预期的效果。
(3)提供了一个参数选项staticConstructor,可以用来生成静态工厂方法。将参数的值设置为所需的方法名,会将构造函数设置为私有的,并生成一个公开的的静态工厂方法,名称就是指定的方法名。并且会将final字段和@NonNull字段作为构造方法和生成的静态工厂方法的参数。
示例代码

@Data(staticConstructor = "getInstance")
public class UserInfo {
    private final Integer id;
    @NonNull
    private String userId;
    private String userName;
}

等效代码

public class UserInfo {
    private final Integer id;
    @NonNull
    private String userId;
    private String userName;
    //生成的构造方法,将@NonNull和fina字段作为了参数
    private UserInfo(Integer id, @NonNull String userId) {
        if (userId == null) {
            throw new NullPointerException("userId is marked non-null but is null");
        } else {
            this.id = id;
            this.userId = userId;
        }
    }
    //生成了一个公开的静态的工厂方法,方法名为注解的staticConstructor参数指定的方法名
    public static UserInfo getInstance(Integer id, @NonNull String userId) {
        return new UserInfo(id, userId);
    }
    //生成的setter、getter方法,final字段不会生成setter方法
    public Integer getId() {
        return this.id;
    }

    @NonNull
    public String getUserId() {
        return this.userId;
    }

    public String getUserName() {
        return this.userName;
    }

    public void setUserId(@NonNull String userId) {
        if (userId == null) {
            throw new NullPointerException("userId is marked non-null but is null");
        } else {
            this.userId = userId;
        }
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
    //生成的equals方法
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof UserInfo)) {
            return false;
        } else {
            UserInfo other = (UserInfo)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
                label47: {
                    Object this$id = this.getId();
                    Object other$id = other.getId();
                    if (this$id == null) {
                        if (other$id == null) {
                            break label47;
                        }
                    } else if (this$id.equals(other$id)) {
                        break label47;
                    }

                    return false;
                }

                Object this$userId = this.getUserId();
                Object other$userId = other.getUserId();
                if (this$userId == null) {
                    if (other$userId != null) {
                        return false;
                    }
                } else if (!this$userId.equals(other$userId)) {
                    return false;
                }

                Object this$userName = this.getUserName();
                Object other$userName = other.getUserName();
                if (this$userName == null) {
                    if (other$userName != null) {
                        return false;
                    }
                } else if (!this$userName.equals(other$userName)) {
                    return false;
                }

                return true;
            }
        }
    }

    protected boolean canEqual(Object other) {
        return other instanceof UserInfo;
    }
    //生成的hashCode方法
    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $id = this.getId();
        int result = result * 59 + ($id == null ? 43 : $id.hashCode());
        Object $userId = this.getUserId();
        result = result * 59 + ($userId == null ? 43 : $userId.hashCode());
        Object $userName = this.getUserName();
        result = result * 59 + ($userName == null ? 43 : $userName.hashCode());
        return result;
    }
    //生成的toString方法
    public String toString() {
        return "UserInfo(id=" + this.getId() + ", userId=" + this.getUserId() + ", userName=" + this.getUserName() + ")";
    }
}

3.6. @Cleanup

@Cleanup注释可用于确保已分配的资源被释放。当用@Cleanup注释局部变量时,任何后续代码都封装在try/finally块中,该块保证在当前范围的末尾处调用清理方法。默认情况下@Cleanup生成的清理方法名为 close,与输入和输出流一样。可以通过参数提供一个不同的方法名。注意方法名必须是该变量可以使用的方法。不然会报错。在使用@Cleanup注释时还需要注意一点。如果清理方法抛出异常,它将抢占方法主体中抛出的任何异常(覆盖了实际异常)。这可能导致问题的实际原因被掩盖,在选择使用Lombok的资源管理时应该考虑这种情况。此外,随着Java 7中出现了自动资源管理,以后会很少需要使用该注解。
示例代码

    public void testCleanUp() {
        try {
            @Cleanup ByteArrayOutputStream baos = new ByteArrayOutputStream();
            baos.write(new byte[] {'Y','e','s'});
            System.out.println(baos.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

等效代码

    public void testCleanUp() {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            try {
                baos.write(new byte[]{89, 101, 115});
                System.out.println(baos.toString());
            } finally {//进行了资源关闭
                if (Collections.singletonList(baos).get(0) != null) {
                    baos.close();
                }

            }
        } catch (IOException var6) {
            var6.printStackTrace();
        }
    }

3.7. @Synchronized

使用@Synchronized注释实例方法将使该方法变为一个同步方法,生成一个名为$lock的私有锁定字段,该方法将在执行之前会锁定该字段。类似地,以同样的方式注释静态方法将生成一个私有静态对象$LOCK,以便静态方法以相同的方式使用。可以通过向注释的值参数提供字段名来指定不同的锁定对象。当提供字段名时,开发人员必须确保Lombok不会生成该字段。
示例代码

    private DateFormat format = new SimpleDateFormat("yyyy-MM-dd");

    @Synchronized
    public String synchronizedFormat(Date date) {
        return format.format(date);
    }

    private static DateFormat format1 = new SimpleDateFormat("yyyy-MM-dd");

    @Synchronized
    public static String synchronizedFormat1(Date date) {
        return format1.format(date);
    }

等效代码

    private DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    
    private final Object $lock = new Object[0];

    public String synchronizedFormat(Date date) {
        synchronized(this.$lock) {
            return this.format.format(date);
        }
    }

    private static DateFormat format1 = new SimpleDateFormat("yyyy-MM-dd");

    private static final Object $LOCK = new Object[0];

    public static String synchronizedFormat1(Date date) {
        synchronized($LOCK) {
            return format1.format(date);
        }
    }

3.8. @SneakyThrows

如果一个类里面抛出一个Exception,但是类上没进行抛出;或者父类抛出了一个异常,但是子类没有进行处理,这种情况都会产生编译期错误,会提示有一个“未处理的异常”错误。当在类上使用@SneakyThrows注释时,错误将消失。默认情况下,@SneakyThrows将允许抛出任何检查过的异常,而不需要在throw子句中声明。通过向注释的值参数提供一个可抛出类(Class)数组,可以将此限制为一组特定的异常。
示例代码

    @SneakyThrows
    public void testSneakyThrows() {
        throw new IllegalAccessException();
    }

等效代码

    public void testSneakyThrows() {
        try {
            throw new IllegalAccessException();
        } catch (Throwable var2) {
            throw var2;
        }
    }

3.9. @NoArgsConstructor

注解在类上,生成无参数的构造方法。
(1)使用@NoArgsConstructor会生成没有参数的构造函数,但如果存在final修饰的成员字段,会编译出错,除非使用@NoArgsConstructor(force=true),那么所有的final字段会被定义为0falsenull等。
(2)使用无参数的构造函数构造出来的实例成员变量是null,如果存在@NonNull修饰的成员字段,那么就矛盾了。所以如果有@NonNull修饰的成员的变量就不要用@NoArgsConstructor修饰类。
示例代码

@NoArgsConstructor(force = true)
public class UserInfo {
    private final Integer Id;
    @NonNull
    private String userId;
    private String userName;
}

等效代码

public class UserInfo {
    //final字段被强制初始化为null了
    private final Integer Id = null;
    @NonNull
    private String userId;
    private String userName;
    //生成的无参构造方法
    public UserInfo() {
    }
}

3.10. @RequiredArgsConstructor

注解在类上,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
示例代码

@RequiredArgsConstructor
public class UserInfo {
    private final Integer Id;
    @NonNull
    private String userId;
    private String userName;
}

等效代码

public class UserInfo {
    private final Integer Id;
    @NonNull
    private String userId;
    private String userName;
    //final字段和@NonNull字段作为了构造函数的参数。
    public UserInfo(final Integer Id, @NonNull final String userId) {
        if (userId == null) {
            throw new NullPointerException("userId is marked non-null but is null");
        } else {
            this.Id = Id;
            this.userId = userId;
        }
    }
}

3.11. @AllArgsConstructor

注解在类上,生成包含类中所有字段的构造方法。
这三个都是处理构造函数的注解,都只能修饰类,都能通过staticName创建静态工厂方法,使用access控制访问级别。不同之处在于@AllArgsConstructor会把所有的成员变量都纳入到构造函数中, @RequiredArgsConstructor只会把final@NonNull修饰的成员变量纳入,@NoArgsConstructor所有的成员变量都不会纳入到构造函数。
示例代码

@AllArgsConstructor(staticName = "getInstance")
public class UserInfo {
    private final Integer Id;
    @NonNull
    private String userId;
    private String userName;
}

等效代码

public class UserInfo {
    private final Integer Id;
    @NonNull
    private String userId;
    private String userName;

    private UserInfo(final Integer Id, @NonNull final String userId, final String userName) {
        if (userId == null) {
            throw new NullPointerException("userId is marked non-null but is null");
        } else {
            this.Id = Id;
            this.userId = userId;
            this.userName = userName;
        }
    }

    public static UserInfo getInstance(final Integer Id, @NonNull final String userId, final String userName) {
        return new UserInfo(Id, userId, userName);
    }
}

3.12. @Slf4j

注解在类上,生成log变量。
@Log,@Log4j,@Log4j2,@Slf4j,@XSlf4j,@CommonsLog,@JBossLog,@Flogger
这几个注解都是不同框架的日志注解。
@Log4j是使用Log4j框架时用的。
@Slf4j是使用Logback框架时用的。
示例代码

@Slf4j
public class UserInfo {
    private String userId;
    private String userName;
}

等效代码

public class UserInfo {
    //生成的日志变量
    private static final Logger log = LoggerFactory.getLogger(UserInfo.class);
    private String userId;
    private String userName;

    public UserInfo() {
    }
}

3.13. @Builder

面对复杂的数据结构,使用builder模式可以抽离复杂的构造方式,能保证线程安全,比如:

UserInfo userInfo = UserInfo.builder().userId("22").userName("张三").build();

示例代码

@Builder
public class UserInfo {
    private String userId;
    private String userName;
}

等效代码

public class UserInfo {
    private String userId;
    private String userName;

    UserInfo(final String userId, final String userName) {
        this.userId = userId;
        this.userName = userName;
    }
    //生成的静态工厂方法
    public static UserInfo.UserInfoBuilder builder() {
        return new UserInfo.UserInfoBuilder();
    }
    //生成的静态内部类
    public static class UserInfoBuilder {
        private String userId;
        private String userName;

        UserInfoBuilder() {
        }

        public UserInfo.UserInfoBuilder userId(final String userId) {
            this.userId = userId;
            return this;
        }

        public UserInfo.UserInfoBuilder userName(final String userName) {
            this.userName = userName;
            return this;
        }

        public UserInfo build() {
            return new UserInfo(this.userId, this.userName);
        }

        public String toString() {
            return "UserInfo.UserInfoBuilder(userId=" + this.userId + ", userName=" + this.userName + ")";
        }
    }
}

3.14. val 类型

使用val可以作为局部变量的声明类型,类型将从初始化表达式中推断出来。例如: val x = 10.0; 将推断为doubleval y = new ArrayList(); 将推断为ArrayList 。局部变量会被设置为 final

4. 实现原理

Lombok这款插件是依靠可插件化的Java自定义注解处理API(JSR 269: Pluggable Annotation Processing API)来实现在javac编译阶段利用Annotation Processor(注解处理) 对自定义的注解进行预处理后生成真正在JVM上面执行的Class文件。大致执行原理图如下:

IntelliJ IDEA 插件 Lombok 使用说明_第7张图片
执行原理

从上面的这个原理图上可以看出Annotation Processing是编译器在解析Java源代码和生成Class文件之间的一个步骤。其中Lombok插件具体的执行流程如下:

IntelliJ IDEA 插件 Lombok 使用说明_第8张图片
执行流程

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

5. 写在最后

(1)优点

  • 能通过注解的形式自动生成构造器、getter/setterequalshashcodetoString等方法,提高了一定的开发效率。
  • 让代码变得简洁,不用过多的去关注相应的方法。
  • 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等。

(2)缺点

  • 不支持多种参数构造器的重载。
  • 降低了源代码的可读性和完整性,降低了阅读源代码的舒适度。

(3)总结
Lombok虽然有很多优点,但Lombok更类似于一种IDE插件,项目也需要依赖相应的jar包。Lombok依赖jar包是因为编译时要用它的注解,为什么说它又类似插件?因为在使用时,eclipseIntelliJ IDEA都需要安装相应的插件,在编译器编译时通过操作AST(抽象语法树)改变字节码生成,变向的就是说它在改变 java语法。它不像spring的依赖注入或者mybatisORM一样是运行时的特性,而是编译时的特性。

这里引用下一些人对Lombok的看法:

这是一种低级趣味的插件,不建议使用。Java发展到今天,各种插件层出不穷,如何甄别各种插件的优劣?能从架构上优化你的设计的,能提高应用程序性能的,实现高度封装可扩展的..., 像Lombok这种,已经不仅仅是插件了,它改变了你如何编写源码,事实上,少去的代码你写上去又如何? 如果Java家族到处充斥着这样的东西,那只不过是一坨披着金属颜色的屎,迟早会被其它语言取代。

如果一个项目有非常多类似Lombok这样的插件,真的会极大的降低阅读源代码的舒适度。Lombok有它得天独厚的优点,也有它避之不及的缺点,在实战中需要灵活运用。其实我觉得在项目中适度使用还是可以的,用几个最简单基本的注解,能大大简化一些模版型代码的开发。比如我常用的是@Data@Slf4j@Setter@Getter@EqualsAndHashCode

你可能感兴趣的:(IntelliJ IDEA 插件 Lombok 使用说明)