最近IDEA 2020最初一个版本公布了,曾经内置了Lombok插件,SpringBoot 2.1.x之后的版本也在Starter中内置了Lombok依赖。为什么他们都要反对Lombok呢?明天我来讲讲Lombok的应用,看看它有何神奇之处!

SpringBoot实战电商我的项目mall(40k+star)地址:https://github.com/macrozheng/mall

Lombok简介

Lombok是一款Java代码性能加强库,在Github上已有9.8k+Star。它会主动集成到你的编辑器和构建工具中,从而使你的Java代码更加生动有趣。通过Lombok的注解,你能够不必再写getter、setter、equals等办法,Lombok将在编译时为你主动生成。

Lombok集成

首先咱们须要在IDEA中装置好Lombok插件,如果你应用的是最新版IDEA 2020.3,则Lombok插件曾经内置,无需装置。

之后在我的项目的pom.xml文件中增加Lombok依赖,SpringBoot 2.1.x版本后无需指定Lombok版本,SpringBoot在spring-boot-dependencies中曾经内置。

<!--lombok依赖--><dependency>    <groupId>org.projectlombok</groupId>    <artifactId>lombok</artifactId>    <optional>true</optional></dependency>

Lombok应用

Lombok中有很多注解,这些注解使得咱们能够更加不便的编写Java代码,上面介绍下这些注解的应用。

val

应用val注解能够取代任意类型作为局部变量,这样咱们就不必写简单的ArrayList和Map.Entry类型了,具体例子如下。

