Lombok简化编码使用方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38288606/article/details/80690827

Lombok简化编码使用方法


Lombok是一个通过注解的形式或简单关键字简化和消除Java应用程序中一些必须但是重复或显得臃肿的样板代码的实用工具,使用Lombok会在编译阶段根据相应的注解生成对应的字节码,使编译前的源码看起来更加简洁,但功能不变。


1.安装Lombok

Lombok需要通过插件的形式与IDE集成,如果使用IntelliJ IDEA可直接到插件仓库搜索Lombok进行安装,最后重启IDEA。


2.使用Lombok

要在项目中使用Lombok,首先要在项目中引入lombok的依赖,重新编译源代码

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


IDEA中使用Lombok的注意事项:
1.项目中要使用lombok 不仅IDEA要支持(否则一堆错误),项目中也要引入jar包
2.如果配置lombok.config文件,修改文件的属性值后,并不会自动重新编译class文件,IDEA编辑器也不会自动更新,所有每次修改配置文件后最后关闭java文件窗口重新打开,并且clean下项目

2.1基本特性

注解/关键字 可使用位置 说明
val 局部变量 简化局部变量声明的类型
@NonNull 字段、方法、入参、本地变量 生成检查NullPointException代码
@Cleanup 可关闭资源的本地变量对象,且销毁方法没有参数 简化资源清理回收的代码,消除try-catch-finally代码块
@Getter/@Setter 字段、枚举常量、接口、类、枚举、注解 简化getter、setter代码
@ToString 接口、类、枚举、注解 自动生成toString方法
@EqualsAndHashCode 接口、类、枚举、注解 自动生成equals方法和hashCode方法
@NoArgsConstructor 接口、类、枚举、注解 生成无参构造函数
@RequiredArgsConstructo 接口、类、枚举、注解 生成所有标识为@NonNull的成员属性的构造函数
@AllArgsConstructor 接口、类、枚举、注解 生成包含所有成员属性的构造函数
@Data 接口、类、枚举、注解 是@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstructor的组合效果
@Value 接口、类、枚举、注解 类似于@Data,区别在于字段会转换为final类型,且没有setter方法
@NonFinal 字段、方法、方法参数、本地变量、注解、接口、类、枚举 用来取消因使用@FieldDefaults和@Value而加上的final修饰符
@SneakyThrows 方法、构造函数 粗粒度的try-catch
@Synchronized 方法 作用等同于synchronized关键字,可自定义锁对象
@Log4j/@Slf4j/@Log 接口、类、枚举、注解 简化定义日志记录器对象的代码,根据日志框架的不同选择不同的Log注解
@Tolerate 方法、注解 解决某些情况下使用Lombok注解生成的构造器或方法与开发者自己写构造器或方法因为冲突而被跳过的情况
@Builder 会按builder模式生成一个内部类

2.1.1 val/var

val用来简化局部变量声明的类型,与Java10中的var关键字类似,都是从初始化表达式中推断出变量的声明类型,起到本地类型推断的作用。需要注意的是val修饰的变量都会变成final类型,其引用不可更改。

val example = new ArrayList<String>();
example.add("hello");
example.add("lombok");
val element = example.get(0);

等价于:

final ArrayList<String> example = new ArrayList<String>();
example.add("hello");
example.add("lombok");
final String element = example.get(0);
  • 注意:
  • 1.只能在本地变量声明的时候使用,不可在类的字段上使用
  • 2.val修饰的变量本身是final类型的,不能被修改

var与val关键字类似,同样起到本地类型推断的作用,区别在于var修饰的变量不会转变为final类型,而val修饰的变量都会变成final类型

2.1.2 @NonNull

常用于加在方法和构造函数的入参上,它会帮助我们生成检查NullPointerException的代码

public NonNullExample(@NonNull Person person) {
    this.name = person.getName();
}

等价于:

public NonNullExample(@NonNull Person person) {
    if(person == null) {
        throw new NullPointException("person");
    }
    this.name = person.getName();
}

2.1.3 @Cleanup

用来简化资源清理回收的代码,确保指定的资源在退出当前代码执行范围前进行自动清理,消除常见的try-catch-finally代码样板,作用等同于try-with-resource,不过需要注意@Cleanup只能指定没有参数的资源销毁方法,如果销毁方法有入参则不能使用@Cleanup注解

