Lombok使用详解

文章目录


lombok可以通过注解的方式在编译期动态的为类添加方法。

一、配置IDEA中使用lombok

1、导入lombok依赖包。

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

如果使用的时gradle构建的项目,不仅需要导入lombok依赖包,还需要指定其为编译期执行。

compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'

2、安装并启用lombok插件

Lombok使用详解

3、启用Annotation processing

File --> Settings --> Build, Execution, Deployment --> Compiler -->Annotation Processors --> Enable annotation processing(如图)
Lombok使用详解

二、lombok常用注解

No. 注解 描述
01 @Setter 为类的属性提供setter设置方法
02 @Getter 为类的属性提供getter获取方法
03 @ToString 提供toString()方法
04 @EqualsAndHashCode 提供equals()和hashCode()方法
05 @NoArgsConstructor 无参构造
06 @AllArgsConstructor 全参构造
07 @RequiredArgsConstructor 指定参数构造
08 @Cleanup 注解需要放在流的声明上,实现IO流资源关闭
09 @Data 相当于@ToString、@EqualsAndHashCode、@Getter以及所有非final字段的@Setter、@RequiredArgsConstructor
10 @Builder 建造者模式
11 @NonNull 避免NullPointException的异常产生
12 @Value 用于注解final类
13 @SneakyThrows 异常处理
14 @Synchronized 同步方法安全转化
15 @Log 支持各种logger对象,使用时用对应的注解,如:@Log4J、@Slf4j

1、@Data注解

新建一个Message类,定义3个属性。

import lombok.Data;

import java.util.Date;

@Data
public class Message {
    private Integer id;
    private String content;
    private Date pubDate;
}

通过反编译工具查看生成的.class文件:

public class Message {
    private Integer id;
    private String content;
    private Date pubDate;

    public Message() {
    }

    public Integer getId() {
        return this.id;
    }

    public String getContent() {
        return this.content;
    }

    public Date getPubDate() {
        return this.pubDate;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public void setPubDate(Date pubDate) {
        this.pubDate = pubDate;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof Message)) {
            return false;
        } else {
            Message other = (Message)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$content = this.getContent();
                Object other$content = other.getContent();
                if (this$content == null) {
                    if (other$content != null) {
                        return false;
                    }
                } else if (!this$content.equals(other$content)) {
                    return false;
                }

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

                return true;
            }
        }
    }

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

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        Object $id = this.getId();
        int result = result * 59 + ($id == null ? 43 : $id.hashCode());
        Object $content = this.getContent();
        result = result * 59 + ($content == null ? 43 : $content.hashCode());
        Object $pubDate = this.getPubDate();
        result = result * 59 + ($pubDate == null ? 43 : $pubDate.hashCode());
        return result;
    }

    public String toString() {
        Integer var10000 = this.getId();
        return "Message(id=" + var10000 + ", content=" + this.getContent() + ", pubDate=" + this.getPubDate() + ")";
    }
}

通过上面代码发现,只要在类上声明了@Data注解,就可以根据当前类所定义的属性来生成相应的setter、getter、toString()、equals()、hashCode()方法,值得注意的是,@Data注解并不会生成构造方法。

2、@NonNull注解

@NonNull使用在属性上,被其注解的属性不许为空。

import lombok.Data;
import lombok.NonNull;

import java.util.Date;

@Data
public class Message {
    @NonNull
    private Integer id;
    @NonNull
    private String content;
    private Date pubDate;
}

通过反编译工具查看生成的.class文件:

public class Message {
    @NonNull
    private Integer id;
    @NonNull
    private String content;
    private Date pubDate;

    public Message(@NonNull Integer id, @NonNull String content) {
        if (id == null) {
            throw new NullPointerException("id is marked non-null but is null");
        } else if (content == null) {
            throw new NullPointerException("content is marked non-null but is null");
        } else {
            this.id = id;
            this.content = content;
        }
    }

    @NonNull
    public Integer getId() {
        return this.id;
    }

    @NonNull
    public String getContent() {
        return this.content;
    }

    public Date getPubDate() {
        return this.pubDate;
    }

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

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

    public void setPubDate(Date pubDate) {
        this.pubDate = pubDate;
    }

	// equals、canEqual、hashCode、toString省略粘贴
}

