Lombok使用指南

Lombok使用指南_第1张图片

Lombok使用分享

一个例子

在面向对象编程中必不可少需要在代码中定义对象模型,而在基于Java的业务平台开发实践中尤其如此。相信大家在平时开发中也深有感触,本来是没有多少代码开发量的,但是因为定义的业务模型对象比较多,而需要重复Getter/Setter、构造器方法、字符串输出的ToString方法和Equals/HashCode方法等。那么是否一款插件或工具能够替大家完成这些繁琐的操作呢?

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

    public static class Builder {
        private String name;
        private int age;

        public Builder() {
        }

        public Builder name(String name) {
            this.name = name;
            return this;
        }

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

        public Person build() {
            return new Person(this.name, this.age);
        }
    }

    private Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

@Builder
public class PersonUseLombok {
    private String name;
    private int age;
}

Lombok简介

Lombok官网对其进行的解释:
Lombok使用指南_第2张图片

Project Lombok是一个可以自动插入你的编辑器和构建工具的java库。

Lombok安装和引入依赖

IDEA中安装插件

  1. 在线安装

打开IDEA的Setting –> 选择Plugins选项 –> 选择MarketPlace –> 搜索lombok –> 点击安装 –> 安装完成重启IDEA –> 安装成功
Lombok使用指南_第3张图片
2. 离线安装

选择Install Plugin from Disk,安装从IDEA官网下载的离线包。

引入依赖

<dependencies>
	<dependency>
		<groupId>org.projectlombokgroupId>
		<artifactId>lombokartifactId>
		<version>1.18.6version>
		<scope>providedscope>
	dependency>
dependencies>

Delombok

lombok官方提供了delombok工具,在Maven工程,在pom.xml文件中加入lombok-maven-plugin插件。生成的代码在target/generate-sources/delombok目录下。

            <plugin>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombok-maven-pluginartifactId>
                <version>1.18.6.0version>
                <executions>
                    <execution>
                        <phase>generate-sourcesphase>
                        <goals>
                            <goal>delombokgoal>
                        goals>
                        <configuration>
                            <addOutputDirectory>falseaddOutputDirectory>
                        configuration>
                    execution>
                executions>
            plugin>

Lombok使用

val

可以使用val作为本地变量的声明类型,来代替实际写入类型。执行此操作时,将会从初始化表达式推断出具体类型,局部变量也被声明为final

注意:此功能仅适用于局部变量和foreach循环,而不适用于***成员变量***。

import lombok.val;
public class ExampleVal {
    val name ="heankang";//报错,不能成员变量
    private void example() {
        val name ="heankang";//不报错
    }
}
import java.util.ArrayList;
import java.util.HashMap;
import lombok.val;

public class ValExample {
  public String example() {
    //相当于final ArrayList example = new ArrayList();
    val example = new ArrayList<String>();
    example.add("Hello, World!");
    //相当于 final String foo = example.get(0);
    val foo = example.get(0); 
    return foo.toLowerCase();
  }
  
  public void example2() {
    //相当于final HashMap map = new HashMap();
    val map = new HashMap<Integer, String>();
    map.put(0, "zero");
    map.put(5, "five");
    //相当于final Map.Entry entry : map.entrySet()
    for (val entry : map.entrySet()){
      System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
    }
  }
}

var

除了局部变量没有标记为final,var和val完全一样。

@Cleanup

该注解使用在属性前,该注解是用来保证分配的资源被释放。在本地变量上使用该注解,任何后续代码都将封装在try/finally中,确保当前作用于中的资源被释放。默认@Cleanup清理的方法为close(),可以使用注解的value指定不同的无参方法名称@Cleanup(“xxxx”)

import lombok.Cleanup;
import java.io.*;

public class CleanupExample {
  public static void main(String[] args) throws IOException {
    @Cleanup InputStream in = new FileInputStream(args[0]);
    @Cleanup OutputStream out = new FileOutputStream(args[1]);
    byte[] b = new byte[10000];
    while (true) {
      int r = in.read(b);
      if (r == -1) break;
      out.write(b, 0, r);
    }
  }
}
import java.io.*;

