0%

Android上使用Lombok

Android上使用Lombok


简介

最近几天尝试了一把后端的工作,发现后端同学使用了一个第三库——Lombok,用了一下,感觉还不错,特来介绍一下,感觉和以前介绍过的AutoValue挺像的。

Lombok 官网上面有个几分钟的视频,接单介绍了Lombok的用途,使用方法很简单,只需要依赖对应的jar文件,然后在对应的Java文件上使用注解即可。

先看个例子,下面是常见的一个Java一个实体类,含有field、setter、getter、equals、hashcode、toString方法。

1
public class User {
2
3
    private int id;
4
5
    private String name;
6
7
    public int getId() {
8
        return id;
9
    }
10
11
    public void setId(int id) {
12
        this.id = id;
13
    }
14
15
    public String getName() {
16
        return name;
17
    }
18
19
    public void setName(String name) {
20
        this.name = name;
21
    }
22
23
    @Override
24
    public boolean equals(Object o) {
25
        if (this == o) return true;
26
        if (o == null || getClass() != o.getClass()) return false;
27
28
        User user = (User) o;
29
30
        if (id != user.id) return false;
31
        return name != null ? name.equals(user.name) : user.name == null;
32
    }
33
34
    @Override
35
    public int hashCode() {
36
        int result = id;
37
        result = 31 * result + (name != null ? name.hashCode() : 0);
38
        return result;
39
    }
40
41
    @Override
42
    public String toString() {
43
        return "User{" +
44
                "id=" + id +
45
                ", name='" + name + '\'' +
46
                '}';
47
    }
48
}

如果使用了Lombok,就很简单了,直接定义好字段,然后添加一个注解@Data即可,其他方法,工具自动生成,虽然上面的方法我们也是用工具生成的,但是如果要添加或者删除字段,还是要修改代码的,如果直接使用注解的方式,那么还是简单的,无需修改任何方法。

1
@Data
2
public class UserLombok {
3
4
    private int id;
5
6
    private String name;
7
}

注解简介

Lombok 主要使用就是通过添加注解,来自动生成代码,主要包含两类,一种是Stable类型,一种是Experimental。前面表示稳定的注解,后面表示实验类型的,可能会被移除。本文主要介绍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,同时填上对应的配置项,Java项目不需要,Android和Java还是有点区别的,不配置有的注解使用不了,编译不过。

lombok.config

1
lombok.anyConstructor.suppressConstructorProperties=true

然后在对应的项目中添加gradle依赖就行了。

1
dependencies {
2
    provided "org.projectlombok:lombok:1.16.18"
3
    compile 'org.glassfish:javax.annotation:10.0-b28'
4
}

可以在Android Studio中安装lombok插件。

这样可以很方便的看到类中生成的方法

注解说明

下面简单说明注解的使用方法(如需了解详细使用,请参阅官方文档),以及使用注解后类中生成的方法。

val

定义一个final类型的变量,并且可以不写类型。

如:

1
public class ValExample {
2
3
    public String example() {
4
        val example = new ArrayList<String>();
5
        example.add("Hello, World!");
6
        val foo = example.get(0);
7
        return foo.toLowerCase();
8
    }
9
10
    public void example2() {
11
        val map = new HashMap<Integer, String>();
12
        map.put(0, "zero");
13
        map.put(5, "five");
14
        for (val entry : map.entrySet()) {
15
            System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
16
        }
17
    }
18
}

class字节码:

1
public class ValExample {
2
    public ValExample() {
3
    }
4
5
    public String example() {
6
        ArrayList<String> example = new ArrayList();
7
        example.add("Hello, World!");
8
        String foo = (String)example.get(0);
9
        return foo.toLowerCase();
10
    }
11
12
    public void example2() {
13
        HashMap<Integer, String> map = new HashMap();
14
        map.put(Integer.valueOf(0), "zero");
15
        map.put(Integer.valueOf(5), "five");
16
        Iterator var2 = map.entrySet().iterator();
17
18
        while(var2.hasNext()) {
19
            Entry<Integer, String> entry = (Entry)var2.next();
20
            System.out.printf("%d: %s\n", new Object[]{entry.getKey(), entry.getValue()});
21
        }
22
    }
23
}

