如何使用泛型实现构建器类,而不是注释? [英] How to implement a builder class using Generics, not annotations?

查看:114
本文介绍了如何使用泛型实现构建器类,而不是注释?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想编写一个通用的构建器类,它包装任何java类并提供特定样式的setter函数。我不确定这是否可以称为动态生成的函数。



当我有一个beanish Pojo类,即

  class Pojo {
public void setValue(int value){...}
public void setName(String name){...}
}

我的 Maker 类应该可用像这样:

  Pojo p = Builder< Pojo> .create(new Pojo())
.setName(有趣)
.setValue(123)
.build();

正如您所看到的,它所做的工作应该类似于

  class PojoBuilder {
private pojo pojo;
PojoBuilder(Pojo pojo){this.pojo = pojo; }
public static PojoMaker create(Pojo p){return new PojoBuilder(p); }
public PojoBuilder setName(String name){pojo.setName(name);返回这个; }
public PojoBuilder setValue(int val){pojo.setValue(val);返回这个; }
public Pojo make(){return pojo; }
}

只有 Maker 是通用的。显然,setXyz方法取决于泛型的参数。如何做到这一点?



当然,功能上相同但语法不同的方法也很好。



D喜欢做它没有注释:有了注释我收集我需要第二个javac通过我的源代码,生成包装代码。这似乎是 Limbok 所做的或某些 JPA包装器的工作原理。但是当我使用Mockito 时,似乎这个传球并不是必要的。那么,我该怎么做才能用

解决方案

直到某一点(我没有测试像原始类型等特殊情况)。



它使用Java 8,lamdba和type erasure。



由于在Java 8中可以使用X :: new引用构造函数,而使用相同语法的方法可以通过在地图中堆叠每个方法及其参数来工作, t依赖于一个特定的实例(这样build()可以创建Foobar的新实例)。

  package foobar; 

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Supplier;

公共类别Maker< T> {
私人最终供应商<延伸T>供应商;
/ **
*我们需要存储实例,因为构建必须返回一个新实例。
*< p>
*可悲的是,我们需要依赖于类型擦除(因此BiConsumer,而不是BiConsumer< T,V>)。
* /
@SuppressWarnings(rawtypes)
private final Map< BiConsumer,Object> values = new HashMap<>();

公共制造商(最终供应商<?延伸T>供应商){
this.supplier =供应商;
}

public static< T>壶< T>创建(最终供应商< ;? extends T>构建器){
返回新的Maker<>(构建器);
}

public< U>壶< T> (最终BiConsumer< T,U>消费者,最终U值){
values.put(consumer,value);
返回此;
}

@SuppressWarnings(unchecked)
public T create(){
final T instance = supplier.get(); ((key,value) - > {
key.accept(instance,value);
});

values.forEach

返回实例;
}

public static void main(final String [] args){
final Maker< Foobar> maker = Maker.create(Foobar :: new).set(Foobar :: setName,Name);

final AtomicInteger generator = new AtomicInteger(0);
Arrays.asList(Alpha,Beta,Gamma)。forEach(name - > {
final Integer id = generator.incrementAndGet();

maker.set(Foobar :: setName,name);
maker.set(Foobar :: setId,id);
final Foobar foobar = maker.create();

if(!name.equals(foobar.getName())){
throw new AssertionError(expected+ name +,got+ foobar.getName());
}
if(!id.equals(foobar.getId())){
throw new AssertionError(expected+ id +,got+ foobar.getId());
}

System.out.println(foobar);

});


$ b $ / code $ / pre
$ b $ p用Foobar类: p>

  public class Foobar {
private Integer id;
私人字符串名称;
public Integer getId(){return id;}
public void setId(final Integer id){this.id = id;} $ b $ public String getName(){return name;
public void setName(final String name){this.name = name;}
@Override public String toString(){
returnFoobar [id =+ id +,name = + name +];
}
}


I want to write a generic builder class which wraps around any java class and providing setter functions of a specific style. I am not sure if this could be called "dynamically generated functions".

When I have a beanish Pojo class i.e.

class Pojo {
    public void setValue(int value) {...}
    public void setName(String name) {...}
}

My Maker class should be usable like this:

Pojo p = Builder<Pojo>.create(new Pojo())
             .setName("Funny")
             .setValue(123)
             .build();

As you can see, the work it does should be similar to

class PojoBuilder {
    private Pojo pojo;
    PojoBuilder(Pojo pojo) { this.pojo = pojo; }
    public static PojoMaker create(Pojo p) { return new PojoBuilder(p); }
    public PojoBuilder setName(String name) { pojo.setName(name); return this; }
    public PojoBuilder setValue(int val) { pojo.setValue(val); return this; }
    public Pojo make() { return pojo; }
}

Only, I would like Maker to be generic. Obviously, the "setXyz"-Methods depend on the generic argument. How to do that?

Of course, functionally equivalent but syntactically different approach is also fine.

I'd like to do it without annotations: With annotations I gather I'd need a second javac-pass over my source code, generating the wrapper code. That seems to be what Limbok does or how some JPA wrappers work. But when I work with Mockito it seems that this pass is not necessary. So, How can I do it with Generics?

解决方案

The following code shows that it is possible up to a certain point (I did not test for corner case such as primitive type, etc).

It use Java 8, lamdba and type erasure.

Since in Java 8 you can reference constructor using X::new, and method using the same syntax, it works by stacking into a map each method and their parameter, so that we don't rely on a particular instance (so that build() can create new instance of Foobar).

package foobar;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Supplier;

public class Maker<T> {
  private final Supplier<? extends T> supplier;
  /**
   * We need to store the instance since build must return a new instance.
   * <p>
   * Sadly, we need to rely on type erasure (hence BiConsumer, not BiConsumer<T,V>).
   */
  @SuppressWarnings("rawtypes")
  private final Map<BiConsumer, Object> values = new HashMap<>();

  public Maker(final Supplier<? extends T> supplier) {
    this.supplier = supplier;
  }

  public static <T> Maker<T> create(final Supplier<? extends T> builder) {
    return new Maker<>(builder);
  }

  public <U> Maker<T> set(final BiConsumer<T, U> consumer, final U value) {
    values.put(consumer, value);
    return this;
  }

  @SuppressWarnings("unchecked")
  public T create() {
    final T instance = supplier.get();

    values.forEach((key, value) -> {
      key.accept(instance, value);
    });

    return instance;
  }

  public static void main(final String[] args) {
    final Maker<Foobar> maker = Maker.create(Foobar::new).set(Foobar::setName, "Name");

    final AtomicInteger generator = new AtomicInteger(0);
    Arrays.asList("Alpha", "Beta", "Gamma").forEach(name -> {
      final Integer id = generator.incrementAndGet();

      maker.set(Foobar::setName, name);
      maker.set(Foobar::setId, id);
      final Foobar foobar = maker.create();

      if (!name.equals(foobar.getName())) {
        throw new AssertionError("expected " + name + ", got " + foobar.getName());
      }
      if (!id.equals(foobar.getId())) {
        throw new AssertionError("expected " + id + ", got " + foobar.getId());
      }

      System.out.println(foobar);

    });

  }          
}

With the Foobar class:

public class Foobar {
  private Integer id;
  private String name;    
  public Integer getId() {return id;}
  public void setId(final Integer id) {this.id = id;}    
  public String getName() {return name;
  public void setName(final String name) {this.name = name;}    
  @Override public String toString() {
    return "Foobar [id=" + id + ", name=" + name + "]";
  }    
}

这篇关于如何使用泛型实现构建器类,而不是注释?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