public class CleanupExample {
  public static void main(String[] args) throws IOException {
    InputStream in = new FileInputStream(args[0]);
    try {
      OutputStream out = new FileOutputStream(args[1]);
      try {
        byte[] b = new byte[10000];
        while (true) {
          int r = in.read(b);
          if (r == -1) break;
          out.write(b, 0, r);
        }
      } finally {
        if (out != null) {
          out.close();
        }
      }
    } finally {
      if (in != null) {
        in.close();
      }
    }
  }
}
  • @Cleanup 不能调用有参清理函数,需要调用有参清理函数需要手工操作

  • @Cleanup 如果清理方法抛出异常,它将抢占抛出方法体内的任何异常。这可能导致问题的实际原因被掩埋,因此在选择使用Project Lombok的资源管理时应该考虑这个问题

    **推荐使用try-with-resources**

import java.io.*;

public class CleanupByTryWithResources {
    public static void main(String[] args) throws IOException {
        try (InputStream in = new FileInputStream(args[0]);
             OutputStream out = new FileOutputStream(args[1])) {
            byte[] b = new byte[10000];
            while (true) {
                int r = in.read(b);
                if (r == -1) break;
                out.write(b, 0, r);
            }
        }
    }
}

@Getter/@Setter

该注解使用在类或者属性上,为字段生成GetterSetter方法。

  • 注解在类上会为类中的所有非静态成员变量生成GetterSetter方法
  • 非boolean类型和boolean类型:都生成getXxxxxsetXxxx,可以用is+大写开头的Boolean类型
  • 如果类和字段上均使用相同的注解,字段上的注解会覆盖相同的注解,以字段上的注解为准
  • 手工编写的Getter和Setter方法会抑制lombok中相同名称的Getter和Setter方法的生成。
  • 可以通过AccessLevel.NONE禁用某个字段上的@Setter、@Getter

import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class GetterSetterExample {
  private int age = 10;
  @Getter(AccessLevel.NONE)@Setter(AccessLevel.PROTECTED) private String name;
}
public class GetterSetterExample {

  private int age = 10;
  private String name;
  //自动生成Getter、Setter
  public int getAge() {
    return age;
  } 
  public void setAge(int age) {
    this.age = age;
  }

  //属性优先级高于类
  protected void setName(String name) {
    this.name = name;
  }
}

@ToString

生成toString()方法,默认情况下,它会以逗号分隔,按顺序,打印类名称以及每个非静态成员变量。

  • 如果需要可以通过注释参数includeFieldNames来控制输出中是否包含的属性名称。
  • 通过@ToString.Exclude注解某一字段,可以从生成的方法中排除特定字段。
  • 也可使用@ToString(onlyExplicitlyIncluded = true)标注类,然后使用@ToString.Include标注想要输入的字段。
  • 通过@ToString(callSuper = true)参数继承父类的输出。
import lombok.ToString;

@ToString
public class ToStringExample {
  private static final int STATIC_VAR = 10;
  private String name;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  @ToString.Exclude private int id;
  
  public String getName() {
    return this.name;
  }
   //继承父类,排除属性名
  @ToString(callSuper=true, includeFieldNames=false)
  public static class Square extends Shape {
    private final int width, height;
    
    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
  }
}
public class ToStringExample {
    private static final int STATIC_VAR = 10;
    private String name;
    private Shape shape = new Square(5, 10);
    private String[] tags;
    private int id;

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

    private static class Square extends Shape {
        private final int width;
        private final int height;

        private Square(int width, int height) {
            this.width = width;
            this.height = height;
        }

        @Override
        public java.lang.String toString() {
            //继承父类,排除属性名
            return "ToStringExample.Square(super=" + super.toString() + ", " + this.width + ", " + this.height + ")";
        }
    }

    @Override
    public java.lang.String toString() {
        //exclude "id"
        return "ToStringExample(name=" + this.getName() + ", shape=" + this.shape + ", tags=" + java.util.Arrays.deepToString(this.tags) + ")";
    }
}

@EqualsAndHashCode

任何类使用@EqualsAndHashCode标注生成hashCode()和equals()方法,默认情况下,它将使用所有非静态,非transient字段。但可以通过在可选的@EqualsAndHashCode.Include 或者@EqualsAndHashCode.Exclude注解字段来排除或包含指定字段