@NonNull

非空值判断,如果为空,则抛出异常

如:

1
public class NonNullExample {
2
3
    public static int length(@NonNull String string) {
4
        return string.length();
5
    }
6
}

class字节码

1
public class NonNullExample {
2
    public NonNullExample() {
3
    }
4
5
    public static int length(@NonNull String string) {
6
        if(string == null) {
7
            throw new NullPointerException("string");
8
        } else {
9
            return string.length();
10
        }
11
    }
12
}

@Cleanup

可以自动调用close方法

1
public class CleanupExample {
2
3
    public static void main(String[] args) throws IOException {
4
        @Cleanup InputStream in = new FileInputStream(args[0]);
5
        @Cleanup OutputStream out = new FileOutputStream(args[1]);
6
        byte[] b = new byte[10000];
7
        while (true) {
8
            int r = in.read(b);
9
            if (r == -1) break;
10
            out.write(b, 0, r);
11
        }
12
    }
13
}

class字节码

1
public class CleanupExample {
2
    public CleanupExample() {
3
    }
4
5
    public static void main(String[] args) throws IOException {
6
        FileInputStream in = new FileInputStream(args[0]);
7
8
        try {
9
            FileOutputStream out = new FileOutputStream(args[1]);
10
11
            try {
12
                byte[] b = new byte[10000];
13
14
                while(true) {
15
                    int r = in.read(b);
16
                    if(r == -1) {
17
                        return;
18
                    }
19
20
                    out.write(b, 0, r);
21
                }
22
            } finally {
23
                if(Collections.singletonList(out).get(0) != null) {
24
                    out.close();
25
                }
26
27
            }
28
        } finally {
29
            if(Collections.singletonList(in).get(0) != null) {
30
                in.close();
31
            }
32
33
        }
34
    }
35
}

@Getter/@Setter

自动生成setter、getter方法

1
// GetterSetterExample.java
2
public class GetterSetterExample {
3
4
    @Getter
5
    @Setter
6
    private int age;
7
8
    @Setter(AccessLevel.PROTECTED)
9
    private String name;
10
}
11
12
// GetterSetterExample.class
13
public class GetterSetterExample {
14
    private int age;
15
    private String name;
16
17
    public GetterSetterExample() {
18
    }
19
20
    public int getAge() {
21
        return this.age;
22
    }
23
24
    public void setAge(int age) {
25
        this.age = age;
26
    }
27
28
    protected void setName(String name) {
29
        this.name = name;
30
    }
31
}

@ToString

自动生成toString方法

1
// ToStringExample.java
2
@ToString(exclude = "id")
3
public class ToStringExample {
4
5
    private int id;
6
7
    private String name;
8
9
    private String passwd;
10
11
    public ToStringExample(int id, String name, String passwd) {
12
        this.id = id;
13
        this.name = name;
14
        this.passwd = passwd;
15
    }
16
}
17
18
// ToStringExample.class
19
public class ToStringExample {
20
    private int id;
21
    private String name;
22
    private String passwd;
23
24
    public ToStringExample(int id, String name, String passwd) {
25
        this.id = id;
26
        this.name = name;
27
        this.passwd = passwd;
28
    }
29
30
    public String toString() {
31
        return "ToStringExample(name=" + this.name + ", passwd=" + this.passwd + ")";
32
    }
33
}

@EqualsAndHashCode

自动生成equals和hashcode方法。

1
// EqualsAndHashCodeExample.java
2
@EqualsAndHashCode
3
public class EqualsAndHashCodeExample {
4
5
    private int id;
6
7
    private String name;
8
9
    public EqualsAndHashCodeExample(int id, String name) {
10
        this.id = id;
11
        this.name = name;
12
    }
13
}
14
15
// EqualsAndHashCodeExample.class
16
public class EqualsAndHashCodeExample {
17
    private int id;
18
    private String name;
19
20
    public EqualsAndHashCodeExample(int id, String name) {
21
        this.id = id;
22
        this.name = name;
23
    }
24
25
    public boolean equals(Object o) {
26
        if(o == this) {
27
            return true;
28
        } else if(!(o instanceof EqualsAndHashCodeExample)) {
29
            return false;
30
        } else {
31
            EqualsAndHashCodeExample other = (EqualsAndHashCodeExample)o;
32
            if(!other.canEqual(this)) {
33
                return false;
34
            } else if(this.id != other.id) {
35
                return false;
36
            } else {
37
                Object this$name = this.name;
38
                Object other$name = other.name;
39
                if(this$name == null) {
40
                    if(other$name != null) {
41
                        return false;
42
                    }
43
                } else if(!this$name.equals(other$name)) {
44
                    return false;
45
                }
46
47
                return true;
48
            }
49
        }
50
    }
51
52
    protected boolean canEqual(Object other) {
53
        return other instanceof EqualsAndHashCodeExample;
54
    }
55
56
    public int hashCode() {
57
        int PRIME = true;
58
        int result = 1;
59
        int result = result * 59 + this.id;
60
        Object $name = this.name;
61
        result = result * 59 + ($name == null?43:$name.hashCode());
62
        return result;
63
    }
64
}

@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor

自动生成相关的构造函数

1
// ConstructorExample.java
2
@ToString
3
@NoArgsConstructor
4
@AllArgsConstructor(access = AccessLevel.PUBLIC)
5
public class ConstructorExample<T> {
6
7
    private String args;
8
9
    @ToString
10
    @RequiredArgsConstructor(staticName = "of")
11
    public static class StaticMethodsExample {
12
13
        @NonNull
14
        private String field;
15
    }
16
}
17
18
// ConstructorExample.class
19
public class ConstructorExample<T> {
20
    private String args;
21
22
    public String toString() {
23
        return "ConstructorExample(args=" + this.args + ")";
24
    }
25
26
    public ConstructorExample() {
27
    }
28
29
    public ConstructorExample(String args) {
30
        this.args = args;
31
    }
32
33
    public static class StaticMethodsExample {
34
        @NonNull
35
        private String field;
36
37
        public String toString() {
38
            return "ConstructorExample.StaticMethodsExample(field=" + this.field + ")";
39
        }
40
41
        private StaticMethodsExample(@NonNull String field) {
42
            if(field == null) {
43
                throw new NullPointerException("field");
44
            } else {
45
                this.field = field;
46
            }
47
        }
48
49
        public static ConstructorExample.StaticMethodsExample of(@NonNull String field) {
50
            return new ConstructorExample.StaticMethodsExample(field);
51
        }
52
    }
53
}

@Builder

自动生成构造者模式方法

1
// BuilderExample.java
2
3
@Builder
4
@Data
5
public class BuilderExample {
6
7
    private String name;
8
9
    private int age;
10
11
    @Singular
12
    private Set<String> occupations;
13
}

class文件太长,就不贴了,下面是调用方式。

1
// test builder
2
BuilderExample builderExample = BuilderExample.builder()
3
    .name("admin")
4
    .age(10)
5
    .occupation("aaa")
6
    .occupation("bbb")
7
    .build();
8
9
Log.i(TAG, "onCreate: " + builderExample);

@SneakyThrows

自动生成异常抛出代码

1
// SneakyThrowsExample.java
2
3
public class SneakyThrowsExample implements Runnable {
4
5
    @SneakyThrows(UnsupportedEncodingException.class)
6
    public String utf8ToString(byte[] bytes) {
7
        return new String(bytes, "UTF-8");
8
    }
9
10
    @SneakyThrows
11
    public void run() {
12
        throw new Throwable();
13
    }
14
}
15
16
// SneakyThrowsExample.class
17
public class SneakyThrowsExample implements Runnable {
18
    public SneakyThrowsExample() {
19
    }
20
21
    public String utf8ToString(byte[] bytes) {
22
        try {
23
            return new String(bytes, "UTF-8");
24
        } catch (UnsupportedEncodingException var3) {
25
            throw var3;
26
        }
27
    }
28
29
    public void run() {
30
        try {
31
            throw new Throwable();
32
        } catch (Throwable var2) {
33
            throw var2;
34
        }
35
    }
36
}

@Synchronized

自动生成线程同步代码

1
// SynchronizedExample.java
2
public class SynchronizedExample {
3
4
    private final Object readLock = new Object();
5
6
    @Synchronized
7
    public static void hello() {
8
        System.out.println("world");
9
    }
10
11
    @Synchronized
12
    public int answerToLife() {
13
        return 42;
14
    }
15
16
    @Synchronized("readLock")
17
    public void foo() {
18
        System.out.println("bar");
19
    }
20
}
21
22
// SynchronizedExample.class
23
public class SynchronizedExample {
24
    private static final Object $LOCK = new Object[0];
25
    private final Object $lock = new Object[0];
26
    private final Object readLock = new Object();
27
28
    public SynchronizedExample() {
29
    }
30
31
    public static void hello() {
32
        Object var0 = $LOCK;
33
        synchronized($LOCK) {
34
            System.out.println("world");
35
        }
36
    }
37
38
    public int answerToLife() {
39
        Object var1 = this.$lock;
40
        synchronized(this.$lock) {
41
            return 42;
42
        }
43
    }
44
45
    public void foo() {
46
        Object var1 = this.readLock;
47
        synchronized(this.readLock) {
48
            System.out.println("bar");
49
        }
50
    }
51
}

@Getter(lazy=true)

延迟初始化

1
// GetterLazyExample.java
2
public class GetterLazyExample {
3
4
    @Getter(lazy = true)
5
    private final double[] cached = expensive();
6
7
    private double[] expensive() {
8
        double[] result = new double[1000000];
9
        for (int i = 0; i < result.length; i++) {
10
            result[i] = Math.asin(i);
11
        }
12
        return result;
13
    }
14
}
15
16
// GetterLazyExample.class
17
public class GetterLazyExample {
18
19
    private final AtomicReference<Object> cached = new AtomicReference();
20
21
    public GetterLazyExample() {
22
    }
23
24
    private double[] expensive() {
25
        double[] result = new double[1000000];
26
27
        for(int i = 0; i < result.length; ++i) {
28
            result[i] = Math.asin((double)i);
29
        }
30
31
        return result;
32
    }
33
34
    public double[] getCached() {
35
        Object value = this.cached.get();
36
        if(value == null) {
37
            AtomicReference var2 = this.cached;
38
            synchronized(this.cached) {
39
                value = this.cached.get();
40
                if(value == null) {
41
                    double[] actualValue = this.expensive();
42
                    value = actualValue == null?this.cached:actualValue;
43
                    this.cached.set(value);
44
                }
45
            }
46
        }
47
48
        return (double[])((double[])(value == this.cached?null:value));
49
    }
50
}

@Log

自动生成日志对象,不过都是J2EE方面的,Android端用途不大。

官方示例

原理

自从Java 6起,javac就支持“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用。

举例来说,现在有一个实现了”JSR 269 API”的程序A,那么使用javac编译源码的时候具体流程如下:

  1. javac对源代码进行分析,生成一棵抽象语法树(AST)

  2. 运行过程中调用实现了”JSR 269 API”的A程序

  3. 此时A程序就可以完成它自己的逻辑,包括修改第一步骤得到的抽象语法树(AST)

  4. javac使用修改后的抽象语法树(AST)生成字节码文件

详细的流程图如下:

总结

综上所述,使用了lombok可以简化Java代码,因为是在编译期处理所以可能会增加点时间,不过对于Android来说,可以尝试一下,不过17年Google IO已经推荐使用Kotlin开发Android了,lombok中好多功能在Kotlin中已经实现了,如果项目暂时还不想使用Kotlin开发,继续使用Java的可以尝试一下。

缺点:

使用lombok虽然能够省去手动创建代码的麻烦,但是却大大降低了源代码文件的可读性和完整性,降低了阅读源代码的舒适度。

相关链接

Lombok官网

AutoValue相关

android基础之依赖注入问题

Lombok的使用和原理

坚持原创技术分享,您的支持将鼓励我继续创作!