此时发现反编译后生成了有参构造,构造方法的参数是@NonNull注解标注的属性,在构造方法和@NonNull注解标注的属性的set方法中都对属性进行了null校验。由于类中已经有了构造方法,那么便不会自动生成无参构造,如果想要生成无参构造,则需要在类上使用@NoArgsConstructor注解,但是使用无参构造生成对象则违背了@NonNull注解属性不为空的原则。

3、@RequiredArgsConstructor注解

在类上标注@RequiredArgsConstructor注解

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@RequiredArgsConstructor(staticName = "setNonNullField")
public class Message {
    @NonNull
    private Integer id;
    @NonNull
    private String content;
    private Date pubDate;
}

通过反编译工具查看生成的.class文件:

private Message(@NonNull Integer id, @NonNull String content) {
    if (id == null) {
        throw new NullPointerException("id is marked non-null but is null");
    } else if (content == null) {
        throw new NullPointerException("content is marked non-null but is null");
    } else {
        this.id = id;
        this.content = content;
    }
}

public static Message setNonNullField(@NonNull Integer id, @NonNull String content) {
    return new Message(id, content);
}

@RequiredArgsConstructor注解可以生成@NonNull注解所标注属性的私有构造,并且指定了static方法调用该私有构造构建对象。

4、@Builder注解

@Builder注解可以生成构建者模式代码。

import lombok.*;

import java.util.Date;

@Data
@Builder
public class Message {
    private Integer id;
    private String content;
    private Date pubDate;
}

通过反编译工具查看生成的.class文件:

import java.util.Date;

public class Message {
    private Integer id;
    private String content;
    private Date pubDate;

    Message(Integer id, String content, Date pubDate) {
        this.id = id;
        this.content = content;
        this.pubDate = pubDate;
    }

    public static Message.MessageBuilder builder() {
        return new Message.MessageBuilder();
    }

	// 其他方法省略...
	public static class MessageBuilder {
        private Integer id;
        private String content;
        private Date pubDate;

        MessageBuilder() {
        }

        public Message.MessageBuilder id(final Integer id) {
            this.id = id;
            return this;
        }

        public Message.MessageBuilder content(final String content) {
            this.content = content;
            return this;
        }

        public Message.MessageBuilder pubDate(final Date pubDate) {
            this.pubDate = pubDate;
            return this;
        }

        public Message build() {
            return new Message(this.id, this.content, this.pubDate);
        }

        public String toString() {
            return "Message.MessageBuilder(id=" + this.id + ", content=" + this.content + ", pubDate=" + this.pubDate + ")";
        }
    }
}

5、@Accessors注解

@Accessors注解可以生成访问器模式代码,同时对于访问器操作形式提供了三种不同的方案:fluent、chain、prefix。

5.1 fluent模式

import lombok.*;
import lombok.experimental.Accessors;

import java.util.Date;

@Data
@Accessors(fluent = true)
public class Message {
    private Integer id;
    private String content;
    private Date pubDate;
}

通过反编译工具查看生成的.class文件:

import java.util.Date;

public class Message {
    private Integer id;
    private String content;
    private Date pubDate;

    public Message() {
    }

    public Integer id() {
        return this.id;
    }

    public String content() {
        return this.content;
    }

    public Date pubDate() {
        return this.pubDate;
    }

    public Message id(Integer id) {
        this.id = id;
        return this;
    }

    public Message content(String content) {
        this.content = content;
        return this;
    }

    public Message pubDate(Date pubDate) {
        this.pubDate = pubDate;
        return this;
    }
    // 其他方法省略...
}

通过以上代码发现,设置为fluent模式后属性设置方法和属性获取方法的方法名全都变成了属性名,这样可以直接通过代码链的形式进行设置。

5.2 chain模式

import lombok.*;
import lombok.experimental.Accessors;

import java.util.Date;

@Data
@Accessors(chain = true)
public class Message {
    private Integer id;
    private String content;
    private Date pubDate;
}

通过反编译工具查看生成的.class文件:

import java.util.Date;

public class Message {
    private Integer id;
    private String content;
    private Date pubDate;

    public Message() {
    }

    public Integer getId() {
        return this.id;
    }

    public String getContent() {
        return this.content;
    }

    public Date getPubDate() {
        return this.pubDate;
    }

    public Message setId(final Integer id) {
        this.id = id;
        return this;
    }

    public Message setContent(final String content) {
        this.content = content;
        return this;
    }