类似于@toString

  • @EqualsAndHashCode.Exclude排除具体字段
  • @EqualsAndHashCode.Include包含指定字段,需和属性onlyExplicitlyIncluded = true配合使用
  • 通过callSuper = true继承父类。
import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class EqualsAndHashCodeExample {
  private transient int transientVar = 10;
  private String name;
  private double score;
  @EqualsAndHashCode.Exclude private Shape shape = new Square(5, 10);
  private String[] tags;
  @EqualsAndHashCode.Exclude private int id;
  
  public String getName() {
    return this.name;
  }
  
  @EqualsAndHashCode(callSuper=true)
  public static class Square extends Shape {
    private final int width, height;
    
    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
  }
}
import java.util.Arrays;

public class EqualsAndHashCodeExample {
  private transient int transientVar = 10;
  private String name;
  private double score;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  private int id;
  
  public String getName() {
    return this.name;
  }
  
  @Override public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof EqualsAndHashCodeExample)) return false;
    EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o;
    if (!other.canEqual((Object)this)) return false;
    if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
    if (Double.compare(this.score, other.score) != 0) return false;
    if (!Arrays.deepEquals(this.tags, other.tags)) return false;
    return true;
  }
  
  @Override public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final long temp1 = Double.doubleToLongBits(this.score);
    result = (result*PRIME) + (this.name == null ? 43 : this.name.hashCode());
    result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
    result = (result*PRIME) + Arrays.deepHashCode(this.tags);
    return result;
  }
  
  protected boolean canEqual(Object other) {
    return other instanceof EqualsAndHashCodeExample;
  }
  
  public static class Square extends Shape {
    private final int width, height;
    
    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
    
    @Override public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof Square)) return false;
      Square other = (Square) o;
      if (!other.canEqual((Object)this)) return false;
      if (!super.equals(o)) return false;
      if (this.width != other.width) return false;
      if (this.height != other.height) return false;
      return true;
    }
    
    @Override public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      result = (result*PRIME) + super.hashCode();
      result = (result*PRIME) + this.width;
      result = (result*PRIME) + this.height;
      return result;
    }
    
    protected boolean canEqual(Object other) {
      return other instanceof Square;
    }
  }
}

@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor

给类增加无参构造器、指定参数的构造器、包含所有参数的构造器

  • @NoArgsConstructor:使用在类上,提供一个无参构造器。当类中有final字段没有被初始化时,编译器会报错,此时可用@NoArgsConstructor(force = true),然后就会为没有初始化的final字段设置默认值 0 / false / null, 这样编译器就不会报错。对于具有约束的字段(例如@NonNull字段),不会生成检查或分配,因此请注意,正确初始化这些字段之前,这些约束无效。
  • @RequiredArgsConstructor:使用在类上,生成一个有参构造函数,其中每个参数对应为类中所有带有 @NonNull 注解的和以final修饰的未经初始化的字段。
  • @RequiredArgsConstructor(staticName = “of”)会生成一个of()的静态工厂方法,并把构造方法设置为私有的。
  • @AllArgsConstructor:使用在类上,该注解提供一个全参数的构造方法,默认不提供无参构造。 这里的全参不包括已初始化的final字段。
  • 这三个注解都会忽略static变量。
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.NonNull;

@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample<T> {
    private int x, y;
    private final String name;
    @NonNull
    private T description;
    private static int count;
    @NoArgsConstructor
    public static class NoArgsExample {
        @NonNull
        private String field;
    }
}
// Generated by delombok at Mon Feb 25 09:40:52 CST 2019
package com.zybank.lombokdemo.entity;

import lombok.*;

public class ConstructorExample<T> {
    private int x;
    private int y;
    private final String name;
    @NonNull
    private T description;
    private static int count;


    public static class NoArgsExample {
        @NonNull
        private String field;

        @java.lang.SuppressWarnings("all")
        public NoArgsExample() {
        }
    }

    private ConstructorExample(final String name, @NonNull final T description) {
        if (description == null) {
            throw new java.lang.NullPointerException("description is marked @NonNull but is null");
        }
        this.name = name;
        this.description = description;
    }

