lombok

9次阅读

共计 12145 个字符,预计需要花费 31 分钟才能阅读完成。

相识

lombok 想必已经有很多人已经使用了很长时间了,而我却是第一次接触到,有点呆。lombok 主要是用于减少重复代码,通过一组简单的注释取代一些重复的 Java 代码。对于 lombok 的评价褒贬不一,有的人觉得特别方便,有的人觉得改变了一成不变的代码结构,增加了代码维护成本(有的人没有用过 lombok),我是觉得每一个工具诞生肯定是有他诞生的价值的,多学一个是一个啊,小老弟,用不用再说。:)

官方文档地址

官方 API 地址

官方注解介绍地址

准备

1、下载 idea 插件

我这里已经安装好了,没有安装的时候按钮应该是 Install

2、pom 文件引入 project lombok 的 maven 依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.2</version>
    <scope>provided</scope>
</dependency>

lombok maven 版本

注解介绍

@Getter 和 @Setter

顾名思义,生成 get 和 set 方法的注解,@Getter 和 @Setter 发生在编译阶段,编译之后,@Getter 和 @Setter 相关的注解就消失了,取而代之的是相应的 get 和 set 方法。

public class LombokTest {
    @Getter @Setter
    private boolean flag;
}

lombok 遵循了 boolean 类型的 get 方法的约定,我们来看一下编译后的代码(等效 Java 代码)吧。

public class LombokTest {
    private boolean flag;

    public LombokTest() {}

    public boolean isFlag() {return this.flag;}

    public void setFlag(boolean flag) {this.flag = flag;}
}

可以看到 boolean 类型的 get 方法是 isFlag()而不是 getFlag()。我们还可定义生成 get 和 set 方法的访问级别。

public class LombokTest {@Getter(AccessLevel.PUBLIC)
    @Setter(AccessLevel.PROTECTED)
    private String name;
}

编译后的代码(等效 Java 代码)

public class LombokTest {
    private String name;

    public LombokTest() {}

    public String getName() {return this.name;}

    protected void setName(String name) {this.name = name;}
}

可以看到 setName 的访问级别是 protected。lombok 提供了下面几种级别。

AccessLevel 枚举类的源代码。

public enum AccessLevel {
    PUBLIC, // public
    MODULE, // 编译后相当于 default
    PROTECTED, // protected
    PACKAGE, // default
    PRIVATE, // private
    NONE; // 不生成

    private AccessLevel() {}
}

最后 @Getter 和 @Setter 注解是可以写在类级别的,作用于所有的成员变量,无法细粒度的控制访问级别。

@Getter(AccessLevel.MODULE)
@Setter(AccessLevel.PROTECTED)
public class LombokTest {
    private String name;
    private boolean flag;
}

编译后的代码(等效 Java 代码)

public class LombokTest {
    private String name;
    private boolean flag;

    public LombokTest() {}

    String getName() {return this.name;}

    boolean isFlag() {return this.flag;}

    protected void setName(String name) {this.name = name;}

    protected void setFlag(boolean flag) {this.flag = flag;}
}

@NonNull

变量空检查,如果 @NonNull 注解使用在构造函数的字段时,构造函数也会进行空变量检查。不可用于类级别。

public class LombokTest {
    @NonNull
    @Setter
    @Getter
    private String name;
    
    public LombokTest(@NonNull String name) {this.name = name;}
}

编译后的代码(等效 Java 代码)

public class LombokTest {
    @NonNull
    private String name;

    public LombokTest(@NonNull String name) {if (name == null) {throw new NullPointerException("name is marked @NonNull but is null");
        } else {this.name = name;}
    }

    public void setName(@NonNull String name) {if (name == null) {throw new NullPointerException("name is marked @NonNull but is null");
        } else {this.name = name;}
    }

    @NonNull
    public String getName() {return this.name;}
}

@ToString

用于生成 toString()方法。只能用于类级别。static 修饰的变量是不能生成在 toString()方法中的,原因是 static 修饰的变量在类创建的时候就生成了,只有一个;而普通的变量属于对象,每创建一个新的对象都会有一个。可以理解为 static 修饰的变量属于类,而普通变量数据对象,这样可能好理解一点。

