Lombok 常用注解

Lombok 常用注解

官方资料

官网

参考博客

https://zhuanlan.zhihu.com/p/32779910?utm_id=0

超级全面的 Lombok 注解介绍,学一波! - 腾讯云开发者社区-腾讯云

Java - 十分钟搞懂Lombok使用与原理

IDEA 配置

设置支持注解处理

然后安装相关 Lombok 插件,但是从 IDEA 2020.3 以后,IDEA 自带了 Lombok 插件,因此只需要检查是否启用此插件即可,如果是老版本的 IDEA,输入 Lombok,然后安装插件,再重启 IDEA 即可。

在项目中引入 POM 依赖

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>provided</scope>
</dependency>

然后就可以在项目代码中使用 Lombok 注解。

基本注解

@Getter/@Setter

使用在属性上;为单个属性提供 getter / setter 方法; 注解在上,为该类所有的属性提供 getter / setter 方法, 默认生成的 getter / setter 方法是 public 的,除非你明确指定一个 AccessLevel。可选的访问级别为 PUBLIC,PROTECTED,PACKAGE和PRIVATE。你可以使用 AccessLevel.NONE 访问级别来手动禁用任何字段的 getter / setter 生成。这使你可以覆盖类上的 @Getter,@Setter或@Data 注释的行为

@Getter/@Setter 注解还有其他属性,比如 lazy 懒加载,带上这个参数,@Getter/@Setter 就不能在类上使用,只能在属性上使用,而且要求属性必须是 final 的

public class GetterSetterTest {

    @Getter
    @Setter
    private String name;

    private String gender;

}

生成的代码,注意编译器会默认添加无参构造函数,这个不是 Lombok 实现的

public class GetterSetterTest {
    private String name;
    private String gender;

    public GetterSetterTest() {
    }

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

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

在类上使用,在类上使用的时候,会为所有的非静态且没有配置 @Getter/@Setter注解 的字段添加 @Getter/@Setter

@Getter
@Setter
public class GetterSetterTest1 {

    private String name = "dsd";

    @Getter(AccessLevel.NONE)
    private String gender;

}

生成代码

public class GetterSetterTest1 {
    private final String name = "dsd";
    private String gender;

    public GetterSetterTest2() {
    }

    public String getName() {
        this.getClass();
        return "dsd";
    }

    public void setGender(String gender) {
        this.gender = gender;
    }
}

有的时候,你需要在 getter 和 setter 上自定义一些业务逻辑,这个时候,你可以直接自己手动添加或者用 IDEA 的 Alt+Insert 自动生成,这样,代码上现有的 Lombok 的 @Getter/@Setter 配置会默认失效。

@AllArgsConstructor/@NoArgsConstructor/@RequiredArgsConstructor

这三个注解都用在上;@AllArgsConstructor 会为类提供一个全参(所有属性都是参数)的构造方法,加了这个注解后,编译器就不会添加默认的无参构造方法了,@NoArgsConstructor 会添加一个无参构造函数,编译器本来就会默认添加无参的构造函数,这个注解一般会在已经添加了@AllArgsConstructor/@RequiredArgsConstructor 之后使用。@RequiredArgsConstructor 将必要的参数,比如 final 修饰的,或者是添加了@NonNull 注解的字段作为参数构建构造函数,其实最常用的,还是@RequiredArgsConstructor,毕竟,设置了@NonNull 注解的属性,肯定是构造的时候需要关注的属性。

如果指定 staticName = “of”参数,同时还会生成一个返回类对象的名称为 "of" 的静态工厂方法,比使用构造函数方便很多。例如 XXX xxx =XXX.of();

注意,如果字段为静态,那么不会添加到构造函数的参数中,因为构造函数是为了创建类实例,而静态字段为所有类实例共有,所以默认不添加到构造函数参数中是合情合理的。举个例子,我们有的时候为了方便会在类里面放一点常量字符串,比如

@Data
public class Result<T> {
    public static int STATUS_NORMAL = 1;
    public static int STATUS_EXCEPTION = -1;
    private int status;
    private String message;
    private T content;
}

这个时候你为 STATUS_NORMAL 或者 STATUS_EXCEPTION 生成 get/set 方法或者将其添加到构造函数中,就会显得不合适。PS:类上的 @Getter/@Setter注解 也不会为类中的静态字段添加 get/set方法

@AllArgsConstructor

@AllArgsConstructor
public class AllArgsConstructorTest {

    private String property0;
    private String property1;
    private String property2;
    private String property3;

}

生成代码

public class AllArgsConstructorTest {
    private String property0;
    private String property1;
    private String property2;
    private String property3;