    public static <T> ConstructorExample<T> of(final String name, @NonNull final T description) {
        return new ConstructorExample<T>(name, description);
    }

    protected ConstructorExample(final int x, final int y, final String name, @NonNull final T description) {
        if (description == null) {
            throw new java.lang.NullPointerException("description is marked @NonNull but is null");
        }
        this.x = x;
        this.y = y;
        this.name = name;
        this.description = description;
    }
}

@Data

@Data 包含了 @ToString@EqualsAndHashCode@Getter /@Setter@RequiredArgsConstructor的功能。

  • 虽然@Data注解非常有用,但是它的控制粒度较粗(Class级别),而且@Data不能设置callSuper, includeFieldNamesexclude等,如果需要改变默认值,可以使用相应的注解,@Data会自动遵循这些注解。

  • @Data提供了一个可以生成静态工厂的单一参数@Data(staticConstructor=”of”),会生成一个of()的静态工厂方法,并把构造方法设置为私有的。

package com.zybank.lombokdemo;

import lombok.*;
@ToString(includeFieldNames=false)
//生成静态工厂
@Data(staticConstructor = "of")
public class DataExample {
    private final String name;
    //自定义Setter方法
    @Setter(AccessLevel.PACKAGE)
    private int age;
    private double score;
    private String[] tags;
}
// Generated by delombok at Mon Feb 25 16:27:59 CST 2019
package com.zybank.lombokdemo;

import lombok.*;

public class DataExample {
    private final String name;
    private int age;
    private double score;
    private String[] tags;

    @java.lang.Override
    public java.lang.String toString() {
        return "DataExample(" + this.getName() + ", " + this.getAge() + ", " + this.getScore() + ", " + java.util.Arrays.deepToString(this.getTags()) + ")";
    }

    private DataExample(final String name) {
        this.name = name;
    }

    public static DataExample of(final String name) {
        return new DataExample(name);
    }

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

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

    @java.lang.SuppressWarnings("all")
    public double getScore() {
        return this.score;
    }

    public String[] getTags() {
        return this.tags;
    }

    public void setScore(final double score) {
        this.score = score;
    }
    
    void setAge(final int age) {
        this.age = age;
    }
   
    public void setTags(final String[] tags) {
        this.tags = tags;
    }
   

    @java.lang.Override
    public boolean equals(final java.lang.Object o) {
        if (o == this) return true;
        if (!(o instanceof DataExample)) return false;
        final DataExample other = (DataExample) o;
        if (!other.canEqual((java.lang.Object) this)) return false;
        final java.lang.Object this$name = this.getName();
        final java.lang.Object other$name = other.getName();
        if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
        if (this.getAge() != other.getAge()) return false;
        if (java.lang.Double.compare(this.getScore(), other.getScore()) != 0) return false;
        if (!java.util.Arrays.deepEquals(this.getTags(), other.getTags())) return false;
        return true;
    }

    protected boolean canEqual(final java.lang.Object other) {
        return other instanceof DataExample;
    }

    @java.lang.Override
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final java.lang.Object $name = this.getName();
        result = result * PRIME + ($name == null ? 43 : $name.hashCode());
        result = result * PRIME + this.getAge();
        final long $score = java.lang.Double.doubleToLongBits(this.getScore());
        result = result * PRIME + (int) ($score >>> 32 ^ $score);
        result = result * PRIME + java.util.Arrays.deepHashCode(this.getTags());
        return result;
    }
}

@Value

该注解用于修饰类,是@Data的不可变形式,生成immutable Class。字段都被修饰为privatefinal,默认的情况下不会生成settter,默认类本身也是final的。
实际上@Value等价于final @ToString @EqualsAndHashCode @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter

import lombok.AccessLevel;
import lombok.experimental.NonFinal;
import lombok.experimental.Wither;
import lombok.ToString;
import lombok.Value;

@Value public class ValueExample {
  String name;
  @Wither(AccessLevel.PACKAGE) @NonFinal int age;
  double score;
  protected String[] tags;
  
  @ToString(includeFieldNames=true)
  @Value(staticConstructor="of")
  public static class Exercise<T> {
    String name;
    T value;
  }
}
import java.util.Arrays;

