文档章节

使用lombok编写优雅的Bean对象

polly
 polly
发布于 04/22 22:17
字数 1949
阅读 3359
收藏 155

使用java编写代码,十之八九都是在写java类,从而构建java对象。lombok之前也说了不少,但使用了这么多年,感觉还是有很多技巧可以使用的。

毫无疑问,使用lombok,编写的java代码很优雅,而使用起来和普通的java编码方式创建的类毫无二致。

不过,这样就满足了吗?实际上lombok很多注解,让这个java类在使用的时候,也可以更优雅。

本文就从ORM实体类、Builder模式工具类、Wither工具类以及Accessors工具类几个层面对比一下。

首先说明,不同的方式本质上没有优劣之分,不过在不同的应用场景就会变得很奇妙了。

ORM实体类

当一个java Bean类作为ORM实体类,或者xml、json的映射类时,需要这个类有这几个特征:

  • 拥有无参构造器
  • 拥有setter方法,用以反序列化;
  • 拥有getter方法,用以序列化。

那么最简单的情况就是:

@Data
public class UserBean{
  private Integer id;
  private String userName;
}
  • 复习一下,Data 注解相当于装配了 @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode

那么,作为实体类、或者序列化的Bean类,足够用了。

Builder

构造器模式,在很多工具类中频繁的使用。

package com.pollyduan.builder;

import lombok.Builder;

@Builder
public class UserBean {
	  private Integer id;
	  private String userName;
}

它做了什么事?

  • 它创建了一个private 的全参构造器。也就意味着 无参构造器没有; 同时也意味着这个类不可以直接构造对象。
  • 它为每一个属性创建了一个同名的方法用于赋值,代替了setter,而该方法的返回值为对象本身。

使用一下:

UserBean u=UserBean.builder()
	.id(1001)
	.userName("polly")
	.build();
System.out.println(u);

还不错,然并卵,由于这个Bean并没有getter方法,里边的数据没办法直接使用。光说没用,继续执行你会发现输出是这个东西:com.pollyduan.builder.UserBean@20322d26,连看都看不出是什么东东。因此,Builder提供了一个种可能性,实际使用还需要更多的东西。

那么,我们为了测试方便需要添加 @ToString() 注解,就会输出 UserBean(id=1001, userName=polly)

换一个思路,你可能想,我不添加ToString注解,我把他转成json输出:

UserBean u=UserBean.builder()
	.id(1001)
	.userName("polly")
	.build();
ObjectMapper mapper=new ObjectMapper();
System.out.println(mapper.writeValueAsString(u));

很不幸,你会收到下面的异常:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.pollyduan.builder.UserBean and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

看到 no properties discovered 了吧,没错,工具类无法找到属性,因为没有 getter,那么我们加上 @Getter 就可以了。

package com.pollyduan.builder;

import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
public class UserBean {
	  private Integer id;
	  private String userName;
}

序列化为json可以了,那么从一个json反序列化为对象呢?

@Builder
@Getter
@Setter
public class UserBean {
	  private Integer id;
	  private String userName;
}