    public AllArgsConstructorTest(String property0, String property1, String property2, String property3) {
        this.property0 = property0;
        this.property1 = property1;
        this.property2 = property2;
        this.property3 = property3;
    }
}

@NoArgsConstructor

@AllArgsConstructor
@NoArgsConstructor
public class AllArgsConstructorTest1 {

    private String property0;
    private String property1;
    private String property2;
    private String property3;

}

生成代码

public class AllArgsConstructorTest1 {
    private String property0;
    private String property1;
    private String property2;
    private String property3;

    public AllArgsConstructorTest1(String property0, String property1, String property2, String property3) {
        this.property0 = property0;
        this.property1 = property1;
        this.property2 = property2;
        this.property3 = property3;
    }

    public AllArgsConstructorTest1() {
    }
}

@RequiredArgsConstructor

@RequiredArgsConstructor
public class AllArgsConstructorTest2 {

    @NonNull
    private String property0;
    private final String property1;
    @NonNull
    private String property2;
    private String property3;

}

生成代码

public class AllArgsConstructorTest2 {
    private @NonNull String property0;
    private final String property1;
    private @NonNull String property2;
    private String property3;

    public AllArgsConstructorTest2(@NonNull String property0, String property1, @NonNull String property2) {
        if (property0 == null) {
            throw new NullPointerException("property0 is marked non-null but is null");
        } else if (property2 == null) {
            throw new NullPointerException("property2 is marked non-null but is null");
        } else {
            this.property0 = property0;
            this.property1 = property1;
            this.property2 = property2;
        }
    }
}

@EqualsAndHashCode

上使用, 生成 equals、canEqual、hashCode 方法。默认情况下,它将使用所有非静态(static),非瞬态(transient)字段,但是您可以通过使用 @EqualsAndHashCode.Include 标记属性来添加这些字段。同时你还可以使用 @EqualsAndHashCode.Exclude 来排除字段。此外,还可以在类上使用 @EqualsAndHashCode(onlyExplicitlyIncluded = true) 配合属性上使用 @EqualsAndHashCode.Include 来精确指定要使用的字段或方法。此时,只有被 @EqualsAndHashCode.Include 修饰的字段才会被使用

如果将 @EqualsAndHashCode 应用于一个父类的子类,可以通过将 callSuper 设置为 true,在生成的 equals/hashCode 方法中包括父类的 equals/hashCode 方法。

基本使用

@EqualsAndHashCode
public class EqualsAndHashCodeTest {

    private String occupation;

    private String payment;

    private int age;

}

生成代码,注意,判断类型的时候,使用的是 instanceof,而不是 getClass。这个区别回看《Java 核心技术》卷一,第五章继承,相等测试与继承小节。

public class EqualsAndHashCodeTest {
    private String occupation;
    private String payment;
    private int age;

    public EqualsAndHashCodeTest() {
    }

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

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

                return true;
            }
        }
    }

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

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

@EqualsAndHashCode.Include@EqualsAndHashCode.Exclude

@EqualsAndHashCode
public class EqualsAndHashCodeTest1 {
    @EqualsAndHashCode.Include
    private static String beanId;

    private String occupation;

    private String payment;
    @EqualsAndHashCode.Exclude
    private int age;


}

生成代码,注意,包含了静态字段,排除了 @EqualsAndHashCode.Exclude 修饰的字段

public class EqualsAndHashCodeTest1 {
    private static String beanId;
    private String occupation;
    private String payment;
    private int age;

    public EqualsAndHashCodeTest1() {
    }

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

                    return false;
                }

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

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

                return true;
            }
        }
    }

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

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

callSuper 设置为 true

@EqualsAndHashCode
@Getter
@Setter
public class BasicBean {

    private String basicInternal;

    private String basicIntrinsic;

    private int basicOrdinal;

}
@EqualsAndHashCode(callSuper = true)
public class EqualsAndHashCodeTest2 extends BasicBean{

    private String occupation;

    private String payment;

    private int age;

}

生成代码,可以注意到 equals 中调用了 super.equals(o) 方法,hashCode 中调用了 super.hashCode() 方法

public class EqualsAndHashCodeTest2 extends BasicBean {
    private String occupation;
    private String payment;
    private int age;

