\*\*注意:\*\* 开始学习Lombok前至少需要保证完成JavaSE课程中的\*\*注解\*\*部分。 #### 以往操作一 代码量大 不便于维护 \`\`\`java public class Account { private int id; private String name; private int age; private String gender; private String password; private String description; ... } \`\`\` 缺点:依次编写类中所有字段的Getter和Setter还有构造方法简直是场灾难,后期字段名字变了甚至还得一个一个修改! #### 以往操作二 Java17之后 可以使用【记录类型】来快速得到一个自带构造方法、Getter以及重写ToString等方法的类: \`\`\`java public record Account(int id, String name, int age, String gender, String password, String description) { } \`\`\` 缺点:不够灵活,无Setter也并未生成,所以说它依然没有大规模使用。(有构造 有get 有toString() 没有set()) #### 推荐Lombok 那么有没有一种更加完美的方案来处理这种问题呢?通过使用Lombok(小辣椒)就可以做到,它就是专门用于简化 Java 编程中的样板代码的,它通过注解的方式,能够自动生成常见的代码,比如构造函数、getter 和 setter 方法、toString 方法、equals 和 hashCode 方法等,从而使开发者能够专注于业务逻辑,而不必重复编写冗长的代码。官网地址:https://projectlombok.org 使用Lombok后,你的代码就会变成这样: \`\`\`java @Getter @Setter @AllArgsConstructor public class Student { private Integer sid; private String name; private String sex; } \`\`\` 使用Lombok提供的注解,即可一步到位,直接生成对应的Getter和Setter方法,包括构造方法、toString等都可以全包。 ## 安装Lombok 1.可以在这里进行下载Lombok jar包:https://projectlombok.org/download 2.通过Maven,直接使用Maven导入: \`\`\`xml org.projectlombok lombok 1.18.34 provided \`\`\` 然后我们要安装一下Lombok插件,由于IDEA终极版默认都安装了Lombok的插件,因此直接导入依赖后就可以使用了。 现在我们在需要测试的实体类上添加\`@Data\`注解试试看: \`\`\`java import lombok.Data; @Data public class Account { private int id; private String name; private int age; private String gender; private String password; private String description; } \`\`\` 接着测试一下是否可以直接使用,\`@Data\`会为我们的类自动生成Getter和Setter方法,我们可以直接调用: \`\`\`java public static void main(String\[\] args) { Account account = new Account(); account.setId(10); } \`\`\` 如果运行后出现要求启用Lombok注解处理,请务必开启,否则会出现错误 主要由于Lombok并非在运行时通过反射生成代码 而是在编译期通过Java的Annotation Processing(注解处理)机制。 需要如下设置 启用注解处理 \`\`\`java File-Settings-Build-Compiler-Annotation Processing 勾选enable Annotation Processing \`\`\` ## 使用Lombok 我们接着来为大家介绍Lombok提供的主要注解。 ### 类属性相关 ##### \`@Getter\` 它用于自动生成Getter方法,定义如下: \`\`\`java @Target({ElementType.FIELD, ElementType.TYPE}) //此注解可以添加在字段或是类型上 @Retention(RetentionPolicy.SOURCE) public @interface Getter { AccessLevel value() default AccessLevel.PUBLIC; //自动生成的Getter的访问权限级别 AnyAnnotation\[\] onMethod() default {}; //用于添加额外的注解 boolean lazy() default false; //懒加载功能 ... } \`\`\` 它最简单的用法,就是直接添加到类上或是字段上: \`\`\`java @Getter //添加到类上时,将为类中所有字段添加Getter方法 public class Account { private int id; @Getter() //当添加到字段上时,仅对此字段生效 private String name; private int age; } \`\`\` 假设我们这里将\`@Getter\`编写在类上,那么生成得到的代码为: \`\`\`java import lombok.Generated; public class Account { private int id; private String name; private int age; public Account() {} @Generated public int getId() { //自动为所有字段生成了对应的Getter方法 return this.id; } ...省略 } \`\`\` 方便灵活。注意默认命名规则,如果该字段名为\`foo\`,则将其直接按照字段名称命名为\`getFoo\`。但是注意,如果字段的类型为\`boolean\`,则会命名\`isFoo\`,这是比较特殊的地方。 常规属性: 访问权限,默认情况下为public,但是有时候可能我们只希望生成一个private的get方法,此时我们可以对其进行修改: \* PUBLIC - 对应public关键字 \* PACKAGE - 相当于不添加任何访问权限关键字 \* PRIVATE - 对应private关键字 \* PROTECTED - 对应protected关键字 \* MODULE - 仅限模块内使用,与PACKAGE类似,相当于不添加任何访问权限关键字 \* NONE - 表示不生成对应的方法,这很适合对类中不需要生成的字段进行排除 这里我们尝试将其更改为: \`\`\`java @Getter(AccessLevel.PRIVATE) //为所有字段生成private的Getter方法 public class Account { private int id; @Getter(AccessLevel.NONE) //不为name生成Getter方法,字段上的注解优先级更高 private String name; private int age; } \`\`\` 得到的结果就是: \`\`\`java public class Account { private int id; private String name; private int age; public Account() { } private int getId() { //得到的就是private的Getter方法 return this.id; } ... } \`\`\` \`onMethod\`属性,这个属性用于添加一些额外的注解到生成的方法上,比如我们要为Getter方法添加一个额外的\`@Deprecated\`表示它不推荐使用,那么: \`\`\`java @Getter public class Account { private int id; @Getter(onMethod_ = { @Deprecated }) private String name; private int age; } \`\`\` 此时得到的代码为: \`\`\`java public class Account { ... /\*\* @deprecated \*/ @Deprecated //由Lombok额外添加的注解 public String getName() { return this.name; } } \`\`\` \`lazy\`属性,这是用于控制【懒加载】 \> 懒加载就是在一开始的时候此字段没有值,当我们需要的时候再将值添加到此处。 只不过它有一些要求,我们的字段必须是private且final的: \`\`\`java public class Account { @Getter(lazy = true) private final String name = "小蜗牛"; } \`\`\` 生成的代码如下: \`\`\`java public class Account { //这里会自动将我们的字段修改为AtomicReference原子类型,以防止多线程环境下出现的问题 private final AtomicReference name = new AtomicReference(); ... //当我们调用getName才会去初始化字段的值,为了保证初始化只进行一次,整个过程与懒汉式单例模式一致 public String getName() { Object $value = this.name.get(); if ($value == null) { //判断值是否为null,如果是则需要进行懒初始化 synchronized(this.name) { //对我们的字段加锁,保证同时只能进一个 $value = this.name.get(); if ($value == null) { //再次进行一次判断,因为有可能其他线程后进入 String actualValue = "小蜗牛"; $value = "小蜗牛" == null ? this.name : "小蜗牛"; this.name.set($value); } } } //返回得到的结果 return (String)($value == this.name ? null : $value); } } \`\`\` 【懒加载】其实就是将字段的值延迟赋值给字段。比如下面这种场景就很适合: \`\`\`java public class Account { @Getter(lazy = true) private final String name = initValue(); private String initValue() { System.out.println("我不希望在对象创建时就执行"); return "小蜗牛"; } } \`\`\` ##### \`@Setter\` 与\`@Getter\`非常相似,用于生成字段对应的Setter方法: \`\`\`java public class Account { @Setter private String name; } \`\`\` 得到结果为: \`\`\`java public class Account { private String name; ... public void setName(String name) { //自动生成一个setter方法 this.name = name; } } \`\`\` ##### 注意: 如果手动编写了对应字段的Getter或是Setter方法(按照上述命名规则进行判断)那么Lombok提供的注解将不会生效,也不会覆盖我们自己编写的方法: \`\`\`java public class Account { @Setter private String name; public void setName(int name) { //即使上面添加Setter注解,这里也不会被覆盖,但是仅限于同名不同参的情况 System.out.println("我是自定义的"); this.name = name; } } \`\`\` ### 默认字段修饰 ##### \`@FieldDefaults\` 默认字段修饰可以为类中字段快速生成对应的修饰符,避免我们手动进行编写。只需添加\`@FieldDefaults\`即可: \`\`\`java @FieldDefaults(level = AccessLevel.PRIVATE) public class Account { int id; String name; int age; } \`\`\` 这样生成的代码中所有字段就自动变成private了: \`\`\`java public class Account { private int id; private String name; private int age; ... } \`\`\` ### 构造方法相关 Lombok也可以为我们自动生成对应的构造方法,它提供了三个用于处理构造方法的注解,我们来依次认识一下它们。首先是最简单的 ##### \`@AllArgsConstructor\` 它用于为类中所有字段生成一个构造方法: \`\`\`java @AllArgsConstructor public class Account { private int id; private String name; private int age; } \`\`\` 它只能添加到类上,之后生成: \`\`\`java public class Account { private int id; private String name; private int age; public Account(int id, String name, int age) { //自动生成一个携带所有参数的构造方法 this.id = id; this.name = name; this.age = age; } } \`\`\` 我们接着来看它的一些属性: \`\`\`java @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface AllArgsConstructor { //用于生成一个静态构造方法 String staticName() default ""; //用于在构造方法上添加额外的注解 AnyAnnotation\[\] onConstructor() default {}; //设置构造方法的访问权限级别 AccessLevel access() default lombok.AccessLevel.PUBLIC; ... } \`\`\` \`staticName\`属性,它主要用于生成静态构造方法,现在很多类都包含一些静态构造方法,用于快速构造对应的类对象。例如集合: \`\`\`java List strings = List.of("A", "B", "C", "D"); \`\`\` 将\`staticName\`设置一个名字即可: \`\`\`java @AllArgsConstructor(staticName = "with") public class Account { ... } \`\`\` 此时得到结果为: \`\`\`java public class Account { ... //强制生成一个带全部参数的private方法,不可修改 private Account(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public static Account with(int id, String name, int age) { return new Account(id, name, age); } } \`\`\` 我们在使用时,需要调用此静态构造方法来创建对象: \`\`\`java Account account = Account.with(1, "小明", 18); \`\`\` 便于类型推断,且简化代码 ##### \`@NoArgsConstructor\` 无参构造方法注解: \`\`\`java @NoArgsConstructor @AllArgsConstructor public class Account { ... } \`\`\` 这样我们就可以得到一个既有全参构造又有无参构造的类: \`\`\`java public class Account { ... public Account() {} public Account(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } } \`\`\` ##### 注意: 使用\`@NoArgsConstructor\`时类中不允许存在final类型的字段,否则会出现错误: 需要操作\`@NoArgsConstructor\`中额\`force\`属性,它可以在创建无参构造时,为final类型的字段给一个默认值,这样就可以同时存在了: \`\`\`java @NoArgsConstructor(force = true) //强制开启 @AllArgsConstructor public class Account { private final int id; //字段必须初始化 private String name; private int age; } \`\`\` 得到的结果为: \`\`\`java public class Account { ... public Account() { this.id = 0; //强行生成一个无参构造,但是这里也会为属性设置一个默认值,不然编译会报错 } ... } \`\`\` ##### \`@RequiredArgsConstructor\` 用于生成那些需要初始化的参数的构造方法,也就是说类中哪些字段为final,它就只针对这些字段生成对应的构造方法,比如: \`\`\`java @RequiredArgsConstructor public class Account { private final int id; private String name; private final int age; } \`\`\` 生成的结果为: \`\`\`java public class Account { ... public Account(int id, int age) { //只为fianl字段id和age生成了对应的构造方法 this.id = id; this.age = age; } } \`\`\` 有关构造函数相关内容我们就介绍到这里。 ##### 自选普通字段 上一个注解只能对final字段生成构造方法,若需要自选普通字段生成构造方法,依然需要手动编写。 ### 打印对象 ##### \`@ToString\` 在类上添加一个\`@ToString\`注解即可: \`\`\`java @ToString @AllArgsConstructor public class Account { private int id; private String name; private int age; } \`\`\` \`\`\`java public static void main(String\[\] args) { Account account = new Account(1, "小明", 18); System.out.println(account); //尝试直接打印 } \`\`\` 这样就可以直接得到一个格式化好的字符串: \`\`\`\`java Account(id=1, name=小明, age=18) \`\`\`\` 我们来看看生成出来的内容: \`\`\`java public class Account { ... public String toString() { //自动为每个字段都生成打印 return "Account(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")"; } ... } \`\`\` 是不是感觉非常方便?一个注解就搞定了这么繁杂的代码。我们来看看它有哪些参数: \`\`\`java @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface ToString { //是否在打印的内容中带上对应字段的名字 boolean includeFieldNames() default true; //用于排除不需要打印的字段(这种用法很快会被移除,不建议使用) String\[\] exclude() default {}; //和上面相反,设置哪些字段需要打印,默认打印所有(这种用法很快会被移除,不建议使用) String\[\] of() default {}; //不仅为当前类中所有字段生成,同时还调用父类toString进行拼接 boolean callSuper() default false; //默认情况下生成的toString会尽可能使用get方法获取字段值,我们也可以手段关闭这个功能 boolean doNotUseGetters() default false; //开启后将只为字段或get方法上添加了@ToString.Includ注解的内容生成toString方法,白名单模式 boolean onlyExplicitlyIncluded() default false; /\*\* \* 用于排除toString中的字段 \*/ @Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface Exclude {} /\*\* \* 用于手动包含toString中的字段 \*/ @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface Include { //配置字段打印顺序的优先级 int rank() default 0; //配置一个自定义的字段名称进行打印 String name() default ""; } } \`\`\` 我们首先来看\`@ToString\`对于get方法的特殊机制,它会尽可能使用我们自定义的get方法获取字段的值,命名规则判定和之前一样: \`\`\`java @ToString @AllArgsConstructor public class Account { ... public String getName() { return name + "同学"; //编写了一个自定义的getName方法 } } \`\`\` 打印得到的结果为: \`\`\`\`java Account(id=1, name=小明同学, age=18) \`\`\`\` 有时候可能我们希望的是直接打印字段原本的值,所以为了避免这种情况,我们可以手动为\`@ToString\`配置doNotUseGetters属性: \`\`\`java @ToString(doNotUseGetters = true) //设置为false只打印属性值 不输出属性名 @AllArgsConstructor public class Account { \`\`\` 这样生成的代码中就不会采用getter方法了。 \~\~\~\~java @ToString(callSuper = true)//该注解会将父类对象也打印出来 @AllArgsConstructor public class Account { private int id; private String name; private int age; } \~\~\~\~ ##### \`@ToString.Exclude\` 黑名单:如果需要排除某些字段,可以使用\`@ToString.Exclude\`注解,将其添加到对应的字段上时,就不会打印了: \`\`\`java @ToString @AllArgsConstructor public class Account { @ToString.Exclude //添加该注解的属性不打印 private int id; private String name; private int age; } \`\`\` \`\`\`\`java Account(name=小明, age=18) \`\`\`\` ##### \`@ToString.Include\` 白名单:该模式就会很麻烦,也就是需要指定哪些字段打印,哪些字段才打印,考虑到这个问题,需要配合白名单相关的注解:要开启白名单模式,需要将onlyExplicitlyIncluded设置为真,接着为我们需要打印的字段添加\`@ToString.Include\`注解: \`\`\`java @ToString(onlyExplicitlyIncluded = true)//开启白名单模式 @AllArgsConstructor public class Account { @ToString.Include //添加该注解的属性被打印 private int id; @ToString.Include private String name; private int age; } \`\`\` \`\`\`java Account(id=1, name=小明) \`\`\` ##### 注意: \`@ToString.Include\`不仅可以对字段生效,还可以对方法生效,它可以将某些方法执行后的结果也包含在toString中: \`\`\`java @ToString(onlyExplicitlyIncluded = true) @AllArgsConstructor public class Account { ... @ToString.Include public String test() { return "写代码"; } } \`\`\` \`\`\`java Account(id=1, name=小明, test=写代码) \`\`\` 我们来看看\`@ToString.Include\`可以设置的一些参数,比如我们可以手动为字段起一个用于打印的名字: \`\`\`java @ToString.Include(name = "编号") private int id; @ToString.Include(name = "名字") private String name; \`\`\` \`\`\`java Account(编号=1, 名字=小明) \`\`\` 默认情况下toString打印的字段属性是按照声明顺序进行的,我们也可以手动为其指定顺序: \`\`\`java @ToString.Include(name = "编号") private int id; @ToString.Include(name = "名字", rank = 1) //rank越大,越靠前,默认为0 private String name; \`\`\` \`\`\`\`java Account(名字=小明, 编号=1) \`\`\`\` ### 比较相关 ##### \`@EqualsAndHashCode\` 为类添加\`@EqualsAndHashCode\`注解,Lombok可以自动生成类属性的比较方法以及对应的HashCode计算。 例如: \`\`\`java @EqualsAndHashCode @AllArgsConstructor public class Account { private int id; private String name; private int age; } \`\`\` 此时生成的类: \`\`\`java public class Account { ... public boolean equals(Object o) { //自动生成的equals重写方法,包含所有参数的比较 ... } protected boolean canEqual(Object other) { return other instanceof Account; } public int hashCode() { //自动生成的hashCode重写方法 ... } ... } \`\`\` 它自动为我们所有的参数生成了对应的比较方法,我们可以来测试一下生成的代码是否可以正常运行: \`\`\`java public static void main(String\[\] args) { Account a1 = new Account(1, "小明", 18); Account a2 = new Account(1, "小明", 18); System.out.println(a1.equals(a2)); //结果为true } \`\`\` \`@EqualsAndHashCode\`注解的参数大部分与我们上节讲解的\`@ToString\`类似,比如\`exclude\`、\`of\`、\`callSuper\`(默认关闭,开启后调用equals比较前先调用父类的equals进行一次比较)、\`doNotUseGetters\`以及生成的方法中需要额外携带的注解\`onParam\`属性等。 它同样可以使用\`onlyExplicitlyIncluded\`属性来开启白名单模式,我们可以使用以下注解来自由控制哪些属性会作为比较的目标,哪些需要排除: \* \`@EqualsAndHashCode.Exclude\` - 用于排除不需要参与比较的字段。 \* \`@EqualsAndHashCode.Include\` - 开启白名单模式后,用于标明哪些字段需要参与比较。 它与前面讲解的\`@ToString\`一样,我们可以来试试看: \`\`\`java @EqualsAndHashCode(onlyExplicitlyIncluded = true)//开启白名单模式 @AllArgsConstructor public class Account { @EqualsAndHashCode.Include private int id; //此时只对id字段进行比较 private String name; private int age; } \`\`\` \`\`\`java public static void main(String\[\] args) { Account a1 = new Account(1, "小明", 18); Account a2 = new Account(1, "小红", 17); System.out.println(a1.equals(a2)); //由于只比较id字段,因此结果为true } \`\`\` 和\`@ToString.Include\`一样,我们也可以将其添加到一个方法上,使其结果也参与到比较中。 \`\`\`java @EqualsAndHashCode.Include public int test() { return 1; } \`\`\` 不过,\`@EqualsAndHashCode.Include\`有一个\`replaces\`属性,它可以用于将当前方法的结果替代目标字段进行比较,比如我们像这样编写: \`\`\`java @EqualsAndHashCode.Include(replaces = "id") //此时id字段的比较不会直接比较其本身,而是改为调用此方法获取对应结果进行比较 public int test() { return 1; } \`\`\` 这样,在生成的equals方法中,需要比较id字段时,会直接比较两个对象调用\`test\`方法的结果。 最后我们来看看它独特的hashCode缓存机制,我们可以通过设置\`cacheStrategy\`属性来控制hashCode结果的缓存,这有助于优化程序的性能,默认情况下缓存为NEVER也就是不启用,我们也可以开启LAZY模式: \`\`\`java @EqualsAndHashCode(cacheStrategy = EqualsAndHashCode.CacheStrategy.LAZY) @AllArgsConstructor public class Account { private int id; private String name; private int age; } \`\`\` 此时,生成的hashCode方法在第一次生成结果后,后续将一直使用同样的结果直接返回,避免二次计算: \`\`\`java public int hashCode() { if (this.$hashCodeCache != 0) { //判断是否生成过hashCode,直接使用缓存 return this.$hashCodeCache; } else { ... this.$hashCodeCache = result; //设置缓存 return result; } } \`\`\` 这对于那些属性值不会发生变化的实体类来说,是一个很好的选择,但是如果后续使用中字段值可能会发生变化从而影响HashCode的结果,则不建议使用此功能。 ### @Data 使用\`@Data\`注解,它等价于我们在类上添加这些注解:\`@Getter\` \`@Setter\` \`@RequiredArgsConstructor\` \`@ToString\` \`@EqualsAndHashCode\` \`\`\`java @Data //一个注解直接把get和set方法、构造方法、toString、equals全包了 public class Account { private int id; private String name; private int age; } \`\`\` 当然,如果我们希望某个类只作为结果,里面的数据不可进行修改,我们也可以使用\`@Data\`的只读版本\`@Value\`,它等价于添加注解:\`@Getter\` \`@FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE)\` \`@AllArgsConstructor\` \`@ToString\` \`@EqualsAndHashCode\` \`\`\`java @Value public class Account { //类会自动变成final类型 int id; //在\`@FieldDefaults\`配置下,会自动将类属性变为private和final的状态,这里我们无需手动编写 String name; int age; } \`\`\` 只不过在Java17之后,这种类完全可以被记录类型平替,因此使用时IDEA会提示我们直接使用记录类型。 ### 总结流程: \`\`\` //1.添加Data //2.添加空参满参 //3.根据具体需求 操作Getter Setter EqualsAndHashCode ToString \`\`\` ### 资源释放和异常处理 有些时候,我们可能会用到一些需要释放资源的对象,比如Stream这类,在Java7的时候推出了try-with-resource语法,它大大减少了我们编写资源释放语句的成本: \`\`\`java public static void main(String\[\] args) throws IOException { //使用try-with-resource会自动生成close相关操作的代码 try (FileInputStream in = new FileInputStream("test.exe")){ byte\[\] bytes = in.readAllBytes(); System.out.println(new String(bytes)); } } \`\`\` 虽然这种方式已经能够很大程度减少我们的工作,但是它依然不够山里灵活,Lombok为我们提供了一个更加简单方便的注解,我们只需要在需要释放资源的变量前添加\`@Cleanup\`即可: \`\`\`java public static void main(String\[\] args) throws IOException { //添加即可自动释放资源 @Cleanup FileInputStream in = new FileInputStream("test.py"); byte\[\] bytes = in.readAllBytes(); System.out.println(new String(bytes)); } \`\`\` 生成的代码为: \`\`\`java public static void main(String\[\] args) throws IOException { FileInputStream in = new FileInputStream("test.py"); try { byte\[\] bytes = in.readAllBytes(); //自动将后续操作添加到try语句块中 System.out.println(new String(bytes)); } finally { //在finally中对资源进行释放 if (Collections.singletonList(in).get(0) != null) { in.close(); } } } \`\`\` 当然,我们也可以自定义需要关闭资源的方法: \`\`\`java static class Test { public void end() { System.out.println("关闭资源"); } } public static void main(String\[\] args) throws IOException { @Cleanup("end") Test test = new Test(); } \`\`\` 我们接着来看下一个更好用的注解,各位小伙伴在使用Steam时多多少少都会遇到手动处理IOException的问题,这就导致我们很多地方都要去写\`throws\`来标明方法会抛出的异常,这实在是太不方便了。 Lombok为我们提供了一个\`@SneakyThrows\`注解,就像它的名字一样悄悄咪咪地抛出,不需要我们手动指定: \`\`\`java @SneakyThrows public static void main(String\[\] args) { @Cleanup FileInputStream in = new FileInputStream("test.py"); byte\[\] bytes = in.readAllBytes(); System.out.println(new String(bytes)); } \`\`\` 实际上在编译后会生成: \`\`\`java public static void main(String\[\] args) { try { //自动使用trycatch包裹内部,出现问题直接原样抛出 ... } catch (Throwable var7) { Throwable $ex = var7; throw $ex; } } \`\`\` 官方解释这个注解是为了避免 javac 的异常捕获检查,减少手动编写异常抛出的代码。 ### 标准异常 我们常常需要在自己的项目中创建各种各样的自定义类型,便于我们快速定位问题原因。 只不过编写一个异常实际上很多情况下都是固定形式,比如: \`\`\`java public class CustomException extends RuntimeException { public CustomException(String message) { //往往都要实现父类的一些构造方法 super(message); } } \`\`\` Lombok为了简化这些繁杂的过程,为我们提供了一个\`@StandardException\`注解,这个注解会在编译时自动为我们生成对应的构造方法实现: \`\`\`java @StandardException public class CustomException extends Exception { } \`\`\` 生成的结果为: \`\`\`java public class CustomException extends Exception { public CustomException() { this((String)null, (Throwable)null); } public CustomException(String message) { this(message, (Throwable)null); } public CustomException(Throwable cause) { this(cause != null ? cause.getMessage() : null, cause); } public CustomException(String message, Throwable cause) { super(message); if (cause != null) { super.initCause(cause); } } } \`\`\`
评论