@ToString
public class LombokTest {
    private String name;
    private static String phone;
}

编译后的代码(等效 Java 代码)

public class LombokTest {
    private String name;
    private static String phone;

    public LombokTest() {}

    public String toString() {return "YmlProperties(name=" + this.name + ")";
    }
}

默认情况下出了 static 修饰的字段都将以 name-value 的形式生成在 toString()方法中。同时 toString 注解中还可以设置一些属性来细粒度的控制 toString()方法。

下面是 ToString 注解的源码和解释:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface ToString {boolean includeFieldNames() default true; // 生成 toString()方法时默认为 name-value 的形式,设置为 false 时 则 value 的形式。String[] exclude() default {}; // 不包含指定字段

    String[] of() default {}; // 包含指定字段

    boolean callSuper() default false; // 是否调用父类的 toString()方法

    boolean doNotUseGetters() default false; // 生成 toString()方法是否直接访问字段,设置为 true 时直接访问字段(如:this.name),false 时通过 getter()方法进行访问 (如:this.getName())。当然如果没有生成 getter() 方法,无论是否设置都直接访问字段

    boolean onlyExplicitlyIncluded() default false; // 是否仅仅包含添加了 @ToString.Include 的字段,默认为 false。@Target({ElementType.FIELD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Exclude {// 使用 @ToString.Exclude 修饰字段,需要和 @ToString 注解一起使用,单独使用无效。}

    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Include {// 使用 @ToString.Include 修饰字段,需要和 @ToString 注解一起使用,单独使用无效。int rank() default 0;

        String name() default "";}
}

注意:

  • of 和 exclude 属性设置多个字段时,以字符串数组的形式,如:
@ToString(of = {"name","phone"}) // 包含 name 和 phone 字段
@ToString(exclude = {"name","phone"}) // 不包含 name 和 phone 字段
  • 使用 @ToString.Include 或者 @ToString.Exclude 注解时也需要使用 @ToString 注解,否者无效。
@ToString
public class LombokTest {
    @ToString.Include
    private String name;
    @ToString.Exclude
    private String phone;
}

@EqualsAndHashCode

生成 equals()和 hashCode()方法。默认使用所有的非 static 和非 transient 字段生成这两个方法。

@EqualsAndHashCode
public class LombokTest {
    private String name;
    private String phone;
    private boolean flag;
}

编译后的代码(等效 Java 代码)

public class LombokTest {
    private String name;
    private String phone;
    private boolean flag;

    public LombokTest() {}

    public boolean equals(Object o) {if (o == this) {return true;} else if (!(o instanceof LombokTest)) {return false;} else {LombokTest other = (LombokTest)o;
            if (!other.canEqual(this)) {return false;} else {
                label39: {
                    Object this$name = this.name;
                    Object other$name = other.name;
                    if (this$name == null) {if (other$name == null) {break label39;}
                    } else if (this$name.equals(other$name)) {break label39;}

                    return false;
                }

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

                if (this.flag != other.flag) {return false;} else {return true;}
            }
        }
    }

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

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $name = this.name;
        int result = result * 59 + ($name == null ? 43 : $name.hashCode());
        Object $phone = this.phone;
        result = result * 59 + ($phone == null ? 43 : $phone.hashCode());
        result = result * 59 + (this.flag ? 79 : 97);
        return result;
    }
}