    public Message setPubDate(final Date pubDate) {
        this.pubDate = pubDate;
        return this;
    }
	// 其他方法省略...
}

使用chain模式后发现生成了setter、getter方法,但是setter方法的返回值不再是void而是this,这样在属性设置时可以直接使用代码链的方式。

5.3 prefix模式

import lombok.*;
import lombok.experimental.Accessors;

import java.util.Date;

@Data
@Accessors(prefix = "it")
public class Message {
    private Integer itId;
    private String itContent;
    private Date itPubDate;
}

通过反编译工具查看生成的.class文件:

import java.util.Date;

public class Message {
    private Integer itId;
    private String itContent;
    private Date itPubDate;

    public Message() {
    }

    public Integer getId() {
        return this.itId;
    }

    public String getContent() {
        return this.itContent;
    }

    public Date getPubDate() {
        return this.itPubDate;
    }

    public void setId(final Integer itId) {
        this.itId = itId;
    }

    public void setContent(final String itContent) {
        this.itContent = itContent;
    }

    public void setPubDate(final Date itPubDate) {
        this.itPubDate = itPubDate;
    }

    // 其他方法省略...
}

使用chain模式后可以在setter、getter方法上去掉声明的属性前缀。

6、@Cleanup注解与@SneakyThrows注解

@Cleanup可以实现资源的自动关闭,@SneakyThrows注解会帮助程序手工的处理异常,二者经常配合使用。

import lombok.Cleanup;
import lombok.Data;
import lombok.NonNull;
import lombok.SneakyThrows;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

@Data
public class MessageRead { // 此时程序会生成双参构造
    @NonNull
    private String filePath;  // 文件路径
    @NonNull
    private String fileName;  // 文件名称

    @SneakyThrows //帮助用户手工处理异常
    public String load() { // 文件读取
        @Cleanup InputStream input = new FileInputStream(new File(filePath, fileName));
        byte[] data = new byte[1024];
        int len = input.read(data);
        return new String(data, 0, len);
    }
}

通过反编译工具查看生成的.class文件:

import java.io.File;
import java.io.FileInputStream;
import java.util.Collections;
import lombok.NonNull;

public class MessageRead {
    @NonNull
    private String filePath;
    @NonNull
    private String fileName;

    public String load() {
        try {
            FileInputStream input = new FileInputStream(new File(this.filePath, this.fileName));

            String var4;
            try {
                byte[] data = new byte[1024];
                int len = input.read(data);
                var4 = new String(data, 0, len);
            } finally {
                if (Collections.singletonList(input).get(0) != null) {
                    input.close();
                }

            }

            return var4;
        } catch (Throwable var9) {
            throw var9;
        }
    }

	// 其他方法省略...
}

通过以上编译后的.class文件发现,@SneakyThrows注解声名的方法编译后会生成try-catch代码块,@Cleanup注解则会生成try-finally代码块并在finally中关闭资源。

7、@Synchronized注解

@Synchronized注解声明在方法上可以自动生成synchronized代码块,synchronized代码块锁定的同步对象是lombok自动生成的Object数组。

import lombok.SneakyThrows;
import lombok.Synchronized;

public class TicketShop {

    private Integer ticket = 10;

    @SneakyThrows
    @Synchronized
    public void sale() {
        while (ticket > 0) {
            if (ticket > 0) {
                System.out.println("[" + Thread.currentThread().getName() + "] 剩余: " + ticket--);
            }
        }
    }
}

通过反编译工具查看生成的.class文件:

import java.io.PrintStream;

public class TicketShop {
    private final Object $lock = new Object[0];
    private Integer ticket = 10;

    public TicketShop() {
    }

    public void sale() {
        synchronized(this.$lock) {
            try {
                while(this.ticket > 0) {
                    if (this.ticket > 0) {
                        PrintStream var10000 = System.out;
                        String var10001 = Thread.currentThread().getName();
                        Integer var2 = this.ticket;
                        Integer var3 = this.ticket = this.ticket - 1;
                        var10000.println("[" + var10001 + "] 剩余: " + var2);
                    }
                }
            } catch (Throwable var5) {
                throw var5;
            }

        }
    }
}

虽然@Synchronized注解可以自动添加synchronized代码块,但是会存在死锁的隐患,不推荐使用在真实项目中。

上一篇:Spring Boot整合Lombok


下一篇:lombok 使用及技巧