減少代碼開發工作的Java庫lombok及註解的使用學習

前言

Laziness is a virtue!

每當寫pojo類時,都會重復寫一些setter/getter/toString方法等大量的模版代碼,無聊繁瑣卻又不得不做,這會讓這個類變得又臭又長,卻沒有多少實質的東西

Lombok是什麼

Lombok是一個旨在減少代碼開發工作的Java庫。它提供瞭一些簡單的註解,並以此來消除java中臃腫的模版代碼,比如 pojo 中最常見的 setter/getter 方法, 比如 toString 方法, 比如 equals 方法等等,還可以幫助我們關閉流,即使 JDK7 中已經有瞭 TWR 特性,但這個包很值得一試。

通過幾個簡單的註解,將模版代碼在編譯時寫入程序。使用 eclipse 可以在 Outline 窗口看到生成的方法,但是在源碼裡是幹凈的.

安裝

首先去 lombok 官網下載jar 包。

隻是把 jar 包下載下來並導入工程中,會發現 IDE 不識別它的註解,那怎麼辦?

對於eclipse

將 lombok.jar 復制到 eclipse.ini 所在的目錄下,然後編輯 eclipse.ini 文件, 在它的末尾插入以下兩行並保存:

Xbootclasspath/a:lombok.jar
javaagent:lombok.jar

接著重啟 eclipse 就可以愉快地使用這個庫瞭。

對於 IDEA

在 IntelliJ 的插件中心可以找到它。

QuickStart

Lombok 提供的註解不多,但都好用,簡要說一下常用的幾個。

@Setter/@Getter

這兩個註解修飾成員變量,可用於生成 setter/gettter 模版代碼。
舉個栗子:

import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
public class Student {
    @Setter @Getter private String name;
    @Getter(AccessLevel.PROTECTED) private int age;// 可以指定訪問權限
    @Setter @Getter private String[] hobbies;
}

將字節碼文件反編譯後可以看到下面這段代碼

public class Student {
  private String name;
  private int age;
  private String[] hobbies;
  public String getName() { return this.name; } 
  public void setName(String name) { this.name = name; }
  protected int getAge() { return this.age; }
  public String[] getHobbies() { return this.hobbies; } 
  public void setHobbies(String[] hobbies) { this.hobbies = hobbies; }
}

@ToString

import lombok.ToString;
@ToString(exclude="id")
public class ToStringExample {
    private int id;
    private String name;
    private String[] tags;
}

編譯後

import java.util.Arrays;
public class ToStringExample {
  public String toString() { 
    return "ToStringExample(name=" + this.name + ", tags=" + Arrays.deepToString(this.tags) + ")"; 
  }
  private int id;
  private String name;
  private String[] tags;
}

我們發現,對於數組,在寫 toString 方法時使用瞭 Arrays類的 靜態方法 deepToString

來看 eclipse 自動生成的 toString 方法:

@Override
public String toString() {
    return "ToStringExample [name=" + name + ", tags=" + Arrays.toString(tags) + "]";
}

eclipse 中對於數組采用的是 Arrays.toString()

區別:這兩個方法的區別是這樣的,對於多維數組,使用 toString 隻能打印出內部數組的名字,這時需要使用 deepToString 方法,它能將內部數組的內容全部打印出來。

exclude 參數

可以指定哪些屬性不出現在 toString 方法中, 比如 exclude={"id", "name"}

doNotUseGetters 參數

當類中有成員變量的 getter 方法時,生成 toString 方法會使用這些 getter 方法,比如

public String toString() { 
    return "ToStringExample(name=" + getName() + ", tags=" + Arrays.deepToString(getTags()) + ")"; 
}

但是將該參數設置為 true 時(默認為 false),生成 toString 方法時就不會使用 getter 方法,而是直接使用這些成員變量,比如

public String toString() { 
    return "ToStringExample(name=" + this.name + ", tags=" + Arrays.deepToString(this.tags) + ")"; 
}

includeFieldNames參數

原本是以 fieldName = fieldValue 的格式來生成 toString 方法的,但是將該參數設置為 false 後(默認是 true),就不會顯示 fieldName 瞭,而隻是生成 fieldValue, 比如