public final class ValueExample {
  private final String name;
  private int age;
  private final double score;
  protected final String[] tags;
  
  @java.beans.ConstructorProperties({"name", "age", "score", "tags"})
  public ValueExample(String name, int age, double score, String[] tags) {
    this.name = name;
    this.age = age;
    this.score = score;
    this.tags = tags;
  }
  
  public String getName() {
    return this.name;
  }
  
  public int getAge() {
    return this.age;
  }
  
  public double getScore() {
    return this.score;
  }
  
  public String[] getTags() {
    return this.tags;
  }
  
  @java.lang.Override
  public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof ValueExample)) return false;
    final ValueExample other = (ValueExample)o;
    final Object this$name = this.getName();
    final Object other$name = other.getName();
    if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
    if (this.getAge() != other.getAge()) return false;
    if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
    if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
    return true;
  }
  
  @java.lang.Override
  public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final Object $name = this.getName();
    result = result * PRIME + ($name == null ? 43 : $name.hashCode());
    result = result * PRIME + this.getAge();
    final long $score = Double.doubleToLongBits(this.getScore());
    result = result * PRIME + (int)($score >>> 32 ^ $score);
    result = result * PRIME + Arrays.deepHashCode(this.getTags());
    return result;
  }
  
  @java.lang.Override
  public String toString() {
    return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")";
  }
  
  ValueExample withAge(int age) {
    return this.age == age ? this : new ValueExample(name, age, score, tags);
  }
  
  public static final class Exercise<T> {
    private final String name;
    private final T value;
    
    private Exercise(String name, T value) {
      this.name = name;
      this.value = value;
    }
    
    public static <T> Exercise<T> of(String name, T value) {
      return new Exercise<T>(name, value);
    }
    
    public String getName() {
      return this.name;
    }
    
    public T getValue() {
      return this.value;
    }
    
    @java.lang.Override
    public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof ValueExample.Exercise)) return false;
      final Exercise<?> other = (Exercise<?>)o;
      final Object this$name = this.getName();
      final Object other$name = other.getName();
      if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
      final Object this$value = this.getValue();
      final Object other$value = other.getValue();
      if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false;
      return true;
    }
    
    @java.lang.Override
    public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      final Object $name = this.getName();
      result = result * PRIME + ($name == null ? 43 : $name.hashCode());
      final Object $value = this.getValue();
      result = result * PRIME + ($value == null ? 43 : $value.hashCode());
      return result;
    }
    
    @java.lang.Override
    public String toString() {
      return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")";
    }
  }
}

@NonNull

使用@NonNull注解标注方法或构造函数的参数,让lombok为您生成null-check语句。

配合lomok中其他生成完整方法和构造函数的注解(比如@Setter注解),仅需将@NonNull标注在成员变量上,就可在生成的方法和构造函数中加入null-check。反之,就必须在入参中标注。

lombok会探测开发者自己编写的null-check语句,如果存在就不在生成。if条件语句必须严格按照PARAMNAME == null的形式书写。

import lombok.NonNull;

public class NonNullExample {
    private String name;

    public void setName(@NonNull String name) {
        this.name = name;
    }
}
public class NonNullExample {
    private String name;

    public void setName(String name) {
        if (name == null) {
            throw new NullPointerException("name is marked @NonNull but is null");
        }
        this.name = name;
    }
}

@Builder

@Builder注释为你的类生成复杂的构建器API,它把我们的Bean类包装为一个构建者模式,编译时增加了一个Builder内部类和全字段的构造器。

import lombok.Builder;
import lombok.Singular;
import java.util.Set;

@Builder
public class BuilderExample {
  @Builder.Default private long created = System.currentTimeMillis();
  private String name;
  private int age;
  @Singular private Set<String> occupations;
}
import java.util.Set;

public class BuilderExample {
  private long created;
  private String name;
  private int age;
  private Set<String> occupations;
  
  BuilderExample(String name, int age, Set<String> occupations) {
    this.name = name;
    this.age = age;
    this.occupations = occupations;
  }
  
  private static long $default$created() {
    return System.currentTimeMillis();
  }
  
