Android上使用Lombok
[TOC]
簡介
最近幾天嘗試了一把后端的工作,發(fā)現(xiàn)后端同學(xué)使用了一個第三庫——Lombok,用了一下,感覺還不錯,特來介紹一下,感覺和以前介紹過的AutoValue挺像的。
Lombok 官網(wǎng)上面有個幾分鐘的視頻,接單介紹了Lombok的用途,使用方法很簡單,只需要依賴對應(yīng)的jar文件,然后在對應(yīng)的Java文件上使用注解即可。
先看個例子,下面是常見的一個Java一個實體類,含有field、setter、getter、equals、hashcode、toString方法。
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (id != user.id) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
如果使用了Lombok,就很簡單了,直接定義好字段,然后添加一個注解@Data即可,其他方法,工具自動生成,雖然上面的方法我們也是用工具生成的,但是如果要添加或者刪除字段,還是要修改代碼的,如果直接使用注解的方式,那么還是簡單的,無需修改任何方法。
@Data
public class UserLombok {
private int id;
private String name;
}
注解簡介
Lombok 主要使用就是通過添加注解,來自動生成代碼,主要包含兩類,一種是Stable類型,一種是Experimental。前面表示穩(wěn)定的注解,后面表示實驗類型的,可能會被移除。本文主要介紹Stable類型,Experimental由于使用較少,不做講解。
Stable
- val
Finally! Hassle-free final local variables.
- @NonNull
or: How I learned to stop worrying and love the NullPointerException.
- @Cleanup
Automatic resource management: Call your close() methods safely with no hassle.
- @Getter/@Setter
Never write public int getFoo() {return foo;} again.
- @ToString
No need to start a debugger to see your fields: Just let lombok generate a toString for you!
- @EqualsAndHashCode
Equality made easy: Generates hashCode and equals implementations from the fields of your object..
- @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor
Constructors made to order: Generates constructors that take no arguments, one argument per final / non-nullfield, or one argument for every field.
- @Data
All together now: A shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, and @Setter on all non-final fields, and @RequiredArgsConstructor!
- @Value
Immutable classes made very easy.
- @Builder
... and Bob's your uncle: No-hassle fancy-pants APIs for object creation!
- @SneakyThrows
To boldly throw checked exceptions where no one has thrown them before!
- @Synchronized
synchronized done right: Don't expose your locks.
- @Getter(lazy=true)
Laziness is a virtue!
- @Log
Captain's Log, stardate 24435.7: "What was that line again?"
Experimental
- var
Modifiable local variables with a type inferred by assigning value.
- @Accessors
A more fluent API for getters and setters.
- @ExtensionMethod
Annoying API? Fix it yourself: Add new methods to existing types!
- @FieldDefaults
New default field modifiers for the 21st century.
- @Delegate
Don't lose your composition.
- @Wither
Immutable 'setters' - methods that create a clone but with one changed field.
- onMethod= / onConstructor= / onParam=
Sup dawg, we heard you like annotations, so we put annotations in your annotations so you can annotate while you're annotating.
- @UtilityClass
Utility, metility, wetility! Utility classes for the masses.
- @Helper
With a little help from my friends... Helper methods for java.
Android 集成
項目根目錄下面新建配置文件 lombok.config,同時填上對應(yīng)的配置項,Java項目不需要,Android和Java還是有點區(qū)別的,不配置有的注解使用不了,編譯不過。

lombok.config
lombok.anyConstructor.suppressConstructorProperties=true
然后在對應(yīng)的項目中添加gradle依賴就行了。
dependencies {
provided "org.projectlombok:lombok:1.16.18"
compile 'org.glassfish:javax.annotation:10.0-b28'
}
可以在Android Studio中安裝lombok插件。

這樣可以很方便的看到類中生成的方法