public String toString() { 
  return "ToStringExample(" + getId() + ", " + getName() + ", " + Arrays.deepToString(getTags()) + ")"; 
}

callSuper 參數

若類 A 是類 B 的子類,那麼在 A 類重寫 toString 時,若把該參數設置為 true,會加入下面這段代碼,即也會把父類 B 的 toString 也寫入。

super=" + super.toString()

@NonNull

檢查傳入對象是否為 Null,若為null,則拋出NullPointerException異常。
舉個栗子

import lombok.NonNull;
public class NonNullExample extends Student{
    private String name;
    public NonNullExample(@NonNull Student student) {
        this.name = student.getName();
    }
}

編譯後代碼

import lombok.NonNull;
public class NonNullExample extends Student { 
    private String name;
    public NonNullExample(@NonNull Student student) { 
        if (student == null) 
            throw new NullPointerException("student");
        this.name = student.getName();
  }
}

@EqualsAndHashCode

用在類上,用於生成 equals 和 hashcode 方法。
舉個栗子

@EqualsAndHashCode(exclude={"id"})
public class EqualsAndHashCodeExample {
     private transient int transientVar = 10;
     private String name;
     private double score;
     private String[] tags;
     private int id;
}

編譯後代碼

import java.util.Arrays;
public class EqualsAndHashCodeExample { 
    public int hashCode() { 
        int PRIME = 59;
        int result = 1;
        Object $name = this.name;
        result = result * 59 + ($name == null ? 43 : $name.hashCode());
        long $score = Double.doubleToLongBits(this.score);
        result = result * 59 + (int)($score ^ $score >>> 32);
        result = result * 59 + Arrays.deepHashCode(this.tags);
        return result; 
    } 
    protected boolean canEqual(Object other) { 
        return other instanceof EqualsAndHashCodeExample; 
    } 
    public boolean equals(Object o) { 
        if (o == this)  return true; 
        if (!(o instanceof EqualsAndHashCodeExample)) return false; 
        EqualsAndHashCodeExample other = (EqualsAndHashCodeExample)o; 
        if (!other.canEqual(this)) return false; 
        Object this$name = this.name;
        Object other$name = other.name; 
        if (this$name == null ? other$name != null : !this$name.equals(other$name)) 
            return false; 
        if (Double.compare(this.score, other.score) != 0) 
            return false; 
        return Arrays.deepEquals(this.tags, other.tags);
    }
private transient int transientVar = 10;
private String name;
private double score;
private String[] tags;
private int id;
}

可以看出transient修飾的變量,不會參與。

參數

參數 of 用來指定參與的變量,其他的跟 @ToString 註解類似。

@Data

該註解用於修飾類,會自動生成getter/setter方法, 以及重寫equals()hashcode()toString()方法。

@Cleanup

該註解可以用來自動管理資源,用在局部變量之前,在當前變量范圍內即將執行完畢退出之前會自動清理資源, 自動生成try­finally這樣的代碼來關閉流

舉個栗子:

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

@NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor

這三個註解修飾在類上。

@NoArgsConstructor 用於生成一個無參構造方法。

@RequiredArgsConstructor 會生成一個包含瞭被@NotNull標識的變量的構造方法。同樣可以設置生成構造方法的權限,使用 access參數進行設置。

@AllArgsConstructor 會生成一個包含所有變量, 同時如果變量使用瞭@NotNull,會進行是否為空的校驗。

舉個栗子:

import lombok.*;
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample {
    private int x;
    private int y;
    @NonNull private String desc;
    @NoArgsConstructor
    public class NoArgsExample{
        private String field;
    }
}

這與下面這段代碼是等價的,

import java.beans.ConstructorProperties;
public class ConstructorExample { 
    public static ConstructorExample of(@lombok.NonNull String desc) { 
        return new ConstructorExample(desc); 
    } 
    private ConstructorExample(@lombok.NonNull String desc) {
        if (desc == null) throw new NullPointerException("desc"); 
        this.desc = desc; 
    } 
    @ConstructorProperties({"x", "y", "desc"})
    protected ConstructorExample(int x, int y, @lombok.NonNull String desc) {
        if (desc == null) throw new NullPointerException("desc"); 
        this.x = x;
        this.y = y;
        this.desc = desc;
    }
    private int x;
    private int y;
    @lombok.NonNull
    private String desc;
    public class NoArgsExample
    {
        private String field;
        public NoArgsExample() {}
    }
}

