一 介绍
lomBok 是通过使用对应的注解,可以在编译源码的时候生成对应的方法,减少模板代码的编写。
二 集成
AS 在build.gradle 添加如下依赖
compile 'javax.annotation:javax.annotation-api:1.2'
compile 'org.projectlombok:lombok:1.16.18'
然后安装lombok插件
三 使用常见注解 及 编译时生成对应的方法
官方注解:https://projectlombok.org/features/all
1 @Setter @Getter 可以修饰javaBean 的类 【编译时让该类生成所有参数的set get方法】 也可以 修饰制定字段上 编译生成对应的setget方法;可以使用可选参数来指定生成的方法的访问级别 如
@Getter @Setter private boolean employed = true;
@Setter(AccessLevel.PROTECTED) private String name; // AccessLevel.PROTECTED 来指定 生成的set方法为
protected
相当于java源码为:
private boolean employed = true;
private String name;
public boolean isEmployed() {
return employed;
}
public void setEmployed(final boolean employed) {
this.employed = employed;
}
protected void setName(final String name) {
this.name = name;
}
2 @ToString : 生成对应的toString方法。按参数顺序 按照key=value形式依次打印,按照,号分割;
@ToString(exclude = {"id","name"}) 用exclude 决定排除哪些参数
@ToString(callSuper = true) 决定调用父类同名方法
例如: 生成toString 调用 父类同名方法 排除someExcludedField字段
@ToString(callSuper=true,exclude="someExcludedField")
public class Foo extends Bar {
private boolean someBoolean = true;
private String someStringField;
private float someExcludedField;
}
相当于 java源码:
public class Foo extends Bar {
private boolean someBoolean = true;
private String someStringField;
private float someExcludedField;
@java.lang.Override
public java.lang.String toString() {
return "Foo(super=" + super.toString() +
", someBoolean=" + someBoolean +
", someStringField=" + someStringField + ")";
}
}
3 @EqualsAndHashCode 生成对应的equals() 和 hashCode()方法 ,默认情况下 非static 和非transient 都被使用。也可以用exclude排除字段,,callSuper = true调用父类方法等;当父类重写的@EqualsAndHashCode 子类要声明是否调用父类的同名 方法,
如父类
@EqualsAndHashCode
public class UBean {
private String school;
}
子类
@EqualsAndHashCode(callSuper = true, exclude = {"value"})
public class TestBean extends UBean {
private transient int transientVar = 10;
private static String active;
private int value;
private String name;
}
相当于Java源码为: 如下结果 不包括 static 和 transient 和exclude = {"value"}声明的字段 并调用了父类同名方法
public class TestBean extends UBean {
private transient int transientVar = 10;
private static String active;
private int value;
private String name;
public TestBean() {
}
public boolean equals(Object o) {
if(o == this) {
return true;
} else if(!(o instanceof TestBean)) {
return false;
} else {
TestBean other = (TestBean)o;
if(!other.canEqual(this)) {
return false;
} else if(!super.equals(o)) {
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 TestBean;
}
public int hashCode() {
int PRIME = true;
int result = 1;
int result = result * 59 + super.hashCode();
Object $name = this.name;
result = result * 59 + ($name == null?43:$name.hashCode());
return result;
}
}
4 @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor
给类增加无参构造器 指定参数的构造器 包含所有参数的构造器
@NoArgsConstructor 生成一个无参构造方法。当类中有final字段没有被初始化时,编译器会报错,此时可用 @NoArgsConstructor(force = true),然后就会为没有初始化的final字段设置默认值 0 / false / null。对于具有约束的字段 (例如@NonNull字段),不会生成检查或分配,因此请注意,正确初始化这些字段之前,这些约束无效。
@NoArgsConstructor(force = true)
public class TestBean extends UBean {
private transient int transientVar = 10;
private static String active;
private final int value;
private String name;
}
@RequiredArgsConstructor : (指定参数构造器)只能为 未初始化的 final 字段和使用 @NonNull 标注的字段生成构造函数
@RequiredArgsConstructor(staticName = "of")会生成一个of()的静态方法,并把构造方法设置为私有的
@RequiredArgsConstructor
@RequiredArgsConstructor
public class TestBean {
@NonNull private Integer id ;
@NonNull private String name ;
private int fa;
}
对应.class 文件为:
public class TestBean {
@NonNull
private Integer id;
@NonNull
private String name;
private int fa;
public TestBean(@NonNull Integer id, @NonNull String name) {
if(id == null) {
throw new NullPointerException("id");
} else if(name == null) {
throw new NullPointerException("name");
} else {
this.id = id;
this.name = name;
}
}
}
@RequiredArgsConstructor(staticName = "of):
@RequiredArgsConstructor(staticName = "of")
public class TestBean {
@NonNull private Integer id ;
@NonNull private String name ;
private int fa;
}
生成对应的.class为 参数只为 未初始化的 final 字段和使用 @NonNull 标注
public class TestBean {
@NonNull
private Integer id;
@NonNull
private String name;
private int fa;
private TestBean(@NonNull Integer id, @NonNull String name) {
if(id == null) {
throw new NullPointerException("id");
} else if(name == null) {
throw new NullPointerException("name");
} else {
this.id = id;
this.name = name;
}
}
public static TestBean of(@NonNull Integer id, @NonNull String name) {
return new TestBean(id, name);
}
}
@AllArgsConstructor
包含所有参数的构造器
@AllArgsConstructor(suppressConstructorProperties = true)
public class TestBean {
@NonNull private Integer id ;
@NonNull private String name ;
private int fa;
}
对应.class 文件为: 包含了所有参数
public class TestBean {
@NonNull
private Integer id;
@NonNull
private String name;
private int fa;
public TestBean(@NonNull Integer id, @NonNull String name, int fa) {
if(id == null) {
throw new NullPointerException("id");
} else if(name == null) {
throw new NullPointerException("name");
} else {
this.id = id;
this.name = name;
this.fa = fa;
}
}
}
5 @Data 包含了 @ToString、@EqualsAndHashCode、@Getter / @Setter和@RequiredArgsConstructor的功能
6 @Accessors 控制生成的set get 方法 和@Data 或者@Setter @Getter搭配使用
@Accessors(fluent = true, chain = true, prefix = "abc")
fluent Boolean类型值 默认为false。此字段主要为控制生成的getter和setter方法前面是否带get/set【false 带有】
chain boolean值,默认false。如果设置为true,setter返回的是此对象,方便链式调用方法 【true 是链式】
prefix 参数的前缀 @Accessors(prefix = "abc") private String abcAge 当生成get/set方法时,会把此前缀去掉
例如;
@Setter
@Getter
@Accessors(fluent = true, chain = true)
public class TestBean {
@NonNull
private Integer id;
@NonNull
private String name;
// 使用@Accessors(prefix = "abc") 区分大小写
@Accessors(prefix = "abc")
private int abcFa;
}
对应生成的.class 为: 函数不带set 链式调用且带有前缀的 去掉了前缀abc
public class TestBean {
@NonNull
private Integer id;
@NonNull
private String name;
private int abcFa;
public TestBean() {
}
public TestBean id(@NonNull Integer id) {
if(id == null) {
throw new NullPointerException("id");
} else {
this.id = id;
return this;
}
}
public TestBean name(@NonNull String name) {
if(name == null) {
throw new NullPointerException("name");
} else {
this.name = name;
return this;
}
}
public void setFa(int abcFa) {
this.abcFa = abcFa;
}
@NonNull
public Integer id() {
return this.id;
}
@NonNull
public String name() {
return this.name;
}
public int getFa() {
return this.abcFa;
}
}
注意:使用@Accessors(prefix = "abc")private int abcFa; 要修饰带有指定前缀的参数 并前缀与后参数名区分大小写
7 @Synchronized 修饰方法 给该方法加同步锁
如
@Synchronized
private void initModel() {
TestBean testBean = new TestBean();
testBean.name("测试");
Toast.makeText(this, testBean.name(), Toast.LENGTH_SHORT).show();
}
对应的.class 为
public class MainActivity extends AppCompatActivity {
private final Object $lock = new Object[0];
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131296283);
this.initModel();
}
private void initModel() {
Object var1 = this.$lock;
synchronized(this.$lock) {
TestBean testBean = new TestBean();
testBean.name("测试");
Toast.makeText(this, testBean.name(), 0).show();
}
}
}
// 使用指定对象锁
private final Object lock = new Object();
@Synchronized("lock")
private void initModel() {
TestBean testBean = new TestBean();
testBean.name("测试");
Toast.makeText(this, testBean.name(), Toast.LENGTH_SHORT).show();
}
对应的.class
public class MainActivity extends AppCompatActivity {
private final Object lock = new Object();
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131296283);
this.initModel();
}
private void initModel() {
Object var1 = this.lock;
synchronized(this.lock) {
TestBean testBean = new TestBean();
testBean.name("测试");
Toast.makeText(this, testBean.name(), 0).show();
}
}
}
8@Wither 提供修改final字段的方法 【实际上是判断newValue 是否和oldValue一样,,不一样重新new对象传新值,返回,达到修改final修饰值的目的】(貌似只能是修改int型)
例如:
public class TestBean {
@Wither
private final int id;
@Wither(AccessLevel.PROTECTED)
private final int name;
public TestBean(int name, int id) {
this.name = name;
this.id = id;
}
}
生成对应的.class 为, 里边是用== 进行判断的
public class TestBean {
private final int id;
private final int name;
public TestBean(int name, int id) {
this.name = name;
this.id = id;
}
public TestBean withId(int id) {
return this.id == id?this:new TestBean(id, this.name);
}
protected TestBean withName(int name) {
return this.name == name?this:new TestBean(this.id, name);
}
}
使用上为 实际返回了一个新对象
TestBean testBean = new TestBean(1,2);
testBean.withId(4);
9 @Getter(onMethod = @_("新注解")) 注解里加注解 适用于结合数据库
例如:
public class Test implements Serializable {
private static final long serialVersionUID = -196412797757026250L;
@Getter(onMethod = @_(@Column(name = "available")))
@Setter
private Integer available = 1;
}
10 @Builder 创建一个静态内部类, 使用该类可以使用链式调用创建对象
如
@Builder
public class TestBean {
private int id;
private int name;
}
对应的.class 为 通过构造链式获取实例
public class TestBean {
private int id;
private int name;
TestBean(int id, int name) {
this.id = id;
this.name = name;
}
public static TestBean.TestBeanBuilder builder() {
return new TestBean.TestBeanBuilder();
}
public static class TestBeanBuilder {
private int id;
private int name;
TestBeanBuilder() {
}
public TestBean.TestBeanBuilder id(int id) {
this.id = id;
return this;
}
public TestBean.TestBeanBuilder name(int name) {
this.name = name;
return this;
}
public TestBean build() {
return new TestBean(this.id, this.name);
}
public String toString() {
return "TestBean.TestBeanBuilder(id=" + this.id + ", name=" + this.name + ")";
}
}
}
使用为:
TestBean testBean =TestBean.builder().id(2).name(1).build();
11 @Delegate 修饰集合list set map 都可以 生成相关的方法 判空 个数等
public class TestBean {
private int id;
private int name;
// private Set<String> strings = new HashSet<>();
@Delegate
private Map<String, Integer> map = new HashMap<>();
}
对应的.class 文件为: 省略很多方法
public class TestBean {
private int id;
private int name;
private Map<String, Integer> map = new HashMap();
public TestBean() {
}
public int size() {
return this.map.size();
}
public boolean isEmpty() {
return this.map.isEmpty();
}
public boolean containsKey(Object arg0) {
return this.map.containsKey(arg0);
}
public boolean containsValue(Object arg0) {
return this.map.containsValue(arg0);
}
public Integer get(Object arg0) {
return (Integer)this.map.get(arg0);
}
public Integer put(String arg0, Integer arg1) {
return (Integer)this.map.put(arg0, arg1);
}
public Integer remove(Object arg0) {
return (Integer)this.map.remove(arg0);
}
public void putAll(Map<? extends String, ? extends Integer> arg0) {
this.map.putAll(arg0);
}
public void clear() {
this.map.clear();
}
省略。。。。。。。
}
12 @NonNull
在方法或构造函数的参数上使用@NonNull,lombok将生成一个空值检查语句。【如果为null会抛出异常 ,方便找到哪里为空】
例如:
private void initModel(@NonNull String params) {
params.equals("1");
}
生成对应的.class为: 为null 抛出哪里为null 的异常
private void initModel(@NonNull String params) {
if(params == null) {
throw new NullPointerException("params");
} else {
params.equals("1");
}
}
13 @Cleanup 修饰io对象 自动释放io资源
如:
public void example() throws IOException {
@Cleanup InputStream in = new FileInputStream("2");
@Cleanup OutputStream out = new FileOutputStream("2");
byte[] b = new byte[10000];
while (true) {
int r = in.read(b);
if (r == -1) break;
out.write(b, 0, r);
}
}
对应的.class 为: 省去自己在finally里回收资源
public void example() throws IOException {
FileInputStream in = new FileInputStream("2");
try {
FileOutputStream out = new FileOutputStream("2");
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();
}
}
}
14 @Value 功能和 @Data类似 都是生成 getter toString()、equals()和hashCode() 会把类和参数声明称final的
如
@Value
@RequiredArgsConstructor(suppressConstructorProperties = true)
public class TestBean {
private int id;
private int name;
private Map<String, Integer> map = new HashMap<>();
}
对应的.class为 生成的代码比较长 直接上方法截图
15@SneakyThrows 只是伪造编译器。在JVM(类文件)级别上,不管您的方法的抛出子句,都可以抛出所有检查或不检查的异常
官方例子:修饰抛出异常的方法
@SneakyThrows
public void testSneakyThrows() {
throw new IllegalAccessException();
}
对应的.class为:
public void testSneakyThrows() {
try {
throw new IllegalAccessException();
} catch (java.lang.Throwable $ex) {
throw lombok.Lombok.sneakyThrow($ex);
}
}
16@Getter(lazy=true) 缓存参数值(getter方法计算值需要大量CPU,或者值占用大量内存,第一次调用这个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;
}
}
对应的.class为:
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;
}
}
注意事项: 依赖 compile 'javax.annotation:javax.annotation-api:1.2'compile 'org.projectlombok:lombok:1.16.8'
在使用@Data 或者 @RequiredArgsConstructor and @AllArgsConstructor 有时会报
解决办法:
@AllArgsConstructor(suppressConstructorProperties = true) 或者
@RequiredArgsConstructor(suppressConstructorProperties = true)
LomBok原理分析:
接下来进行lombok能够工作的原理分析,以Oracle的javac编译工具为例。
自从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本质上就是这样的一个实现了"JSR 269 API"的程序。在使用javac的过程中,它产生作用的具体流程如下:
1)javac对源代码进行分析,生成一棵抽象语法树(AST)
2)运行过程中调用实现了"JSR 269 API"的lombok程序
3)此时lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点
4)javac使用修改后的抽象语法树(AST)生成字节码文件