/** * Created by macro on 2020/12/16. */public class ValExample {    public static void example() {        //val代替ArrayList<String>和String类型        val example = new ArrayList<String>();        example.add("Hello World!");        val foo = example.get(0);        System.out.println(foo.toLowerCase());    }    public static void example2() {        //val代替Map.Entry<Integer,String>类型        val map = new HashMap<Integer, String>();        map.put(0, "zero");        map.put(5, "five");        for (val entry : map.entrySet()) {            System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());        }    }    public static void main(String[] args) {        example();        example2();    }}

当咱们应用了val注解后,Lombok会从局部变量的初始化表达式推断出具体类型,编译后会生成如下代码。

public class ValExample {    public ValExample() {    }    public static void example() {        ArrayList<String> example = new ArrayList();        example.add("Hello World!");        String foo = (String)example.get(0);        System.out.println(foo.toLowerCase());    }    public static void example2() {        HashMap<Integer, String> map = new HashMap();        map.put(0, "zero");        map.put(5, "five");        Iterator var1 = map.entrySet().iterator();        while(var1.hasNext()) {            Entry<Integer, String> entry = (Entry)var1.next();            System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());        }    }}

@NonNull

在办法上应用@NonNull注解能够做非空判断,如果传入空值的话会间接抛出NullPointerException。

/** * Created by macro on 2020/12/16. */public class NonNullExample {    private String name;    public NonNullExample(@NonNull String name){        this.name = name;    }    public static void main(String[] args) {        new NonNullExample("test");        //会抛出NullPointerException        new NonNullExample(null);    }}

编译后会在结构器中增加非空判断,具体代码如下。

public class NonNullExample {    private String name;    public NonNullExample(@NonNull String name) {        if (name == null) {            throw new NullPointerException("name is marked non-null but is null");        } else {            this.name = name;        }    }    public static void main(String[] args) {        new NonNullExample("test");        new NonNullExample((String)null);    }}

@Cleanup

当咱们在Java中应用资源时,不可避免地须要在应用后敞开资源。应用@Cleanup注解能够主动敞开资源。

/** * Created by macro on 2020/12/16. */public class CleanupExample {    public static void main(String[] args) throws IOException {        String inStr = "Hello World!";        //应用输入输出流主动敞开,无需编写try catch和调用close()办法        @Cleanup ByteArrayInputStream in = new ByteArrayInputStream(inStr.getBytes("UTF-8"));        @Cleanup ByteArrayOutputStream out = new ByteArrayOutputStream();        byte[] b = new byte[1024];        while (true) {            int r = in.read(b);            if (r == -1) break;            out.write(b, 0, r);        }        String outStr = out.toString("UTF-8");        System.out.println(outStr);    }}

编译后Lombok会生成如下代码。

public class CleanupExample {    public CleanupExample() {    }    public static void main(String[] args) throws IOException {        String inStr = "Hello World!";        ByteArrayInputStream in = new ByteArrayInputStream(inStr.getBytes("UTF-8"));        try {            ByteArrayOutputStream out = new ByteArrayOutputStream();            try {                byte[] b = new byte[1024];                while(true) {                    int r = in.read(b);                    if (r == -1) {                        String outStr = out.toString("UTF-8");                        System.out.println(outStr);                        return;                    }                    out.write(b, 0, r);                }            } finally {                if (Collections.singletonList(out).get(0) != null) {                    out.close();                }            }        } finally {            if (Collections.singletonList(in).get(0) != null) {                in.close();            }        }    }}

@Getter/@Setter

有了@Getter/@Setter注解,咱们再也不必编写getter/setter办法了。试想下之前即便咱们应用IDEA主动生成getter/setter办法,如果类属性的类型和名称改了,又要从新生成getter/setter办法也是一件很麻烦的事件。

/** * Created by macro on 2020/12/17. */public class GetterSetterExample {    @Getter    @Setter    private String name;    @Getter    @Setter(AccessLevel.PROTECTED)    private Integer age;    public static void main(String[] args) {        GetterSetterExample example = new GetterSetterExample();        example.setName("test");        example.setAge(20);        System.out.printf("name:%s age:%d",example.getName(),example.getAge());    }}

编译后Lombok会生成如下代码。

public class GetterSetterExample {    private String name;    private Integer age;    public GetterSetterExample() {    }    public String getName() {        return this.name;    }    public void setName(final String name) {        this.name = name;    }    public Integer getAge() {        return this.age;    }    protected void setAge(final Integer age) {        this.age = age;    }}

@ToString

把所有类属性都编写到toString办法中不便打印日志,是一件如许枯燥无味的事件。应用@ToString注解能够主动生成toString办法,默认会蕴含所有类属性,应用@ToString.Exclude注解能够排除属性的生成。

/** * Created by macro on 2020/12/17. */@ToStringpublic class ToStringExample {    @ToString.Exclude    private Long id;    private String name;    private Integer age;    public ToStringExample(Long id,String name,Integer age){        this.id =id;        this.name = name;        this.age = age;    }    public static void main(String[] args) {        ToStringExample example = new ToStringExample(1L,"test",20);        //主动实现toString办法,输入ToStringExample(name=test, age=20)        System.out.println(example);    }}

编译后Lombok会生成如下代码。

public class ToStringExample {    private Long id;    private String name;    private Integer age;    public ToStringExample(Long id, String name, Integer age) {        this.id = id;        this.name = name;        this.age = age;    }    public String toString() {        return "ToStringExample(name=" + this.name + ", age=" + this.age + ")";    }}

@EqualsAndHashCode

应用@EqualsAndHashCode注解能够主动生成hashCode和equals办法,默认蕴含所有类属性,应用@EqualsAndHashCode.Exclude能够排除属性的生成。

/** * Created by macro on 2020/12/17. */@Getter@Setter@EqualsAndHashCodepublic class EqualsAndHashCodeExample {    private Long id;    @EqualsAndHashCode.Exclude    private String name;    @EqualsAndHashCode.Exclude    private Integer age;    public static void main(String[] args) {        EqualsAndHashCodeExample example1 = new EqualsAndHashCodeExample();        example1.setId(1L);        example1.setName("test");        example1.setAge(20);        EqualsAndHashCodeExample example2 = new EqualsAndHashCodeExample();        example2.setId(1L);        //equals办法只比照id,返回true        System.out.println(example1.equals(example2));    }}

编译后Lombok会生成如下代码。

public class EqualsAndHashCodeExample {    private Long id;    private String name;    private Integer age;    public EqualsAndHashCodeExample() {    }    public boolean equals(final Object o) {        if (o == this) {            return true;        } else if (!(o instanceof EqualsAndHashCodeExample)) {            return false;        } else {            EqualsAndHashCodeExample other = (EqualsAndHashCodeExample)o;            if (!other.canEqual(this)) {                return false;            } else {                Object this$id = this.getId();                Object other$id = other.getId();                if (this$id == null) {                    if (other$id != null) {                        return false;                    }                } else if (!this$id.equals(other$id)) {                    return false;                }                return true;            }        }    }    protected boolean canEqual(final Object other) {        return other instanceof EqualsAndHashCodeExample;    }    public int hashCode() {        int PRIME = true;        int result = 1;        Object $id = this.getId();        int result = result * 59 + ($id == null ? 43 : $id.hashCode());        return result;    }}

@XxConstructor

应用@XxConstructor注解能够主动生成构造方法,有@NoArgsConstructor、@RequiredArgsConstructor和@AllArgsConstructor三个注解能够应用。

  • @NoArgsConstructor:生成无参构造函数。
  • @RequiredArgsConstructor:生成蕴含必须参数的构造函数,应用@NonNull注解的类属性为必须参数。
  • @AllArgsConstructor:生成蕴含所有参数的构造函数。
/** * Created by macro on 2020/12/17. */@NoArgsConstructor@RequiredArgsConstructor(staticName = "of")@AllArgsConstructorpublic class ConstructorExample {    @NonNull    private Long id;    private String name;    private Integer age;    public static void main(String[] args) {        //无参结构器        ConstructorExample example1 = new ConstructorExample();        //全副参数结构器        ConstructorExample example2 = new ConstructorExample(1L,"test",20);        //@NonNull注解的必须参数结构器        ConstructorExample example3 = ConstructorExample.of(1L);    }}

编译后Lombok会生成如下代码。

public class ConstructorExample {    @NonNull    private Long id;    private String name;    private Integer age;    public ConstructorExample() {    }    private ConstructorExample(@NonNull final Long id) {        if (id == null) {            throw new NullPointerException("id is marked non-null but is null");        } else {            this.id = id;        }    }    public static ConstructorExample of(@NonNull final Long id) {        return new ConstructorExample(id);    }    public ConstructorExample(@NonNull final Long id, final String name, final Integer age) {        if (id == null) {            throw new NullPointerException("id is marked non-null but is null");        } else {            this.id = id;            this.name = name;            this.age = age;        }    }}

@Data

@Data是一个方便使用的组合注解,是@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstructor的组合体。

/** * Created by macro on 2020/12/17. */@Datapublic class DataExample {    @NonNull    private Long id;    @EqualsAndHashCode.Exclude    private String name;    @EqualsAndHashCode.Exclude    private Integer age;    public static void main(String[] args) {        //@RequiredArgsConstructor已失效        DataExample example1 = new DataExample(1L);        //@Getter @Setter已失效        example1.setName("test");        example1.setAge(20);        //@ToString已失效        System.out.println(example1);        DataExample example2 = new DataExample(1L);        //@EqualsAndHashCode已失效        System.out.println(example1.equals(example2));    }}

编译后Lombok会生成如下代码。

public class DataExample {    @NonNull    private Long id;    private String name;    private Integer age;    public DataExample(@NonNull final Long id) {        if (id == null) {            throw new NullPointerException("id is marked non-null but is null");        } else {            this.id = id;        }    }    @NonNull    public Long getId() {        return this.id;    }    public String getName() {        return this.name;    }    public Integer getAge() {        return this.age;    }    public void setId(@NonNull final Long id) {        if (id == null) {            throw new NullPointerException("id is marked non-null but is null");        } else {            this.id = id;        }    }    public void setName(final String name) {        this.name = name;    }    public void setAge(final Integer age) {        this.age = age;    }    public boolean equals(final Object o) {        if (o == this) {            return true;        } else if (!(o instanceof DataExample)) {            return false;        } else {            DataExample other = (DataExample)o;            if (!other.canEqual(this)) {                return false;            } else {                Object this$id = this.getId();                Object other$id = other.getId();                if (this$id == null) {                    if (other$id != null) {                        return false;                    }                } else if (!this$id.equals(other$id)) {                    return false;                }                return true;            }        }    }    protected boolean canEqual(final Object other) {        return other instanceof DataExample;    }    public int hashCode() {        int PRIME = true;        int result = 1;        Object $id = this.getId();        int result = result * 59 + ($id == null ? 43 : $id.hashCode());        return result;    }    public String toString() {        return "DataExample(id=" + this.getId() + ", name=" + this.getName() + ", age=" + this.getAge() + ")";    }}

@Value

应用@Value注解能够把类申明为不可变的,申明后此类相当于final类,无奈被继承,其属性也会变成final属性。

/** * Created by macro on 2020/12/17. */@Valuepublic class ValueExample {    private Long id;    private String name;    private Integer age;    public static void main(String[] args) {        //只能应用全参结构器        ValueExample example = new ValueExample(1L,"test",20);        // example.setName("andy") //没有生成setter办法,会报错        // example.name="andy" //字段被设置为final类型,会报错    }}

编译后Lombok会生成如下代码。

public final class ValueExample {    private final Long id;    private final String name;    private final Integer age;    public static void main(String[] args) {        new ValueExample(1L, "test", 20);    }    public ValueExample(final Long id, final String name, final Integer age) {        this.id = id;        this.name = name;        this.age = age;    }    public Long getId() {        return this.id;    }    public String getName() {        return this.name;    }    public Integer getAge() {        return this.age;    }}

@Builder

应用@Builder注解能够通过建造者模式来创建对象,建造者模式加链式调用,创建对象太不便了!

/** * Created by macro on 2020/12/17. */@Builder@ToStringpublic class BuilderExample {    private Long id;    private String name;    private Integer age;    public static void main(String[] args) {        BuilderExample example = BuilderExample.builder()                .id(1L)                .name("test")                .age(20)                .build();        System.out.println(example);    }}

编译后Lombok会生成如下代码。

public class BuilderExample {    private Long id;    private String name;    private Integer age;    BuilderExample(final Long id, final String name, final Integer age) {        this.id = id;        this.name = name;        this.age = age;    }    public static BuilderExample.BuilderExampleBuilder builder() {        return new BuilderExample.BuilderExampleBuilder();    }    public String toString() {        return "BuilderExample(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")";    }    public static class BuilderExampleBuilder {        private Long id;        private String name;        private Integer age;        BuilderExampleBuilder() {        }        public BuilderExample.BuilderExampleBuilder id(final Long id) {            this.id = id;            return this;        }        public BuilderExample.BuilderExampleBuilder name(final String name) {            this.name = name;            return this;        }        public BuilderExample.BuilderExampleBuilder age(final Integer age) {            this.age = age;            return this;        }        public BuilderExample build() {            return new BuilderExample(this.id, this.name, this.age);        }        public String toString() {            return "BuilderExample.BuilderExampleBuilder(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")";        }    }}

@SneakyThrows

还在手动捕捉并抛出异样?应用@SneakyThrows注解主动实现试试!

/** * Created by macro on 2020/12/17. */public class SneakyThrowsExample {    //主动抛出异样,无需解决    @SneakyThrows(UnsupportedEncodingException.class)    public static byte[] str2byte(String str){        return str.getBytes("UTF-8");    }    public static void main(String[] args) {        String str = "Hello World!";        System.out.println(str2byte(str).length);    }}

编译后Lombok会生成如下代码。

public class SneakyThrowsExample {    public SneakyThrowsExample() {    }    public static byte[] str2byte(String str) {        try {            return str.getBytes("UTF-8");        } catch (UnsupportedEncodingException var2) {            throw var2;        }    }}

@Synchronized

当咱们在多个线程中拜访同一资源时,往往会呈现线程平安问题,以前咱们往往应用synchronized关键字润饰办法来实现同步拜访。应用@Synchronized注解同样能够实现同步拜访。

package com.macro.mall.tiny.example;import lombok.*;/** * Created by macro on 2020/12/17. */@Datapublic class SynchronizedExample {    @NonNull    private Integer count;    @Synchronized    @SneakyThrows    public void reduceCount(Integer id) {        if (count > 0) {            Thread.sleep(500);            count--;            System.out.println(String.format("thread-%d count:%d", id, count));        }    }    public static void main(String[] args) {        //增加@Synchronized三个线程能够同步调用reduceCount办法        SynchronizedExample example = new SynchronizedExample(20);        new ReduceThread(1, example).start();        new ReduceThread(2, example).start();        new ReduceThread(3, example).start();    }    @RequiredArgsConstructor    static class ReduceThread extends Thread {        @NonNull        private Integer id;        @NonNull        private SynchronizedExample example;        @Override        public void run() {            while (example.getCount() > 0) {                example.reduceCount(id);            }        }    }}

编译后Lombok会生成如下代码。

public class SynchronizedExample {    private final Object $lock = new Object[0];    @NonNull    private Integer count;    public void reduceCount(Integer id) {        try {            synchronized(this.$lock) {                if (this.count > 0) {                    Thread.sleep(500L);                    Integer var3 = this.count;                    Integer var4 = this.count = this.count - 1;                    System.out.println(String.format("thread-%d count:%d", id, this.count));                }            }        } catch (Throwable var7) {            throw var7;        }    }}

@With

应用@With注解能够实现对原对象进行克隆,并扭转其一个属性,应用时须要指定全参构造方法。

@With@AllArgsConstructorpublic class WithExample {    private Long id;    private String name;    private Integer age;    public static void main(String[] args) {        WithExample example1 = new WithExample(1L, "test", 20);        WithExample example2 = example1.withAge(22);        //将原对象进行clone并设置age,返回false        System.out.println(example1.equals(example2));    }}

编译后Lombok会生成如下代码。

public class WithExample {    private Long id;    private String name;    private Integer age;    public WithExample withId(final Long id) {        return this.id == id ? this : new WithExample(id, this.name, this.age);    }    public WithExample withName(final String name) {        return this.name == name ? this : new WithExample(this.id, name, this.age);    }    public WithExample withAge(final Integer age) {        return this.age == age ? this : new WithExample(this.id, this.name, age);    }    public WithExample(final Long id, final String name, final Integer age) {        this.id = id;        this.name = name;        this.age = age;    }}

@Getter(lazy=true)

当咱们获取某一个属性比拟耗费资源时,能够给@Getter增加lazy=true属性实现懒加载,会生成Double Check Lock 样板代码对属性进行懒加载。

/** * Created by macro on 2020/12/17. */public class GetterLazyExample {    @Getter(lazy = true)    private final double[] cached = expensive();    private double[] expensive() {        double[] result = new double[1000000];        for (int i = 0; i < result.length; i++) {            result[i] = Math.asin(i);        }        return result;    }    public static void main(String[] args) {        //应用Double Check Lock 样板代码对属性进行懒加载        GetterLazyExample example = new GetterLazyExample();        System.out.println(example.getCached().length);    }}

编译后Lombok会生成如下代码。

public class GetterLazyExample {    private final AtomicReference<Object> cached = new AtomicReference();    public GetterLazyExample() {    }    private double[] expensive() {        double[] result = new double[1000000];        for(int i = 0; i < result.length; ++i) {            result[i] = Math.asin((double)i);        }        return result;    }    public double[] getCached() {        Object value = this.cached.get();        if (value == null) {            synchronized(this.cached) {                value = this.cached.get();                if (value == null) {                    double[] actualValue = this.expensive();                    value = actualValue == null ? this.cached : actualValue;                    this.cached.set(value);                }            }        }        return (double[])((double[])(value == this.cached ? null : value));    }}

@Log

应用@Log注解,能够间接生成日志对象log,通过log对象能够间接打印日志。

/** * Created by macro on 2020/12/17. */@Logpublic class LogExample {    public static void main(String[] args) {        log.info("level info");        log.warning("level warning");        log.severe("level severe");    }}

编译后Lombok会生成如下代码。

public class LogExample {    private static final Logger log = Logger.getLogger(LogExample.class.getName());    public LogExample() {    }    public static void main(String[] args) {        log.info("level info");        log.warning("level warning");        log.severe("level severe");    }}

@Slf4j

应用Lombok生成日志对象时,依据应用日志实现的不同,有多种注解能够应用。比方@Log、@Log4j、@Log4j2、@Slf4j等。

/** * Created by macro on 2020/12/17. */@Slf4jpublic class LogSlf4jExample {    public static void main(String[] args) {        log.info("level:{}","info");        log.warn("level:{}","warn");        log.error("level:{}", "error");    }}

编译后Lombok会生成如下代码。

public class LogSlf4jExample {    private static final Logger log = LoggerFactory.getLogger(LogSlf4jExample.class);    public LogSlf4jExample() {    }    public static void main(String[] args) {        log.info("level:{}", "info");        log.warn("level:{}", "warn");        log.error("level:{}", "error");    }}

Lombok原理

如果IDEA不装置Lombok插件的话,咱们关上应用Lombok的我的项目是无奈通过编译的。装了当前IDEA才会提醒咱们Lombok为咱们生成的办法和属性。

应用了@Data注解当前,查看类构造能够发现getter、setter、toString等办法。

关上target目录下的.class文件,咱们能够看到Lombok为咱们生成的代码,可见Lombok是通过解析注解,而后在编译时生成代码来实现Java代码的性能加强的。

参考资料

官网文档:https://projectlombok.org/fea...

我的项目源码地址

https://github.com/macrozheng...

本文 GitHub https://github.com/macrozheng/mall-learning 曾经收录,欢送大家Star!