@Value

該註解用於修飾類,是@Data的不可變形式, 相當於為成員變量添加final聲明, 隻提供getter方法, 而不提供setter方法,然後還有 equals/hashCode/toString方法,以及一個包含所有參數的構造方法。

@builder

用在類、構造器、方法上,為你提供復雜的builder APIs,讓你可以像如下方式調用

Person.builder().name("A
dam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build()

舉個栗子:

import lombok.Builder;
import java.util.Set;
@Builder
public class BuilderExample {
    private String name;
    private int age;
}

反編譯代碼如下:

package tutorial.lombok;
    public class BuilderExample
    {
        public static class BuilderExampleBuilder
        {
            private String name;
            private int age;
            public BuilderExampleBuilder name(String name)
            {
                this.name = name;
                return this;
            }
            public BuilderExampleBuilder age(int age)
            {
                this.age = age;
                return this;
            }
            public BuilderExample build()
            {
                return new BuilderExample(name, age);
            }
            public String toString()
            {
                return (new StringBuilder()).append("BuilderExample.BuilderExampleBuilder(name=").append(name).append(", age=").append(age).append(")").toString();
            }
            BuilderExampleBuilder()
            {
            }
        }
        private String name;
        private int age;
        BuilderExample(String name, int age)
        {
            this.name = name;
            this.age = age;
        }
        public static BuilderExampleBuilder builder()
        {
            return new BuilderExampleBuilder();
        }
    }

註意:

使用@Singular註解的集合屬性名必須使用s結尾, lombok會將屬性名結尾的s去掉,剩餘的名字會作為方法名, 向這個集合中添加元素。

@Builder 的參數builderClassName設置生成的builder方法名,buildMethodName 設置build方法名,builderMethodName設置builderMethod`方法名。
比如

@Builder(builderClassName = "GBuilder", buildMethodName = "buildG", builderMethodName = "GBuilder"

@SneakyThrows

自動拋受檢異常, 而無需顯式在方法上使用throws語句。

@Synchronized

用在方法上,將方法聲明為同步的,並自動加鎖,而鎖對象是一個私有的屬性 LOCK,而java中的synchronized關鍵字鎖對象是this,鎖在this或者自己的類對象上存在副作用,就是你不能阻止非受控代碼去鎖this或者類對象,這可能會導致競爭條件或者其它線程錯誤。

舉個栗子:

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

反編譯代碼如下:

public class SynchronizedExample {
    private static 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)

可以替代經典的Double Check Lock樣板代碼。

舉個栗子:

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

反編譯代碼如下,

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

@Log

根據不同的註解生成不同類型的log對象, 但是實例名稱都是log, 有六種可選實現類

@CommonsLog
Creates log = org. apache. commons. logging. LogFactory. getLog(LogExample. class) ;
@Log
Creates log = java. util. logging. Logger. getLogger(LogExample. class. getName() ) ;
@Log4j
Creates log = org. apache. log4j. Logger. getLogger(LogExample. class) ;
@Log4j2
Creates log = org. apache. logging. log4j. LogManager. getLogger(LogExample. class) ;
@Slf4j
Creates log = org. slf4j. LoggerFactory. getLogger(LogExample. class) ;
@XSlf4j
Creates log = org. slf4j. ext. XLoggerFactory. getXLogger(LogExample. class) ;

舉個栗子,

import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;
@Log
public class LogExample {
    public static void main(String... args) {
        log.error("Something's wrong here");
    }
}
@Slf4j
public class LogExampleOther {
    public static void main(String... args) {
        log.error("Something else is wrong here");
    }
}
@CommonsLog(topic="CounterLog")
public class LogExampleCategory {
    public static void main(String... args) {
        log.error("Calling the 'CounterLog' with a message");
    }
}
@CommonsLog(topic="CounterLog")

這條語句會翻譯成這樣

private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog("CounterLog");

以上就是減少代碼開發工作的Java庫lombok使用學習的詳細內容,更多關於Java lombok庫減少代碼開發的資料請關註WalkonNet其它相關文章!

推薦閱讀: