关于java:还在用LombokSpringBoot和IDEA官方都要敬它三分

39次阅读

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

摘要

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

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: %sn", 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: %sn", 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.
 */
@ToString
public 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
@EqualsAndHashCode
public 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")
@AllArgsConstructor
public 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.
 */
@Data
public 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.
 */
@Value
public 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
@ToString
public 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.
 */
@Data
public 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
@AllArgsConstructor
public 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.
 */
@Log
public 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.
 */
@Slf4j
public 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 代码的性能加强的。

参考资料

《2020 最新 Java 根底精讲视频教程和学习路线!》

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

链接:https://juejin.cn/post/691147…

正文完
 0