  public static BuilderExampleBuilder builder() {
    return new BuilderExampleBuilder();
  }
  
  public static class BuilderExampleBuilder {
    private long created;
    private boolean created$set;
    private String name;
    private int age;
    private java.util.ArrayList<String> occupations;
    
    BuilderExampleBuilder() {
    }
    
    public BuilderExampleBuilder created(long created) {
      this.created = created;
      this.created$set = true;
      return this;
    }
    
    public BuilderExampleBuilder name(String name) {
      this.name = name;
      return this;
    }
    
    public BuilderExampleBuilder age(int age) {
      this.age = age;
      return this;
    }
    
    public BuilderExampleBuilder occupation(String occupation) {
      if (this.occupations == null) {
        this.occupations = new java.util.ArrayList<String>();
      }
      
      this.occupations.add(occupation);
      return this;
    }
    
    public BuilderExampleBuilder occupations(Collection<? extends String> occupations) {
      if (this.occupations == null) {
        this.occupations = new java.util.ArrayList<String>();
      }

      this.occupations.addAll(occupations);
      return this;
    }
    
    public BuilderExampleBuilder clearOccupations() {
      if (this.occupations != null) {
        this.occupations.clear();
      }
      
      return this;
    }

    public BuilderExample build() {
      Set<String> occupations = ...;
      return new BuilderExample(created$set ? created : BuilderExample.$default$created(), name, age, occupations);
    }
    
    @java.lang.Override
    public String toString() {
      return "BuilderExample.BuilderExampleBuilder(created = " + this.created + ", name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")";
    }
  }
}

@SneakyThrows

该注解的作用是将检查异常包装为运行时异常,那么编码时就无需处理异常了。自动抛受检异常, 而无需显式在方法上使用throws语句。把checked异常转化为unchecked异常,不再向上层方法抛出。

package com.zybank.lombokdemo.entity;
import lombok.SneakyThrows;
import java.io.UnsupportedEncodingException;

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

    @SneakyThrows
    public void run() {
        throw new Throwable();
    }
}

Delombok:

public class SneakyThrowsExample implements Runnable {
  public String utf8ToString(byte[] bytes) {
    try {
      return new String(bytes, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw Lombok.sneakyThrow(e);
    }
  }
  
  public void run() {
    try {
      throw new Throwable();
    } catch (Throwable t) {
      throw Lombok.sneakyThrow(t);
    }
  }
}

@Synchronized

这个注解用在类方法(static修饰)或者实例方法上,效果和synchronized关键字相同,区别在于锁对象不同,对于类方法和实例方法,synchronized关键字的锁对象分别是类的class对象和this对象,而@Synchronized得锁对象分别是私有静态final对象 L O C K 和 私 有 f i n a l 对 象 LOCK和私有final对象 LOCKfinallock(如果字段不存在,则会创建),当然,也可以自己指定锁对象

给方法加上同步锁,如果直接指定了value=xxx,其中xxx为类的一个成员,那么该方法使用该成员xxx作为加锁对象,放在同步块中执行

import lombok.Synchronized;

public class SynchronizedExample {
  private final Object readLock = new Object();
  
  @Synchronized
  public static void hello() {
    System.out.println("world");
  }
  
  @Synchronized
  public int answerToLife() {
    return 42;
  }
  
  @Synchronized("readLock")
  public void foo() {
    System.out.println("bar");
  }
}

Delombok:

public class SynchronizedExample {
  private static final Object $LOCK = new Object[0];
  private final Object $lock = new Object[0];
  private final Object readLock = new Object();
  
  public static void hello() {
    synchronized($LOCK) {
      System.out.println("world");
    }
  }
  
  public int answerToLife() {
    synchronized($lock) {
      return 42;
    }
  }
  
  public void foo() {
    synchronized(readLock) {
      System.out.println("bar");
    }
  }
}

@Getter(lazy=true)

标注字段为懒加载字段,懒加载字段在创建对象时不会进行真正的初始化,而是在第一次访问的时候才会初始化,后面再次访问也不会重复初始化。

import lombok.Getter;

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;
  }
}

delombok: Double-checked locking and the Singleton pattern