和 ToString 注解相似也可以通过设置一些属性来控制决堤生成的 hashCode()和 equals()方法。下面是 @EqualsAndHashCode 注解的源码和解释,和 @ToString 一样的属性就没有写注释了。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface EqualsAndHashCode {String[] exclude() default {};

    String[] of() default {};

    boolean callSuper() default false;// 是否使用父类的 equals 和 toString 方法,注意:父类为 Object 时无法设置为 true。boolean doNotUseGetters() default false;

    EqualsAndHashCode.AnyAnnotation[] onParam() default {};

    boolean onlyExplicitlyIncluded() default false;

    /** @deprecated */
    @Deprecated
    @Retention(RetentionPolicy.SOURCE)
    @Target({})
    public @interface AnyAnnotation {// 过时的方法(deprecated)}

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Exclude { }

    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Include {String replaces() default "";
    }
}

@Data

这是 lombok 中使用的最多的注解,相当于 @ToString@EqualsAndHashCode@RequiredArgsConstructor@Getter@Setter五个注解的功能。虽然使用起来方法,但是失去了细粒度的控制。如果需要细粒度控制,则需要进行注解的覆盖。

@Data
public class LombokTest {
    private String name;
    private String phone;
    private boolean flag;
}

编译后的代码(等效 Java 代码)

public class LombokTest {
    private String name;
    private String phone;
    private boolean flag;

    public LombokTest() {}

    public String getName() {return this.name;}

    public String getPhone() {return this.phone;}

    public boolean isFlag() {return this.flag;}

    public void setName(String name) {this.name = name;}

    public void setPhone(String phone) {this.phone = phone;}

    public void setFlag(boolean flag) {this.flag = flag;}

    public boolean equals(Object o) {if (o == this) {return true;} else if (!(o instanceof LombokTest)) {return false;} else {LombokTest other = (LombokTest)o;
            if (!other.canEqual(this)) {return false;} else {
                label39: {Object this$name = this.getName();
                    Object other$name = other.getName();
                    if (this$name == null) {if (other$name == null) {break label39;}
                    } else if (this$name.equals(other$name)) {break label39;}

                    return false;
                }

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

                if (this.isFlag() != other.isFlag()) {return false;} else {return true;}
            }
        }
    }

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

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $name = this.getName();
        int result = result * 59 + ($name == null ? 43 : $name.hashCode());
        Object $phone = this.getPhone();
        result = result * 59 + ($phone == null ? 43 : $phone.hashCode());
        result = result * 59 + (this.isFlag() ? 79 : 97);
        return result;
    }

    public String toString() {return "LombokTest(name=" + this.getName() + ", phone=" + this.getPhone() + ", flag=" + this.isFlag() + ")";
    }
}

下面看一下 @Data 注解的源码。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Data {String staticConstructor() default "";
}

可以看到提供了一个,staticConstructor(), 这个方法的作用是私有化构造器,并生成一个静态的获取实例的方法,这样就可通过类名来获取实例(如:LombokTest.getLombokTest())

@Data(staticConstructor = "getLombokTest")
private LombokTest() {}

public static LombokTest getLombokTest() {return new LombokTest();
}
// ... 省略其他代码

@Cleanup

自动释放资源,主要用于 IO 流的操作,不需要手动编写释放资源的 try/finally 代码块。

public class LombokTest {public static void main(String[] args) throws Exception {@Cleanup InputStream is = new FileInputStream(args[0]);
        @Cleanup OutputStream os = new FileOutputStream(args[0]);
    }
}

编译后的代码(等效 Java 代码)

public class LombokTest {public static void main(String[] args) throws Exception {FileInputStream is = new FileInputStream(args[0]);

        try {OutputStream os = new FileOutputStream(args[0]);
            if (Collections.singletonList(os).get(0) != null) {os.close();
            }
        } finally {if (Collections.singletonList(is).get(0) != null) {is.close();
            }
        }
    }
}

还是挺方便的。再来看看 @Cleanup 注解的源代码

