使用lombok编写优雅的Bean对象

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

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

不过,这样就知足了吗?实际上lombok不少注解,让这个java类在使用的时候,也能够更优雅。api

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

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

ORM实体类

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

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

那么最简单的状况就是:ui

@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;
}

它作了什么事?code

  • 它建立了一个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。

相关文章
相关标签/搜索