    public EqualsAndHashCodeTest2() {
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof EqualsAndHashCodeTest2)) {
            return false;
        } else {
            EqualsAndHashCodeTest2 other = (EqualsAndHashCodeTest2)o;
            if (!other.canEqual(this)) {
                return false;
            } else if (!super.equals(o)) {
                return false;
            } else if (this.age != other.age) {
                return false;
            } else {
                label40: {
                    Object this$occupation = this.occupation;
                    Object other$occupation = other.occupation;
                    if (this$occupation == null) {
                        if (other$occupation == null) {
                            break label40;
                        }
                    } else if (this$occupation.equals(other$occupation)) {
                        break label40;
                    }

                    return false;
                }

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

                return true;
            }
        }
    }

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

    public int hashCode() {
        int PRIME = true;
        int result = super.hashCode();
        result = result * 59 + this.age;
        Object $occupation = this.occupation;
        result = result * 59 + ($occupation == null ? 43 : $occupation.hashCode());
        Object $payment = this.payment;
        result = result * 59 + ($payment == null ? 43 : $payment.hashCode());
        return result;
    }
}

@ToString

这个注解用在上,可以生成所有属性的 toString 方法。

默认情况下,将打印所有非静态(static)字段。跟@EqualsAndHashCode 注解一样,可以使用 @ToString.Include 手动包含静态字段,如果要跳过某些字段,可以使用 @ToString.Exclude 注释这些字段。或者,可以通过使用 @ToString(onlyExplicitlyIncluded = true),然后使用 @ToString.Include 标记要包含的每个字段,来确切指定希望使用的字段。

通过将 callSuper 设置为 true,可以将 toString 的超类实现的输出包含到输出中。请注意,java.lang.Object中toString() 的默认实现几乎毫无意义。

@ToString
public class ToStringTest {
    private String property0;
    private String property1;
    private String property2;
    private String property3;

}

生成代码

public class ToStringTest {
    private String property0;
    private String property1;
    private String property2;
    private String property3;

    public ToStringTest() {
    }

    public String toString() {
        return "ToStringTest(property0=" + this.property0 + ", property1=" + this.property1 + ", property2=" + this.property2 + ", property3=" + this.property3 + ")";
    }
}

@ToString.Include@ToString.Exclude

@ToString
public class ToStringTest1 {

    private String property0;

    @ToString.Include
    private static String property1;

    private String property2;

    @ToString.Exclude
    private String property3;

}

生成代码,可以看到静态的 property1 包含在 toString 方法中,property3 不在

public class ToStringTest1 {
    private String property0;
    private static String property1;
    private String property2;
    private String property3;

    public ToStringTest1() {
    }

    public String toString() {
        return "ToStringTest1(property0=" + this.property0 + ", property1=" + property1 + ", property2=" + this.property2 + ")";
    }
}

调用父类的 toString 方法

@ToString(callSuper = true)
public class ToStringTest2 extends BasicBean{

    private String property0;

    private static String property1;

    private String property2;

    private String property3;

}

生成代码

public class ToStringTest2 extends BasicBean {
    private String property0;
    private static String property1;
    private String property2;
    private String property3;

    public ToStringTest2() {
    }

    public String toString() {
        return "ToStringTest2(super=" + super.toString() + ", property0=" + this.property0 + ", property2=" + this.property2 + ", property3=" + this.property3 + ")";
    }
}

@Data/@Value

@Data 是一个方便的快捷方式注解,它将 @ToString@EqualsAndHashCode@Getter/@Setter@RequiredArgsConstructor 的功能打包在一起:换句话说,@Data 生成与简单 POJO(Plain Old Java Object) 关联的所有样板:包括所有属性的 getter,所有 非final 属性的 setter,以及涉及类字段 (static 属性) 的适当的 toStringequalshashCode 实现,同时以所有 final 属性和 @NonNull 修饰的属性作为参数创建构造函数,以确保这些字段永远不会为 null

一般情况下,如果你没有进行特殊设置的需求,那你可以直接使用@Data 注解,但是如果有,那就只能手动配置一个个注解了。

注意:当你明确使用其他非 @RequiredArgsConstructor 注解的时候,无参构造可能会消失,如果需要有一个无参构造,则需要手动加上 @NoArgsConstructor 注解。

@Data 注解

@Data
public class DataTest {

    private String occupation;

    private String payment;

    private int age;

}

生成代码

public class DataTest {
    private String occupation;
    private String payment;
    private int age;

    public DataTest() {
    }

    public String getOccupation() {
        return this.occupation;
    }

    public String getPayment() {
        return this.payment;
    }

    public int getAge() {
        return this.age;
    }

    public void setOccupation(String occupation) {
        this.occupation = occupation;
    }