@Target({ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface Cleanup {String value() default "close";
}

可以看到默认调用的是 close 方法,也就是说我们可以设置 finally 方法体具体调用的流处理方法。如:

public static void main(String[] args) throws Exception {@Cleanup(value = "available") InputStream is = new FileInputStream(args[0]);
    @Cleanup OutputStream os = new FileOutputStream(args[0]);
}

编译后的代码(等效 Java 代码)

具体可以指定哪些字段,需要流对象支持哪些方法了。如 InputStream:

@Synchronized

同步代码块,只能使用在类方法(static)或者对象方法(普通方法)上,synchronized 关键字锁住的是 this,@Synchronized 注解默认使用的锁为 $lock, 静态的锁为 $LOCK,也可以自己指定锁对象,如示例中的:world

public class LombokTest {
    @Synchronized
    public void hello() {}

    @Synchronized
    public static void helloWorld() {}

    private final Object world = new Object();
    
    @Synchronized("world")
    public void world() {}
}

编译后的代码(等效 Java 代码)

public class LombokTest {private final Object $lock = new Object[0];
    private static final Object $LOCK = new Object[0];
    private final Object world = new Object();

    public LombokTest() {}

    public void hello() {
        Object var1 = this.$lock;
        synchronized(this.$lock) {;}
    }

    public static void helloWorld() {
        Object var0 = $LOCK;
        synchronized($LOCK) {;}
    }

    public void world() {
        Object var1 = this.world;
        synchronized(this.world) {;}
    }
}

@SneakyThrows

个人觉得没有啥意义,唯一的作用可能就是不用手动捕获异常或者向上抛出,而是通过 lombok 帮你捕获。。。可以指定捕获的异常类型,默认捕获的是 Throwable。

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

    @SneakyThrows(IllegalAccessException.class)
    public void testSneakyThrows2() {throw new IllegalAccessException();
    }
}

编译后的代码(等效 Java 代码)

public class LombokTest {public LombokTest() { }

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

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

感觉有点呆。。。官方建议在没有深思熟虑之前不要使用这个注解。。。下面是官网上的一句话:

  • You can pass any number of exceptions to the @SneakyThrows annotation. If you pass no exceptions, you may throw any exception sneakily.

@NoArgsConstructor,@ RequiredArgsConstructor,@ AllArgsConstructor

@NoArgsConstructor 生成一个无参构造器。

@NoArgsConstructor 注解源代码,另外两个注解的源码和这个很类似,就不贴出来了。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface NoArgsConstructor {String staticName() default ""; // 如果指定了相应字段,则私有化构造并生成一个静态的获取实例方法。NoArgsConstructor.AnyAnnotation[] onConstructor() default {};

    AccessLevel access() default AccessLevel.PUBLIC;

    boolean force() default false;

    /** @deprecated */
    @Deprecated
    @Retention(RetentionPolicy.SOURCE)
    @Target({})
    public @interface AnyAnnotation {// 过时的(deprecated)}
}

@ RequiredArgsConstructor 生成包含必须处理的字段的构造器,如:final 修饰(必须初始化)、@NonNull 注解修饰的。

@RequiredArgsConstructor
public class LombokTest {
    private String name;
    private String phone;
    private transient boolean flag;
    private final String finalFiled;
    private static int num;
}

编译后的代码(等效 Java 代码)

public class LombokTest {
    private String name;
    private String phone;
    private transient boolean flag;
    private final String finalFiled;
    private static int num;

    public LombokTest(String finalFiled) {this.finalFiled = finalFiled;}
}

@ AllArgsConstructor 生成一个全参构造器,除 static 修饰的字段。@NonNull 可以组合使用。

@AllArgsConstructor
public class LombokTest {
    private String name;
    @NonNull
    private String phone;
    private transient boolean flag;
    private final String finalFiled;
    private static int num;
}

编译后的代码(等效 Java 代码)

public class LombokTest {
    private String name;
    @NonNull
    private String phone;
    private transient boolean flag;
    private final String finalFiled;
    private static int num;

    public LombokTest(String name, @NonNull String phone, boolean flag, String finalFiled) {if (phone == null) {throw new NullPointerException("phone is marked @NonNull but is null");
        } else {
            this.name = name;
            this.phone = phone;
            this.flag = flag;
            this.finalFiled = finalFiled;
        }
    }
}

小结

学到这里常用的 Lombok 注解基本上都有了,如果想要根据深入的学习,建议去官网看。官网地址文章开始已经列出来了,加油!!!

正文完
 0

Lombok

9次阅读

共计 2268 个字符,预计需要花费 6 分钟才能阅读完成。

官网链接:https://projectlombok.org/

什么是 Lombok

Lombok 是一个 Java 库,它可以通过添加注解的方式,为 Java 类自动插入相应的 Getter/Setter,构造器,equals 方法等。这样就省去了手动重建代码的麻烦,简化开发,也是代码更为简洁。

当然,Lombok 也支持使用注解为 Java 类添加 Builder,自动注入日志记录变量等。

为什么使用 Lombok(优劣)

优点

显然,使用 Lombok 可以大量减少项目中的模版代码。虽然开发过程中可以通过 IDE 快速生成这些代码,但当 Java 类的变量发生变化,或者协作开发过程中有代码冲突,这无疑增加了我们无谓的工作量。

其次,Lombok 还可以增强代码的规范性,通过 @Data 注解可以自动添加包括 toString 在内的模版方法,避免了这些方法缺失,也省去了因变量修改而修改 toString,equals 方法的麻烦。

缺点

Lombok 的项目集成和使用,它需要 IDE 和项目都要支持。不过这不麻烦。

安装

IntelliJ IDEA

  1. 打开 File/Setting 或 快捷键 Ctrl+Alt+S
  2. 选择 Plugins
  3. 搜索插件 IntelliJ Lombok plugin 并安装
  4. 重启 IDEA 即完成

STS/Eclipse

  1. 下载 lombok.jar
  2. 将 lombok.jar 放在 sts(eclipse)安装目录下,和 sts.ini 文件平级的
  3. 在 lombok.jar 的目录下,运行:java -jar lombok.jar,在弹出框中指定 STS/Eclipse 安装目录,点击安装
  4. 查看 sts.ini,正常情况应该多个一行配置:-javaagent:XXX\lombok.jar

依赖添加

<lombok.version>1.18.6</lombok.version>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
    <scope>provided</scope>
</dependency>

使用介绍

按使用频率从上及下排列

@Data

@Data@ToString@EqualsAndHashCode,所有字段上加 @Getter,所有非 final 字段加@Setter@RequiredArgsConstructor 的快捷方式。它提供了这些注解的默认配置,用于满足最基本 Java 类配置。

注意:@Data 注解时,以 $- 开头的字段不会被自动注入对应方法,包括 @Getter, @Setter, @ToString 和 @EqualsAndHashCode 内不会包含这个字段。

@Getter @Setter

给字段添加 Getter/Setter 方法,可以添加到类或字段上。如果同时添加,以字段上的配置为准。但都会被显示声明的方法覆盖。

对于一些 boolean 类型的字段,需要注意 is- 开头的字段。但对于引用类型 Boolean 无此影响。

@Getter @Setter public boolean isFlower;
@Getter @Setter public Boolean isTree;
// getter
public boolean isFlower() { return this.isFlower;};
// setter
public void setFlower(boolean isFlower) {this.isFlower = isFlower;}
// getter
public boolean getIsTree() { return this.isTree;};
// setter
public void setIsTree(boolean isTree) {this.isTree = isTree;}

@Getter 和 @Setter 可以通过指定 PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE, NONE 设置访问权限。

@ToString

添加 toString 方法。

注意,如果一个类继承父类,默认插入的 toString 是不会添加父类字段。要添加父类的字段,需要使用 @ToString(callSuper = true)

去除字段名,可以设置 includeFieldNames = false 默认为 true

屏蔽字段,可以在字段声明时添加 @ToString.Exclude

@ToString.Exclude private String name;

@EqualsAndHashCode

添加 equals hashCode 方法。

默认情况下,任何以 $ 符号开头的变量都会自动排除。只能通过标记来包含它们@EqualsAndHashCode.Include

@EqualsAndHashCode.Exclude 用来屏蔽指定字段。

@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

添加构造方法。

设置 access = AccessLevel.PRIVATE 可以指定构造方法的访问权限。权限包括(PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE, NONE),默认 PUBLIC。

@AllArgsConstructor 会替换掉默认的无参构造方法。如需同时存在无参构造方法和有参构造方法,可以同时添加 @NoArgsConstructor

注意事项

  1. 优先级:显示声明 > 字段上加注解 > 类上加注解

更多使用的用例,可以查询官网的详细内容。

正文完
 0