使用lombok编写优雅的Bean对象
使用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 of
com.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。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Go 定时器内部实现原理剖析
前言 前面我们介绍了一次性定时器Timer和周期性定时器Ticker,这两种定时器内部实现机制相同。创建定时器的协程并不负责计时,而是把任务交给系统协程,系统协程统一处理所有的定时器。 本节,我们重点关注系统协程是如何管理这些定器的,包括以下问题: 定时器使用什么数据结构存储? 定时器如何触发事件? 定时器如何添加进系统协程? 定时器如何从系统协程中删除? 定时器存储 timer数据结构 Timer和Ticker数据结构除名字外完全一样,二者都含有一个runtimeTimer类型的成员,这个就是系统协程所维护的对象。 runtimeTimer类型是time包的名称,在runtime包中,这个类型叫做timer。 timer数据结构如下所示: type timer struct { tb *timersBucket // the bucket the timer lives in // 当前定时器寄存于系统timer堆的地址 i int // heap index // 当前定时器寄存于系统timer堆的下标 when int64 // 当前定时器下次触发时间 period int64 ...
- 下一篇
死磕 java集合之LinkedBlockingQueue源码分析
问题 (1)LinkedBlockingQueue的实现方式? (2)LinkedBlockingQueue是有界的还是无界的队列? (3)LinkedBlockingQueue相比ArrayBlockingQueue有什么改进? 简介 LinkedBlockingQueue是java并发包下一个以单链表实现的阻塞队列,它是线程安全的,至于它是不是有界的,请看下面的分析。 源码分析 主要属性 // 容量 private final int capacity; // 元素数量 private final AtomicInteger count = new AtomicInteger(); // 链表头 transient Node<E> head; // 链表尾 private transient Node<E> last; // take锁 private final ReentrantLock takeLock = new ReentrantLock(); // notEmpty条件 // 当队列无元素时,take锁会阻塞在notEmpty条件上,等待其它线程...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Red5直播服务器,属于Java语言的直播服务器
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS关闭SELinux安全模块
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS7安装Docker,走上虚拟化容器引擎之路