public static void tradition() {
        InputStream in = null;
        OutputStream out = null;
        try {
            in = new FileInputStream("test.txt");
            out = new FileOutputStream("output.txt");
            byte[] buffer = new byte[1024];
            int begin = 0;
            while (true) {
                int len = in.read(buffer);
                if (len == -1)
                    break;
                out.write(buffer, begin, len);
                begin += len;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void tryWithResource() {
        try (InputStream in = new FileInputStream("test.txt"); 
            OutputStream out = new FileOutputStream("output.txt")) {
            byte[] buffer = new byte[1024];
            int begin = 0;
            while (true) {
                int len = in.read(buffer);
                if (len == -1)
                    break;
                out.write(buffer, begin, len);
                begin += len;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void cleanUp() {
        try {
            @Cleanup InputStream in = new FileInputStream("test.txt");
            @Cleanup OutputStream out = new FileOutputStream("output.txt");
            byte[] buffer = new byte[1024];
            int begin = 0;
            while (true) {
                int len = in.read(buffer);
                if (len == -1)
                    break;
                out.write(buffer, begin, len);
                begin += len;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2.1.4 @Getter/@Setter

分别用来简化getter和setter样板代码,默认生成的getter、setter方法修饰符为public,如果需要指定方法的访问范围,可以设置AccessLevel属性,如:

@Getter @Setter(AccessLevel.PROTECTED) private String password;

另外,@Getter注解还有一个lazy=true的属性,设置了该属性会使我们调用getter方法时才真正去计算获取到的值,并且将第一次计算后的结果缓存下来,之后的调用直接返回该缓存值

@Getter(lazy = true)
private final double[] cached = expensive();

private double[] expensive() {
    long begin = System.currentTimeMillis();
    double[] result = new double[5];
    for (int i = 0; i < result.length; i++) {
        result[i] = Math.asin(i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println((System.currentTimeMillis() - begin) / 1000);
    return result;
}

public static void main(String[] args) {
    GetterLazyExample example = new GetterLazyExample();
    System.out.println(example.getCached());
    System.out.println(example.getCached());
}

等价于:

private final AtomicReference<Object> cached = new AtomicReference<>();

public double[] getCached() {
    Object value = this.cached.get();
    if (value == null) {
        synchronized (this.cached) {
            value = this.cached.get();
            if (value == null) {
                final double[] actualValue = expensive();
                value = actualValue == null ? this.cached : actualValue;
                this.cached.set(value);
            }
        }
    }
    return (double[]) (value == this.cached ? null : value);
}

private double[] expensive() {
    long begin = System.currentTimeMillis();
    double[] result = new double[5];
    for (int i = 0; i < result.length; i++) {
        result[i] = Math.asin(i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println((System.currentTimeMillis() - begin) / 1000);
    return result;
}

public static void main(String[] args) {
    GetterLazyExample_Src example = new GetterLazyExample_Src();
    System.out.println(example.getCached());
    System.out.println(example.getCached());
}

2.1.5 @ToString

用来自动生成toString方法,默认的toString方法会打印出类名和字段属性和值,如果需要排除指定字段可以用exclude=’字段名’的方式进行排除;如果要嵌套调用父类的toString方法,则加上callSuper=true,includeFieldNames=true等属性

// @ToString // 默认打印类名、每个字段名=值,用逗号分隔
// @ToString(exclude="password") //exclude属性指定排除哪些字段
@ToString(callSuper = true,includeFieldNames=true)
public class ToStringExample extends Parent {
    @Getter
    @Setter
    private String name;
    @Getter
    @Setter
    private String password;
    @Getter
    @Setter
    private int age;

    public static void main(String[] args) {
        System.out.println(new ToStringExample());
    }
}

@ToString
class Parent {
    @Getter
    @Setter
    private String address;
    @Getter
    @Setter
    private String city;
}

2.1.6 @EqualsAndHashCode

用来从字段中自动生成equals和hashCode方法,默认情况下使用的是所有非静态字段,也可以使用exclude属性排除指定的字段

@EqualsAndHashCode(exclude= {"name"})
public class EqualsAndHashCodeExample {

    @Getter @Setter private String name;
    @Getter @Setter private int age;
    @Getter @Setter private double weight;

    public static void main(String[] args) {
        EqualsAndHashCodeExample example1 = new EqualsAndHashCodeExample();
        example1.setName("小明");
        example1.setAge(10);

        EqualsAndHashCodeExample example2 = new EqualsAndHashCodeExample();

        example2.setName("小红");
        example2.setAge(10);

        System.out.println(example1.hashCode());
        System.out.println(example2.hashCode());
        System.out.println(example1.equals(example2));
    }
}

2.1.6 @NoArgsConstructor

用来生成无参构造函数,如果类含有final字段,会出现编译错误,通过指定属性force为true,为final字段进行初始化

@NoArgsConstructor
public class NoArgsConstructorExample {
    @Getter @Setter private String name;
}

等价于:

public class NoArgsConstructorExample {
    private String name;
    public NoArgsConstructorExample() {
        //public无参构造器
    }
    //省略getter、setter方法
    ......
}

2.1.7 @RequiredArgsConstructor

用来生成包含所有修饰为@NonNull的成员属性的构造函数

@RequiredArgsConstructor
public class RequiredArgsConstructorExample {
    @Getter @Setter @NonNull private String name;
    @Getter @Setter private String password;
    @Getter @Setter @NonNull private Character sex;
}

等价于:

public class RequiredArgsConstructorExample {
    private String name;
    private String password;
    private Character sex;

    private RequiredArgsConstructorExample(String name, Character sex) {
        if(name == null) {
            throw new NullPointerException("name");
        }
        if(sex == null) {
            throw new NullPointerException("sex");
        }
        this.name = name;
        this.sex = sex;
    }
    //省略getter、setter方法
    ......
}

2.1.8 @AllArgsConstructor

@AllArgsConstructor
public class AllArgsContructorExample {
    @Getter @Setter private String name;
    @Getter @Setter private Integer age;
    @Getter @Setter private String address;
}

等价于:

public class AllArgsContructorExample {
    private String name;
    private Integer age;
    private String address;

    public AllArgsContructorExample(String name, Integer age, String address) {
        this.name = name,
        this.age = age;
        this.address = address;
    }
    //省略getter、setter方法
    ......
}

2.1.9 @Data

是一个简单粗暴的组合注解,使用@Data注解相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstructor这几个注解

@Data
public class DataExample {
    private String name;
    private int age;
    private String password;

}

2.1.10 @Value

跟@Data类似,区别在于如果变量不加@NonFinal修饰,@Value会将字段变成final类型,同时也没有setter方法

2.1.11 @NonFinal

修饰字段,用来取消因使用@FieldDefaults和@Value而加上的final修饰符

@Value
public class NonFinalExample {
    private String id; //final
    private String name; //final
    @NonFinal private String password; //非final
}

2.1.12 @Builder

简化了普通的建造者模式API,可以用在类、构造器、方法上,如果字段属于集合类型,加上@Singular,会生成两个向集合中添加单一元素和所有元素的方法,以及一个清除集合的方法

@Builder
 public class Example {
        private int foo;
        private final String bar;
 }

等价于:

public class Example<T> {
    private T foo;
    private final String bar;

    private Example(T foo, String bar) {
            this.foo = foo;
            this.bar = bar;
    }

    public static <T> ExampleBuilder<T> builder() {
            return new ExampleBuilder<T>();
    }

    public static class ExampleBuilder<T> {
            private T foo;
            private String bar;

            private ExampleBuilder() {}

            public ExampleBuilder foo(T foo) {
                    this.foo = foo;
                    return this;
            }

            public ExampleBuilder bar(String bar) {
                    this.bar = bar;
                    return this;
            }

            @java.lang.Override 
            public String toString() {
                    return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")";
            }

            public Example build() {
                    return new Example(foo, bar);
            }
    }
}

2.1.13 @SneakyThrows

注解用在方法和构造函数上,它会将方法中的所有代码用try-catch语句包裹起来,当捕获到异常后通过Lombok.sneakyThrow(e)将原始异常抛出,不过需要注意的是调用该方法的Client端并不知道会抛出哪种异常,即使这是一个CheckException

public class SneakyThrowsExample {

    @SneakyThrows(UnsupportedEncodingException.class)
    public static String utf8ToString(byte[] bytes) {
        return new String(bytes, "UTF-8");
    }

    public static void main(String[] args) {
        String str = SneakyThrowsExample.utf8ToString("hello lomboks".getBytes());
        System.out.println(str);
    }
}

2.1.14 @Synchronized

注解用在方法上,作用等同于synchronized关键字,区别在于锁对象不同,对于synchronized关键字,修饰类方法时锁对象是class对象,修饰成员方法时锁对象是this对象,而使用@synchronized注解时锁对象分别是私有静态变量LOCK和私有final对象lock,也可以自己指定锁对象

public class SynchronizedExample {
    private final Object readLock = new Object();

    @Synchronized("readLock")
    @SneakyThrows
    public void read() {
        System.out.println(Thread.currentThread().getName() + " read");
        Thread.sleep(3000);
    }

    public static void main(String[] args) {
        SynchronizedExample example = new SynchronizedExample();
        new Thread(()->example.read()).start();
        new Thread(()->example.read()).start();
    }
}

2.1.15 Log注解:@CommonsLog、@Log、@Log4j、@Log4j2、@Slf4j、@XSl4j、@JBossLog

Log注解可以省去从日志工厂生成日志记录器对象的代码,可以使用topic指定生成log对象时的类名,根据项目中使用的日志框架不同,有不同的注解可以选择

@CommonsLog(topic="LogExample")
//等价于
org.apache.commons.logging.LogFactory.getLog(LogExample.class);

@Log(topic="LogExample")
//等价于
java.util.loggin.Logger.getLogger(LogExample.class);

@Log4j(topic="LogExample")
//等价于
org.apache.log4j.Logger.getLogger(LogExample.class);

@Log4j2(topic="LogExample")
//等价于
org.apache.loggin.log4j.LogManager.getLoggerr(LogExample.class);

@Slf4j(topic="LogExample")
//等价于
org.slf4j.LoggerFactory.getLogger(LogExample.class);

@XSLf4j(topic="LogExample") 
//等价于
org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);

@JBossLog(topic="LogExample") 
//等价于
org.jboss.logging.Logger.getLogger(LogExample.class);

2.1.16 @Tolerate

该注解用来解决某些情况下使用Lombok注解生成的构造器或方法与开发者自己写构造器或方法因为冲突而被跳过的情况,将@Tolerate修饰在构造器/方法上,会被lombok视为该构造器/方法不存在,典型的如当@Data和@Builder同时使用时Lombok生成构造器只有一个包含所有成员属性的构造函数,如果再自定义一个无参构造函数将会冲突,此时可以使用@Tolerate解决

@Data
@Builder
public class TolerateExample {

    private String name;
    private String age;

    @Tolerate
    public TolerateExample() {
    }

}

2.1.17 @UtilityClass

创建工具类的注释,当在类上加上该注解,该类会被修饰为final类型,如果该类声明了构造函数编译器将会提示错误,否则会自动生成一个私有的构造函数,内部抛出一个UnsupportedOperationException异常。并且所有的方法、内部类和属性都会被修饰为static

@UtilityClass
public class UtilityClassExample {
    private DateFormat df = new SimpleDateFormat("YYYY-MM-DD");

    public String formateToDay(Date date) {
        return df.format(date);
    }
}

等价于:

public class UtilityClassExample {
    private static DateFormat df = new SimpleDateFormat("YYYY-MM-DD");

    public static String formateToDay(Date date) {
        return df.format(date);
    }

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

}

除了以上常用的基本功能外,Lombok还有部分实验性质的特性没有正式推荐使用,有些可能违背了对Java的常规认知或者只支持部分开发环境,所以不推荐使用

2.2 扩展

2.2.1 lombok.config增加

lombok.equalsAndHashCode.doNotUseGetters = [true | false] (default:false)
如果设置为 true,lombok将直接访问字段,而不是在生成equals和hashcode方法时使用getter(如果可用),可以在该注解上配置属性 donotusegetter 来标示不使用getter的字段,这样可以覆盖默认配置

lombok.equalsAndHashCode.callSuper = [call | skip | warn] (default:warn)
如果设置为 call,lombok将生成对hashCode的超类实现的调用
如果设置为 skip,则不会生成这样的调用,默认行为 warn 类似于 skip,并带有附加警告。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页