    public void setPayment(String payment) {
        this.payment = payment;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

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

                return true;
            }
        }
    }

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

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        result = result * 59 + this.getAge();
        Object $occupation = this.getOccupation();
        result = result * 59 + ($occupation == null ? 43 : $occupation.hashCode());
        Object $payment = this.getPayment();
        result = result * 59 + ($payment == null ? 43 : $payment.hashCode());
        return result;
    }

    public String toString() {
        return "DataTest(occupation=" + this.getOccupation() + ", payment=" + this.getPayment() + ", age=" + this.getAge() + ")";
    }
}

@Value 注解

@Value 注解和 @Data 类似,区别在于它会把强制吧所有属性设置为 private final,并且不会生成 set 方法。相当于创建一个不可变类,即一旦初始化就无法再更改,关于不可变类的概念,回看《Java 核心技术》卷一第 5 章继承:不可变类

@Value
public class ValueTest {

    private String occupation;

    private String payment;

    private int age;

}

生成代码

public final class ValueTest {
    private final String occupation;
    private final String payment;
    private final int age;

    public ValueTest(String occupation, String payment, int age) {
        this.occupation = occupation;
        this.payment = payment;
        this.age = age;
    }

    public String getOccupation() {
        return this.occupation;
    }

    public String getPayment() {
        return this.payment;
    }

    public int getAge() {
        return this.age;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof ValueTest)) {
            return false;
        } else {
            ValueTest other = (ValueTest)o;
            if (this.getAge() != other.getAge()) {
                return false;
            } else {
                Object this$occupation = this.getOccupation();
                Object other$occupation = other.getOccupation();
                if (this$occupation == null) {
                    if (other$occupation != null) {
                        return false;
                    }
                } else if (!this$occupation.equals(other$occupation)) {
                    return false;
                }

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

                return true;
            }
        }
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        result = result * 59 + this.getAge();
        Object $occupation = this.getOccupation();
        result = result * 59 + ($occupation == null ? 43 : $occupation.hashCode());
        Object $payment = this.getPayment();
        result = result * 59 + ($payment == null ? 43 : $payment.hashCode());
        return result;
    }

    public String toString() {
        return "ValueTest(occupation=" + this.getOccupation() + ", payment=" + this.getPayment() + ", age=" + this.getAge() + ")";
    }
}

功能性注解

@NonNull

注解在属性上,会自动在所有设置此属性的语句前增加一个对赋的值的非空检查,如果参数为空,则抛出一个空指针异常

@Data
public class NonNullTest {

    private String occupation;

    @NonNull
    private String payment;

    private int age;

}

生成代码,可以看到为了保证参数不为空,生成了一个包含@NonNull 修饰的属性为参数的构造函数,同时构造函数中进行了非空校验,同时此属性的 setter 方法中也有非空校验

public class NonNullTest {
    private String occupation;
    private @NonNull String payment;
    private int age;

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

    public String getOccupation() {
        return this.occupation;
    }

    public @NonNull String getPayment() {
        return this.payment;
    }

    public int getAge() {
        return this.age;
    }

    public void setOccupation(String occupation) {
        this.occupation = occupation;
    }

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

    public void setAge(int age) {
        this.age = age;
    }

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

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

                return true;
            }
        }
    }

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

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        result = result * 59 + this.getAge();
        Object $occupation = this.getOccupation();
        result = result * 59 + ($occupation == null ? 43 : $occupation.hashCode());
        Object $payment = this.getPayment();
        result = result * 59 + ($payment == null ? 43 : $payment.hashCode());
        return result;
    }

    public String toString() {
        return "NonNullTest(occupation=" + this.getOccupation() + ", payment=" + this.getPayment() + ", age=" + this.getAge() + ")";
    }
}

注意,这个时候,生成的代码里是没有无参构造函数的,为了保证仍然有无参构造函数,需要添加额外添加 @NoArgsConstructor@RequiredArgsConstructor

@Data
@NoArgsConstructor
@RequiredArgsConstructor
public class NonNullTest1 {

    private String occupation;

    @NonNull
    private String payment;

    private int age;

}

也可以直接用来方法参数上,实现简单的非空校验,效果有点类似于 Bean Validation

@Data
public class NonNullTest2 {

    private String occupation;

    private String payment;

    private int age;

    public void info(@NonNull String name, @NonNull String score) {
        System.out.println(name + score);
    }

}

生成代码,不过只会比较 null,不会比较空字符串

public class NonNullTest2 {
    private String occupation;
    private String payment;
    private int age;

    public void info(@NonNull String name, @NonNull String score) {
        if (name == null) {
            throw new NullPointerException("name is marked non-null but is null");
        } else if (score == null) {
            throw new NullPointerException("score is marked non-null but is null");
        } else {
            System.out.println(name + score);
        }
    }