public class GetterLazyExample {
  private final java.util.concurrent.AtomicReference<java.lang.Object> cached = new    java.util.concurrent.AtomicReference<java.lang.Object>();
  
  public double[] getCached() {
    java.lang.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() {
    double[] result = new double[1000000];
    for (int i = 0; i < result.length; i++) {
      result[i] = Math.asin(i);
    }
    return result;
  }
}

@Log

@log用在类上,可以省去从日志工厂生成日志对象这一步,直接进行日志记录,具体注解根据日志工具的不同而不同,具体如下:

@CommonsLog

Creates private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);

@Flogger

Creates private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass();

@JBossLog

Creates private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);

@Log

Creates private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());

@Log4j

Creates private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);

@Log4j2

Creates private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);

@Slf4j

Creates private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);

@XSlf4j

Creates private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);

简单日记门面(simple logging Facade for java)SLF4J是为各种loging APIs提供一个简单统一的接口,从而使得最终用户能够在部署的时候配置自己希望的loging APIs实现。

slf4j为不同的日志框架,提供了适配工具方便与slf4j门面连接:
Lombok使用指南_第4张图片
同样,为存在多种日志框架的复杂大型项目中也提供了解决方法:

  1. 将系统中其它日志框架删除掉;

  2. 用中间包来替换原有的日志框架;

  3. 导入slf4j的其他实现
    Lombok使用指南_第5张图片
    图片来自slf4j官网

  4. 将系统中其它日志框架删除掉;

  5. 用中间包来替换原有的日志框架;

  6. 导入slf4j的其他实现

工作原理

要了解Project Lombok的工作原理,首先必须了解Java编译的工作原理。 OpenJDK提供了编译过程的完美概述。换句话说,编译有3个阶段:

  1. 解析和输入
  2. 注释处理
  3. 分析和生成

img

在Parse和Enter阶段,编译器将源文件解析为抽象语法树(AST)。将AST视为Java代码的DOM等价物。如果语法无效,解析只会抛出错误。在阶段3中检查诸如无效类或方法使用之类的编译错误。

在注释处理阶段,调用自定义注释处理器。这被认为是预编译阶段。注释处理器可以执行诸如验证类或生成新资源(包括源文件)之类的操作。注释处理器可能会生成错误,导致编译过程失败。如果由于注释处理而生成新的源文件,则编译将循环回Parse和Enter阶段,并重复该过程,直到不生成新的源文件。

在最后阶段,分析和生成,编译器从阶段1中生成的抽象语法树生成类文件(字节代码)。作为此过程的一部分,将分析AST是否存在损坏的引用(例如:未找到类,未找到方法)检查有效流(例如没有无法到达的语句),执行类型擦除,解除语法糖(例如,增强循环变为迭代器循环),最后,如果一切都成功,则写出Class文件。

Project Lombok正是依靠可插件化的Java自定义注解处理API(JSR 269: Pluggable Annotation Processing API)来实现在Javac编译阶段利用“Annotation Processor”对自定义的注解进行预处理后生成真正在JVM上面执行的“Class文件”。类似的还有AutoValue和Immutables。

Lombok使用指南_第6张图片

从上面的Lombok执行的流程图中可以看出,在Javac解析成AST抽象语法树之后, Lombok 根据自己编写的注解处理器,动态地修改 AST,增加新的节点(即Lombok自定义注解所需要生成的代码),最终通过分析生成JVM可执行的字节码Class文件。如需要更加深入理解Project Lombok的技术细节,请阅读源码。

讨论

Lombok Project 是一款强大的工具,他提供一系列的注解去减少大量样版(Boilerplate)代码的编写。简单的一个注解,有时甚至能减少上百行代码。使Java类变得干净、整洁、易于维护。

Lombok Project一直以来也伴随着不少争议,Don’t use Lombok、Is it safe to use Project Lombok?中,进行了诸多讨论。主要有以下几方面问题:

  • 自身bug,功能改动
  • 依赖非公开API,对compiler版本的兼容。
  • 团队协作

Reference:

Project Lombok

Project Lombok - Trick Explained

slf4j

Java开发神器Lombok的使用与原理

Reducing Boilerplate Code with Project Lombok

你可能感兴趣的:(Java)