注解說明
下面簡單說明注解的使用方法(如需了解詳細使用,請參閱官方文檔),以及使用注解后類中生成的方法。
val
定義一個final類型的變量,并且可以不寫類型。
如:
public class ValExample {
public String example() {
val example = new ArrayList<String>();
example.add("Hello, World!");
val foo = example.get(0);
return foo.toLowerCase();
}
public void example2() {
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());
}
}
}
class字節(jié)碼:
public class ValExample {
public ValExample() {
}
public String example() {
ArrayList<String> example = new ArrayList();
example.add("Hello, World!");
String foo = (String)example.get(0);
return foo.toLowerCase();
}
public void example2() {
HashMap<Integer, String> map = new HashMap();
map.put(Integer.valueOf(0), "zero");
map.put(Integer.valueOf(5), "five");
Iterator var2 = map.entrySet().iterator();
while(var2.hasNext()) {
Entry<Integer, String> entry = (Entry)var2.next();
System.out.printf("%d: %s\n", new Object[]{entry.getKey(), entry.getValue()});
}
}
}
@NonNull
非空值判斷,如果為空,則拋出異常
如:
public class NonNullExample {
public static int length(@NonNull String string) {
return string.length();
}
}
class字節(jié)碼
public class NonNullExample {
public NonNullExample() {
}
public static int length(@NonNull String string) {
if(string == null) {
throw new NullPointerException("string");
} else {
return string.length();
}
}
}
@Cleanup
可以自動調(diào)用close方法
如
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);
}
}
}
class字節(jié)碼
public class CleanupExample {
public CleanupExample() {
}
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream(args[0]);
try {
FileOutputStream out = new FileOutputStream(args[1]);
try {
byte[] b = new byte[10000];
while(true) {
int r = in.read(b);
if(r == -1) {
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
自動生成setter、getter方法
// GetterSetterExample.java
public class GetterSetterExample {
@Getter
@Setter
private int age;
@Setter(AccessLevel.PROTECTED)
private String name;
}
// GetterSetterExample.class
public class GetterSetterExample {
private int age;
private String name;
public GetterSetterExample() {
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
protected void setName(String name) {
this.name = name;
}
}
@ToString
自動生成toString方法
// ToStringExample.java
@ToString(exclude = "id")
public class ToStringExample {
private int id;
private String name;
private String passwd;
public ToStringExample(int id, String name, String passwd) {
this.id = id;
this.name = name;
this.passwd = passwd;
}
}
// ToStringExample.class
public class ToStringExample {
private int id;
private String name;
private String passwd;
public ToStringExample(int id, String name, String passwd) {
this.id = id;
this.name = name;
this.passwd = passwd;
}
public String toString() {
return "ToStringExample(name=" + this.name + ", passwd=" + this.passwd + ")";
}
}
@EqualsAndHashCode
自動生成equals和hashcode方法。
// EqualsAndHashCodeExample.java
@EqualsAndHashCode
public class EqualsAndHashCodeExample {
private int id;
private String name;
public EqualsAndHashCodeExample(int id, String name) {
this.id = id;
this.name = name;
}
}
// EqualsAndHashCodeExample.class
public class EqualsAndHashCodeExample {
private int id;
private String name;
public EqualsAndHashCodeExample(int id, String name) {
this.id = id;
this.name = name;
}
public boolean equals(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 if(this.id != other.id) {
return false;
} else {
Object this$name = this.name;
Object other$name = other.name;
if(this$name == null) {
if(other$name != null) {
return false;
}
} else if(!this$name.equals(other$name)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof EqualsAndHashCodeExample;
}
public int hashCode() {
int PRIME = true;
int result = 1;
int result = result * 59 + this.id;
Object $name = this.name;
result = result * 59 + ($name == null?43:$name.hashCode());
return result;
}
}
@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor
自動生成相關(guān)的構(gòu)造函數(shù)
// ConstructorExample.java
@ToString
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PUBLIC)
public class ConstructorExample<T> {
private String args;
@ToString
@RequiredArgsConstructor(staticName = "of")
public static class StaticMethodsExample {
@NonNull
private String field;
}
}
// ConstructorExample.class
public class ConstructorExample<T> {
private String args;
public String toString() {
return "ConstructorExample(args=" + this.args + ")";
}
public ConstructorExample() {
}
public ConstructorExample(String args) {
this.args = args;
}
public static class StaticMethodsExample {
@NonNull
private String field;
public String toString() {
return "ConstructorExample.StaticMethodsExample(field=" + this.field + ")";
}
private StaticMethodsExample(@NonNull String field) {
if(field == null) {
throw new NullPointerException("field");
} else {
this.field = field;
}
}
public static ConstructorExample.StaticMethodsExample of(@NonNull String field) {
return new ConstructorExample.StaticMethodsExample(field);
}
}
}
@Builder
自動生成構(gòu)造者模式方法
// BuilderExample.java
@Builder
@Data
public class BuilderExample {
private String name;
private int age;
@Singular
private Set<String> occupations;
}
class文件太長,就不貼了,下面是調(diào)用方式。
// test builder
BuilderExample builderExample = BuilderExample.builder()
.name("admin")
.age(10)
.occupation("aaa")
.occupation("bbb")
.build();
Log.i(TAG, "onCreate: " + builderExample);
@SneakyThrows
自動生成異常拋出代碼
// SneakyThrowsExample.java
public class SneakyThrowsExample implements Runnable {
@SneakyThrows(UnsupportedEncodingException.class)
public String utf8ToString(byte[] bytes) {
return new String(bytes, "UTF-8");
}
@SneakyThrows
public void run() {
throw new Throwable();
}
}
// SneakyThrowsExample.class
public class SneakyThrowsExample implements Runnable {
public SneakyThrowsExample() {
}
public String utf8ToString(byte[] bytes) {
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException var3) {
throw var3;
}
}
public void run() {
try {
throw new Throwable();
} catch (Throwable var2) {
throw var2;
}
}
}
@Synchronized
自動生成線程同步代碼
// SynchronizedExample.java
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");
}
}
// SynchronizedExample.class
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 SynchronizedExample() {
}
public static void hello() {
Object var0 = $LOCK;
synchronized($LOCK) {
System.out.println("world");
}
}
public int answerToLife() {
Object var1 = this.$lock;
synchronized(this.$lock) {
return 42;
}
}
public void foo() {
Object var1 = this.readLock;
synchronized(this.readLock) {
System.out.println("bar");
}
}
}
@Getter(lazy=true)
延遲初始化
// GetterLazyExample.java
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;
}
}
// GetterLazyExample.class
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) {
AtomicReference var2 = this.cached;
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
自動生成日志對象,不過都是J2EE方面的,Android端用途不大。
原理
自從Java 6起,javac就支持“JSR 269 Pluggable Annotation Processing API”規(guī)范,只要程序?qū)崿F(xiàn)了該API,就能在javac運行的時候得到調(diào)用。
舉例來說,現(xiàn)在有一個實現(xiàn)了"JSR 269 API"的程序A,那么使用javac編譯源碼的時候具體流程如下:
javac對源代碼進行分析,生成一棵抽象語法樹(AST)
運行過程中調(diào)用實現(xiàn)了"JSR 269 API"的A程序
此時A程序就可以完成它自己的邏輯,包括修改第一步驟得到的抽象語法樹(AST)
javac使用修改后的抽象語法樹(AST)生成字節(jié)碼文件
詳細的流程圖如下:

總結(jié)
綜上所述,使用了lombok可以簡化Java代碼,因為是在編譯期處理所以可能會增加點時間,不過對于Android來說,可以嘗試一下,不過17年Google IO已經(jīng)推薦使用Kotlin開發(fā)Android了,lombok中好多功能在Kotlin中已經(jīng)實現(xiàn)了,如果項目暫時還不想使用Kotlin開發(fā),繼續(xù)使用Java的可以嘗試一下。
缺點:
使用lombok雖然能夠省去手動創(chuàng)建代碼的麻煩,但是卻大大降低了源代碼文件的可讀性和完整性,降低了閱讀源代碼的舒適度。