    public NonNullTest2() {
    }

    public String getOccupation() {
        return this.occupation;
    }

    public String getPayment() {
        return this.payment;
    }

    public int getAge() {
        return this.age;
    }

    public void setOccupation(String occupation) {
        this.occupation = occupation;
    }

    public void setPayment(String payment) {
        this.payment = payment;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

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

                return true;
            }
        }
    }

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

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        result = result * 59 + this.getAge();
        Object $occupation = this.getOccupation();
        result = result * 59 + ($occupation == null ? 43 : $occupation.hashCode());
        Object $payment = this.getPayment();
        result = result * 59 + ($payment == null ? 43 : $payment.hashCode());
        return result;
    }

    public String toString() {
        return "NonNullTest2(occupation=" + this.getOccupation() + ", payment=" + this.getPayment() + ", age=" + this.getAge() + ")";
    }
}

@Log4j/@Log4j2/@Slf4j/@XSlf4j

注解在上;为类提供一个属性名为 log 的 Log4j/Log4j2/Slf4j/XSlf4j 日志对象,具体说明类型的,取决于你用的是哪个注解,注意,使用注解之后,在类路径中必须有相应的日志框架包的依赖的引入才可以正常使用,否则会报错,比如使用@Log4j2,类路径中必须有 org.apache.logging.log4j.Logger,例子如下

引入 pom 依赖

<!--日志相关-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.17.2</version>
</dependency>

添加最基本的 log4j 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
    <properties>
        <property name="LOG_HOME">${sys:user.dir}\logs</property>
    </properties>
    <!--    <ThresholdFilter level="debug"/>-->

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <!-- 常规情况性能提升不大-->
            <!-- <Console name="Console" target="SYSTEM_OUT" direct="true">-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>

    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

编写测试代码

@Log4j2
@Data
public class Log4jTest {

    private String name;
    private int age;

    public void info() {
        log.info("Log4j2");
    }

    public static void main(String[] args) {
        Log4jTest test = new Log4jTest();
        test.info();
    }

}

实际生成代码

public class Log4jTest {
    private static final Logger log = LogManager.getLogger(Log4jTest.class);
    private String name;
    private int age;

    public void info() {
        log.info("Log4j");
    }

    public static void main(String[] args) {
        Log4jTest test = new Log4jTest();
        test.info();
    }

    public Log4jTest() {
    }

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

    public int getAge() {
        return this.age;
    }

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

    public void setAge(int age) {
        this.age = age;
    }

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

                return true;
            }
        }
    }

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

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

    public String toString() {
        return "Log4jTest(name=" + this.getName() + ", age=" + this.getAge() + ")";
    }
}

@UtilityClass

这个注解只能用在上,用于标记一个普通类是一个工具类,注意,@UtilityClass 修饰的类中不能有任何构造函数,否则在编译的时候就会生成一个错误 @UtilityClasses cannot have declared constructors.

使用这个标记后

最后,不要使用非星型静态导入来导入 @UtilityClass 修饰的类的静态属性;javac 编译器是无法分辨的。我们要么这么写 import static ThisType.*;,要么干脆不静态导入此类的静态属性。

@UtilityClass
public class UtilityClassTest {

    private String name;
    private String gender;

    public void getInfo() {
        System.out.println(name + gender);
    }
    public boolean isXiashuo() {
        return "xiashuo".equals(name);
    }

}

生成代码

public final class UtilityClassTest {
    private static String name;
    private static String gender;

    public static void getInfo() {
        System.out.println(name + gender);
    }

    public static boolean isXiashuo() {
        return "xiashuo".equals(name);
    }

    private UtilityClassTest() {
        throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
    }
}

@Cleanup

这个注解只能用在局部变量(比如方法内部变量)上,可以保证此变量代表的资源会被自动关闭,默认是调用变量的 close() 方法,如果该变量(资源)有其它关闭方法,可使用 @Cleanup(“methodName”) 来指定要调用的方法,常用于文件流的关闭。

其中用到了@SneakyThrows,是因为懒得写 try-catch 或者 throw Exception 了

@Data
public class CleanupTest {

    private String name;

    private int age;


    @SneakyThrows
    public void writeFile() {
        String fileName = System.getProperty("user.dir")+ File.separator +"LomBok"+File.separator +"cleanupTest.log";
        @Cleanup
        FileWriter writer = null;
        writer = new FileWriter(fileName);
        writer.write("Hello Kuka:\n");
        writer.write("My name is coolszy!\n");
        writer.write("I like you and miss you。");
    }

    public static void main(String[] args) {
        CleanupTest cleanupTest = new CleanupTest();
        cleanupTest.writeFile();
    }


}

生成代码

public class CleanupTest {
    private String name;
    private int age;

    public void writeFile() {
        try {
            String fileName = System.getProperty("user.dir") + File.separator + "LomBok" + File.separator + "cleanupTest.log";
            FileWriter writer = null;

            try {
                writer = new FileWriter(fileName);
                writer.write("Hello Kuka:\n");
                writer.write("My name is coolszy!\n");
                writer.write("I like you and miss you。");
            } finally {
                if (Collections.singletonList(writer).get(0) != null) {
                    writer.close();
                }

            }

        } catch (Throwable var7) {
            throw var7;
        }
    }

    public static void main(String[] args) {
        CleanupTest cleanupTest = new CleanupTest();
        cleanupTest.writeFile();
    }

    public CleanupTest() {
    }

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

    public int getAge() {
        return this.age;
    }

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

    public void setAge(int age) {
        this.age = age;
    }

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

                return true;
            }
        }
    }

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

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

    public String toString() {
        return "CleanupTest(name=" + this.getName() + ", age=" + this.getAge() + ")";
    }
}

@SneakyThrows

感觉就是省略了 try-catch 或者 throw Exception,用处不是很大

@Data
public class SneakyThrowsTest {
    private String name;


    @SneakyThrows(ArithmeticException.class)
    public void info() {
        System.out.println(1/0);
    }


    public static void main(String[] args) {
        SneakyThrowsTest test = new SneakyThrowsTest();
        test.info();
    }

}

生成代码

public class SneakyThrowsTest {
    private String name;

    public void info() {
        try {
            System.out.println(1 / 0);
        } catch (ArithmeticException var2) {
            throw var2;
        }
    }

    public static void main(String[] args) {
        SneakyThrowsTest test = new SneakyThrowsTest();
        test.info();
    }

    public SneakyThrowsTest() {
    }

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

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

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

                return true;
            }
        }
    }

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

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

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

@Synchronized

这个注解用在类方法(static 静态方法)或者实例方法上,效果和 synchronized 关键字相同,区别在于锁对象不同,对于类方法和实例方法,synchronized 关键字的锁对象分别是类的 class 对象和 this 对象,而 @Synchronized 的锁对象分别是私有 static final 对象 lock 和 私有 final 对象 lock,当然,也可以自己指定锁对象。确实非常的方便

@Data
public class SynchronizedTest {
    private Object myLock = new Object();

    @Synchronized
    public static void info() {
        System.out.println("");
    }

    @Synchronized
    public static void info1() {
        System.out.println("");
    }

    @Synchronized
    public  void show() {
        System.out.println("");
    }

    @Synchronized
    public  void show1() {
        System.out.println("");
    }

    @Synchronized("myLock")
    public  void myLockFunc() {
        System.out.println("");
    }

}

生成代码,其中静态的同步方法共用自动生成的锁对象 $LOCK,所以非静态同步方法共用锁对象 $lock

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

    public static void info() {
        synchronized($LOCK) {
            System.out.println("");
        }
    }

    public static void info1() {
        synchronized($LOCK) {
            System.out.println("");
        }
    }

    public void show() {
        synchronized(this.$lock) {
            System.out.println("");
        }
    }

    public void show1() {
        synchronized(this.$lock) {
            System.out.println("");
        }
    }

    public void myLockFunc() {
        synchronized(this.myLock) {
            System.out.println("");
        }
    }

    public SynchronizedTest() {
    }

    public Object getMyLock() {
        return this.myLock;
    }

    public void setMyLock(Object myLock) {
        this.myLock = myLock;
    }

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

                return true;
            }
        }
    }

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

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

    public String toString() {
        return "SynchronizedTest(myLock=" + this.getMyLock() + ")";
    }
}

构造器注解

@Builder

只能标注到类上,将生成类的一个当前流程的一种链式构造工厂,@Builder.Default() 可用于设置默认值,当你在构造的时候没有设置此属性的时候,改属性将使用此默认值。Collection 或者 Map 类型的属性可配合 @Singular 注解使用,@Singular 注解使用 Collection 类型、Map 类型的属性以及 Guavacom.google.common.collect 的属性上。未标注 @Singular 的属性,调用 setter 时,相当于为属性重新赋值,标注了 @Singular 的属性,则支持在集合中添加元素。

@Builder 可以跟@Data 一起使用

构造器模式的基本结构