还是不行,如无意外,会遇到 com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance ofcom.pollyduan.builder.UserBean(no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

前面已经交代了,光增加 @Setter 还不够,他还需要一个无参构造器。那么,下面可以吗?

package com.pollyduan.builder;

import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class UserBean {
	  private Integer id;
	  private String userName;
}

同样不行,因为虽然 Data使用的时候可以直接使用无参构造器,但由于 Builder 引入了全参构造器,那么按照java原生的规则,无参构造器就没有了。那么就再加一个无参构造器

@Builder
@Data
@NoArgsConstructor

很不幸,Builder又报错了,它找不到全参构造器了。好吧,最终的效果如下:

@Builder
@Data
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
  • 注意全参构造器的访问级别,不要破坏Builder的规则。

更进一步,看如下示例:

package com.pollyduan.builder;

import java.util.List;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
public class UserBean {
	  private Integer id;
	  private String userName;
	  private List<String> addresses;
}

思考一下,这个List 我们也需要在外面new 一个 ArrayList,然后build进去,使用起来并不舒服。lombok提供了另一个注解配合使用,那就是 @Singular,如下:

package com.pollyduan.builder;

import java.util.List;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
public class UserBean {
	  private Integer id;
	  private String userName;
	  @Singular
	  private List<String> favorites;
}

那么就可以这样操作这个列表了。

UserBean u = UserBean.builder()
	.id(1001)
	.userName("polly")
	.favorite("music")
	.favorite("movie")
	.build();

是不是很方便?同时还提供了一个 clearXXX方法,清空集合。

还有一个小坑,如果我们增加一个example属性,然后给它一个默认值:

@Builder
@Data
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
public class UserBean {
	  private Integer id;
	  private String userName;
	  private String example="123456";
}

测试一下看:

UserBean u = UserBean.builder()
	.id(1001)
	.userName("polly")
	.build();
System.out.println(u.toString());

输出结果:UserBean(id=1001, userName=polly, example=null)

咦,不对呀?缺省值呢??这要从Builder的原理来解释,他实际上是分别设置了一套属性列表的值,然后使用全参构造器创建对象。那么,默认值在Bean上,不在Builder上,那么Builder没赋值,它的值就是null,最后把所有属性都复制给UserBean,从而null覆盖了默认值。

如何让Builder实体来有默认值呢?只需要给该字段增加 @Default 注解级可。

package com.pollyduan.builder;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Builder.Default;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
public class UserBean {
	  private Integer id;
	  private String userName;
	  @Default
	  private String example="123456";
}

重新执行测试用例,输出:UserBean(id=1001, userName=polly, example=123456),OK,没毛病了。

Wither

用wither方式构建对象,这在Objective-C 中比较多见。

适用的场景是,使用几个必要的参数构建对象,其他参数,动态的拼装。举个例子,我们构建一个ApiClient,它的用户名和密码是必须的,他的ApiService的地址有一个默认值,然后我们可以自己定制这个地址。

package com.pollyduan.wither;

import lombok.AllArgsConstructor;
import lombok.experimental.Wither;

@Wither
@AllArgsConstructor //WITHER NEED IT.
public class ApiClient {
	private String appId;
	private String appKey;
	private String endpoint="http://api.pollyduan.com/myservice";
}

如何使用呢?

@Test
public void test1() {
	ApiClient client1=new ApiClient(null, null,null);
	System.out.println(client1);

	Object client2 = client1.withAppId("10001")
		.withAppKey("abcdefg")
		.withEndpoint("http://127.0.0.1/");
	System.out.println(client2);
}

默认的使用null去初始化一个对象还是很奇怪的。和 Builder一样,Wither也是提供了可能性,实际使用还需要调整一下。

我们可以设置一个必选参数的构造器,如下:

package com.pollyduan.wither;

import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Wither;

@RequiredArgsConstructor
@Wither
@AllArgsConstructor
public class ApiClient {
	@NonNull
	private String appId;
	@NonNull
	private String appKey;
	private String endpoint="http://api.pollyduan.com/myservice";
}

这样使用时就可以这样:

	@Test
	public void test1() {
		ApiClient client1=new ApiClient("10001", "abcdefgh");
		System.out.println(client1);
		
		Object client2 = client1.withEndpoint("http://127.0.0.1/");
		System.out.println(client2);
	}

是不是优雅了很多?而且实际上使用时也使用链式语法:

ApiClient client1=new ApiClient("10001", "abcdefgh")
	withEndpoint("http://127.0.0.1/");

另外还有一个小细节,前面的示例输出如下:

com.pollyduan.wither.ApiClient@782830e
com.pollyduan.wither.ApiClient@470e2030

这个日志表明,with() 返回的对象并不是原来的对象,而是一个新对象,这很重要。

Accessors

访问器模式,是给一个普通的Bean增加一个便捷的访问器,包括读和写。

它有两种工作模式,fluent和chain,举例说明:

package com.pollyduan.accessors;

import lombok.Data;
import lombok.experimental.Accessors;

@Accessors(fluent = true)
@Data
public class UserBean {
	private Integer id;
	private String userName;
	private String password;
	
}

使用代码:

UserBean u=new UserBean()
	.id(10001)
	.userName("polly")
	.password("123456");

u.userName("Tom");
System.out.println(u.userName());

这和 Builder 类似,但更小巧,而且不影响属性的读写,只不过使用属性同名字符串代替了getter和setter。

另一个模式是 chain:

UserBean u=new UserBean()
	.setId(10001)
	.setUserName("polly")
	.setPassword("123456");

u.setUserName("Tom");
System.out.println(u.getUserName());

可以看得出来,这fluent的区别就在于使用getter和setter。

© 著作权归作者所有

polly

polly

粉丝 158
博文 66
码字总数 76732
作品 0
海淀
架构师
私信 提问
加载中

评论(44)

力软快速开发平台
力软快速开发平台
代替重复写代码时间,节省没必要的时间,人应该去做更有意义的事。
_vince
_vince

引用来自“_vince”的评论

引用来自“polly”的评论

引用来自“_vince”的评论

我觉得有必要指正下..那些缺少get或者无参构造方法,不是这个插件的bug,我在开发中依赖注入也有遇到无参构造无法注入的情况,但是不用lombok,无参构造也需要自己写,,但是我发现一个bug,就是构造参数要不全部使用要不就构造参数自己写,不能两者一起用,不然这个就真的会找不到
你看看 @RequiredArgsConstructor

@polly这个构造方法也有用,运行是没有问题,你尝试一个构造是注解一个是代码编写,Maven会无法编译通过

引用来自“polly”的评论

无法复现,测试代码:
package com.pollyduan.demo;

import java.time.LocalDateTime;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class UserTest {
  
  public UserTest(Integer id, String user, String pass, LocalDateTime birth) {
    super();
    this.id = id;
    this.user = user;
    this.pass = pass;
    this.birth = birth;
  }
  public UserTest(Integer id, String user) {
    super();
    this.id = id;
    this.user = user;
  }
  private Integer id;
  private String user;
  private String pass;
  private LocalDateTime birth;
}

mvn clean package -DskipTests
...
[INFO] BUILD SUCCESS

引用来自“polly”的评论

或者,只写 @AllArgsConstructor,加上一个: public UserTest(Integer id, String user) 同样无法复现你说的问题
运行可以哦..但是 mvn clean install 有问题
polly
polly

引用来自“formever”的评论

@Accessors(fluent = true)
@Data
使用这个注释怎么在对象里面取到值

引用来自“polly”的评论

demo里有,就是:
System.out.println(u.userName());

跟属性名同名的无参方法,就是getter,有参方法就是setter

引用来自“formever”的评论

这个我试了,取单独某个值可以,但是看这个对象,结果还是没有值,json格式化之后发现之前注入的值都是空的
如果你需要json序列化,或者映射ORM的实体,那么这个模式不适合,因为序列化或orm的时候,在文章开头就说明了,需要标准的getter/setter,因此你可以考虑 chain
formever
formever

引用来自“formever”的评论

@Accessors(fluent = true)
@Data
使用这个注释怎么在对象里面取到值

引用来自“polly”的评论

demo里有,就是:
System.out.println(u.userName());

跟属性名同名的无参方法,就是getter,有参方法就是setter
这个我试了,取单独某个值可以,但是看这个对象,结果还是没有值,json格式化之后发现之前注入的值都是空的
polly
polly

引用来自“formever”的评论

@Accessors(fluent = true)
@Data
使用这个注释怎么在对象里面取到值
demo里有,就是:
System.out.println(u.userName());

跟属性名同名的无参方法,就是getter,有参方法就是setter
formever
formever
@Accessors(fluent = true)
@Data
使用这个注释怎么在对象里面取到值
polly
polly

引用来自“_vince”的评论

引用来自“polly”的评论

引用来自“_vince”的评论

我觉得有必要指正下..那些缺少get或者无参构造方法,不是这个插件的bug,我在开发中依赖注入也有遇到无参构造无法注入的情况,但是不用lombok,无参构造也需要自己写,,但是我发现一个bug,就是构造参数要不全部使用要不就构造参数自己写,不能两者一起用,不然这个就真的会找不到
你看看 @RequiredArgsConstructor

@polly这个构造方法也有用,运行是没有问题,你尝试一个构造是注解一个是代码编写,Maven会无法编译通过

引用来自“polly”的评论

无法复现,测试代码:
package com.pollyduan.demo;

import java.time.LocalDateTime;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class UserTest {
  
  public UserTest(Integer id, String user, String pass, LocalDateTime birth) {
    super();
    this.id = id;
    this.user = user;
    this.pass = pass;
    this.birth = birth;
  }
  public UserTest(Integer id, String user) {
    super();
    this.id = id;
    this.user = user;
  }
  private Integer id;
  private String user;
  private String pass;
  private LocalDateTime birth;
}

mvn clean package -DskipTests
...
[INFO] BUILD SUCCESS
或者,只写 @AllArgsConstructor,加上一个: public UserTest(Integer id, String user) 同样无法复现你说的问题
polly
polly

引用来自“_vince”的评论

引用来自“polly”的评论

引用来自“_vince”的评论

我觉得有必要指正下..那些缺少get或者无参构造方法,不是这个插件的bug,我在开发中依赖注入也有遇到无参构造无法注入的情况,但是不用lombok,无参构造也需要自己写,,但是我发现一个bug,就是构造参数要不全部使用要不就构造参数自己写,不能两者一起用,不然这个就真的会找不到
你看看 @RequiredArgsConstructor

@polly这个构造方法也有用,运行是没有问题,你尝试一个构造是注解一个是代码编写,Maven会无法编译通过
无法复现,测试代码:
package com.pollyduan.demo;

import java.time.LocalDateTime;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class UserTest {
  
  public UserTest(Integer id, String user, String pass, LocalDateTime birth) {
    super();
    this.id = id;
    this.user = user;
    this.pass = pass;
    this.birth = birth;
  }
  public UserTest(Integer id, String user) {
    super();
    this.id = id;
    this.user = user;
  }
  private Integer id;
  private String user;
  private String pass;
  private LocalDateTime birth;
}

mvn clean package -DskipTests
...
[INFO] BUILD SUCCESS
w
wailouci

引用来自“wailouci”的评论

引用来自“cc_z”的评论

我上个评论其实不正确,用lambda简写匿名类不错,但闭包不是很有必要,拓展成stream就更糟糕了。

@cc_z java大神的新特性都让说成糟糕了👍🏻

@wailouci 全世界都在函数化异步化,却说lambda糟糕,全世界开发者都是傻瓜?
w
wailouci

引用来自“cc_z”的评论

我上个评论其实不正确,用lambda简写匿名类不错,但闭包不是很有必要,拓展成stream就更糟糕了。

@cc_z java大神的新特性都让说成糟糕了👍🏻
从今天起让我们忘记Java中的get/set方法吧!

曾几何时,我们写代码的时候,每次写Bean的时候都会使用快捷键生成get/set方法,有时候我经常会想,既然每一个Bean我们都会给其提供get/set方法,那么为什么还有会这个东西哪?(当然这只是一...

编程SHA
03/16
0
0
以简单的方式消除 Java 的冗长(Lombok)

复制的红薯的,自己只是做一个归档,方便自己看。 ------------------------------------------------------------------------------------ Lombok 是一种 Java Archive (JAR) 文件,可用来...

qqli
2012/12/27
0
0
以简单的方式消除 Java 的冗长

Lombok 是一种 Java Archive (JAR) 文件,可用来消除 Java 代码的冗长。 我们看这样一个例子,一个标准的 Java bean。一个典型的 Java bean 一般具有几个属性。每个属性具有一个 accessor 和...

红薯
2010/04/13
1K
6
优雅的Java工具库Lombok

优雅的Java工具库Lombok 最近在公司的项目中看到了对于Lombok的应用,通过@Data注解标注POJO,省略了大量的getter/setter代码,原先冗长的POJO在瘦身之后直接变得干净、清爽,程序员再也不需...

wxiaoqi
2018/08/19
0
0
Intellij IDEA 安装lombok及使用详解

项目中经常使用bean,entity等类,绝大部分数据类类中都需要get、set、toString、equals和hashCode方法,虽然eclipse和idea开发环境下都有自动生成的快捷方式,但自动生成这些代码后,如果b...

zhglance
2017/11/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

对集合的理解

开端 同事小G提了一点,Set都是无序的,但是我之前有看到过treeSet是有序的,就和他讨论了起来,还百度了一下,有序。然而他只是淡淡的说自己敲代码验证一下。 TreeSet 循环int类型 1~20,毫...

无极之岚
31分钟前
0
0
Kernel字符设备驱动框架

Linux设备分为三大类:字符设备,块设备和网络设备,这三种设备基于不同的设备框架。相较于块设备和网络设备,字符设备在kernel中是最简单的,也是唯一没有基于设备基础框架(device结构)的...

yepanl
35分钟前
0
0
Ajax

定义 Ajax是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术,用于创建动态网页 Ajax=Asynchronous Javascript And XML(异步的JavaScript和XML) 原理 XMLHttpRequest对象(异...

星闪海洋
昨天
2
0
Jenkins 中文本地化的重大进展

本文首发于:Jenkins 中文社区 我从2017年开始,参与 Jenkins 社区贡献。作为一名新成员,翻译可能是帮助社区项目最简单的方法。 本地化的优化通常是较小的改动,你无需了解项目完整的上下文...

Jenkins中文社区
昨天
3
0
Spring中如何使用设计模式

关于设计模式,如果使用得当,将会使我们的代码更加简洁,并且更具扩展性。本文主要讲解Spring中如何使用策略模式,工厂方法模式以及Builder模式。 1. 策略模式 关于策略模式的使用方式,在S...

爱宝贝丶
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部