在 XXX(POJO) 的内部创建一个 XXXBuilder 的静态内部类,其内部属性跟其外部类 XXX 一样,创建对象的时候,首先创建 XXXBuilder 对象,链式设置属性,最后调用 XXXBuilder 的 build 方法,创建 XXX 对象,可见,构造器模式的代码在 XXXBuilder 的内部,跟 XXX 本身的构造过程是隔离的。

链式调用

BuilderTest user = BuilderTest.builder().age(18).gender("男").occupation("Programmer").build();

测试代码

@Builder
@Data
public class BuilderTest {

    @Builder.Default()
    private String name = "xiashuo";
    private String gender;
    private int age;

    @Singular("occupation")
    private List<String> occupation;

}

生成代码

public class BuilderTest {
    private String name;
    private String gender;
    private int age;
    private List<String> occupation;

    public static void main(String[] args) {
        BuilderTest user = builder().age(18).gender("男").occupation("Programmer").build();
        System.out.println(user.toString());
    }

    private static String $default$name() {
        return "xiashuo";
    }

    BuilderTest(String name, String gender, int age, List<String> occupation) {
        this.name = name;
        this.gender = gender;
        this.age = age;
        this.occupation = occupation;
    }

    public static BuilderTestBuilder builder() {
        return new BuilderTestBuilder();
    }

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

    public String getGender() {
        return this.gender;
    }

    public int getAge() {
        return this.age;
    }

    public List<String> getOccupation() {
        return this.occupation;
    }

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

    public void setGender(String gender) {
        this.gender = gender;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setOccupation(List<String> occupation) {
        this.occupation = occupation;
    }

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

                    return false;
                }

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

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

                return true;
            }
        }
    }

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

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        result = result * 59 + this.getAge();
        Object $name = this.getName();
        result = result * 59 + ($name == null ? 43 : $name.hashCode());
        Object $gender = this.getGender();
        result = result * 59 + ($gender == null ? 43 : $gender.hashCode());
        Object $occupation = this.getOccupation();
        result = result * 59 + ($occupation == null ? 43 : $occupation.hashCode());
        return result;
    }

    public String toString() {
        return "BuilderTest(name=" + this.getName() + ", gender=" + this.getGender() + ", age=" + this.getAge() + ", occupation=" + this.getOccupation() + ")";
    }

    public static class BuilderTestBuilder {
        private boolean name$set;
        private String name$value;
        private String gender;
        private int age;
        private ArrayList<String> occupation;

        BuilderTestBuilder() {
        }

        public BuilderTestBuilder name(String name) {
            this.name$value = name;
            this.name$set = true;
            return this;
        }

        public BuilderTestBuilder gender(String gender) {
            this.gender = gender;
            return this;
        }

        public BuilderTestBuilder age(int age) {
            this.age = age;
            return this;
        }

        public BuilderTestBuilder occupation(String occupation) {
            if (this.occupation == null) {
                this.occupation = new ArrayList();
            }

            this.occupation.add(occupation);
            return this;
        }

        public BuilderTestBuilder occupation(Collection<? extends String> occupation) {
            if (occupation == null) {
                throw new NullPointerException("occupation cannot be null");
            } else {
                if (this.occupation == null) {
                    this.occupation = new ArrayList();
                }

                this.occupation.addAll(occupation);
                return this;
            }
        }

        public BuilderTestBuilder clearOccupation() {
            if (this.occupation != null) {
                this.occupation.clear();
            }

            return this;
        }

        public BuilderTest build() {
            List occupation;
            switch (this.occupation == null ? 0 : this.occupation.size()) {
                case 0:
                    occupation = Collections.emptyList();
                    break;
                case 1:
                    occupation = Collections.singletonList(this.occupation.get(0));
                    break;
                default:
                    occupation = Collections.unmodifiableList(new ArrayList(this.occupation));
            }

            String name$value = this.name$value;
            if (!this.name$set) {
                name$value = BuilderTest.$default$name();
            }

            return new BuilderTest(name$value, this.gender, this.age, occupation);
        }

        public String toString() {
            return "BuilderTest.BuilderTestBuilder(name$value=" + this.name$value + ", gender=" + this.gender + ", age=" + this.age + ", occupation=" + this.occupation + ")";
        }
    }
}

@Accessors

也是为了支持链式调用风格,不过不是构造器模式

@Accessors 批注用于配置 lombok 如何生成和查找 gettersetter。默认情况下,lombok 遵循针对 gettersetterbean 规范:例如,名为 Pepper 的字段的 gettergetPepper。但是,有些人可能希望打破 bean 规范,以得到更好看的 API@Accessors 允许您执行此操作。标注到类上,chain 属性设置为 true 时,类的所有属性的 setter 方法返回值将为 this,用来支持 setter 方法的链式写法

可标注在类或属性上,当然最实用的功能还是标注到类上。

这里就不多写了,以后用到了的时候再研究

原理 - Annotation Processing

JSR 269: Pluggable Annotation Processing API 之前,我们想要进行对类信息的获取和操作,只能在运行时通过反射进行,这样很慢,JSR 269 之后,我们可以在编译时期进行,而且在编译时期,我们可以通过语法树自己随心所欲地编辑源码。这也是 Lombok 插件实现的操作。

我们可以定义自己的注解,实现自己的注解处理工作。自定义注解处理分两个工程:注解处理实现工程 + 使用注解处理的工程,之所以是这种结构,本质上是因为底层还是 ServiceLoader 机制在生效。具体看《JavaSETips》ServiceLoader 小节

注解处理实现工程

pom 文件配置

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
                <compilerArgument>
                    -proc:none
                </compilerArgument>
                <source>8</source>
                <target>8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

创建注解和注解处理类

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface ShowMessage {

    boolean show() default true;

}
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("xyz.xiashuo.myAnnotation.*")
public class MyAnnotationProcessor extends AbstractProcessor {
    public MyAnnotationProcessor() {
        super();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element elem : roundEnv.getElementsAnnotatedWith(ShowMessage.class)) {
            ShowMessage annotation = elem.getAnnotation(ShowMessage.class);
            String message = "--------- annotation found in " + elem.getSimpleName() + " with  " + annotation.show() + " ---------";
            System.out.println(message);
            // 不知道这个信息写到哪里去了
            //Messager messager = processingEnv.getMessager();
            //messager.printMessage(Diagnostic.Kind.NOTE, message);
        }
        return true;
    }
}

src\main\resources\ 目录下新建 META-INF\services 目录,然后新建文件 javax.annotation.processing.Processor,内容为注解处理类的权限定性类名

xyz.xiashuo.myAnnotation.MyAnnotationProcessor

使用注解处理的工程

在 pom 文件中引入注解处理 jar

<dependency>
    <groupId>xyz.xiashuo</groupId>
    <artifactId>MyAnnotationProcessing</artifactId>
    <version>1.0-SNAPSHOT</version>
    <scope>compile</scope>
</dependency>

测试类

@ShowMessage
public class BeanTest {
}
public class Main {
    public static void main(String[] args) {
        BeanTest beanTest = new BeanTest();
        System.out.println("Hello world!");
    }
}

执行此工程 Maven 声明周期中的 compile

输出日志中,包含了注解处理的日志,表示注解处理成功

[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ MyAnnotationProcessingTest ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to E:\IDEAProject\CoreJava\MyAnnotationProcessingTest\target\classes
--------- annotation found in BeanTest with  true ---------

当然你可以跟 Lombok 一样,在这里添加代码

Lombok 中的配置过程也是一样的

我们主要看 Lombok 的 jar 包

看 AnnotationProcessorHider 的静态内部类 AnnotationProcessor

也实现了 process 方法

AnnotationProcessorHider 的静态内部类 ClaimingProcessor 就要简单一些

总结

优点

Lombok 实际上就是一个模板代码工具合集,通过注解来指定在什么地方,使用哪种模板代码。

查看 class 文件的时候需要注意,编译器会默认添加无参构造函数,这个不是 Lombok 实现的

从上面的例子可以看出,Lombok 确实可以节省大量的重复编码,而且由于是通过模板生成的代码,出现低级错误的概率也会降低。

Lombok 甚至可以用在内部类中

缺点

Lombok 的缺点就是,当别人的开发环境没有安装 Lombok 插件的时候,而且如果两个人使用的 Lombok 版本不一致,而其中一个人提交的代码中使用了高版本中才引入的新的注解,代码编译就会报错。所以 Lombok 实际上提升了协同开发的成本。

除此之外的问题我认为都不是很大的问题,比如 Getter/Setter 方法引起的问题,你可以手动实现,来覆盖 Lombok 生成的 getter/setter 方法,比如 Lombok 的升级问题,Lombok 本身不复杂,应该不至于跟不上 JDK 的版本升级,

省略代码可以提高效率,但是也会降低代码的可读性,我们需要在其中找到一个平衡,基础注解基本上能从注解名称直接联想到意思,可以常用,但是,像其他的,功能性注解,熟悉之后可以谨慎使用,表示复杂结构的,比如 @Delegate,应该少用.