4

Let's assume we have a very simple class:

public class User {

    private Long id;
    private String name;

    public void setId(Long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

}

I'm doing a simple binder class to populate a user and would like to signal the required properties.

UserBinder binder = new UserBinder(user)
    .requires(User::setId)
    .requires(User::setName);

The above would signal that both properties are required. Of course I could pass the values "id" and "name" instead of the method references. But it seems that passing the setter is very appropriate since that setter needs to be called inside the binder.

The following works great:

public void requires(BiConsumer<User, Long> r) // for User::setId;
public void requires(BiConsumer<User, String> r) // for User::setName;

But the following fails:

public void requires(BiConsumer<User, ?> r)

Is there a type that would hold all possible setter references of java class regardless of the type being set?

Ricardo Marimon
  • 10,339
  • 9
  • 52
  • 59
  • 1
    Bluntly: this is probably impossible. You _cannot_ assume one `BiConsumer` is ever equal to another, even if both are entirely equivalent. You can't put them in a set, you can't `==` on them, you can't introspect them to see what method they represent. It's a nice idea, but you almost certainly won't be able to make it work -- and, even worse, it might _look like_ it works but not actually work. – Louis Wasserman Feb 09 '19 at 18:03

2 Answers2

4

You could bind type possibly using :

<T> void requires(BiConsumer<User, T> r) {
}

Also, if you were to mandate just these parameters you could have created the custom constructor for the required fields as :

public User(Long id, String name)

to ensure the instantiation of the object must have these attributes.

Naman
  • 27,789
  • 26
  • 218
  • 353
2

You can add a generic parameter to the requires method:

public <T> void requires(BiConsumer<User, T> consumer)

Now this:

new UserBinder(new User())
    .requires(User::setName);

compiles fine.


You told that you need to store received consumers in some collection and check if the collection already contains some consumer

Unfortunately you can't do it easily (see this question).

As a workaround you can create a @FunctionalInterface extending Serializable with the same method signature as in the BiConsumer and compare serialized bytes. See this answer. But remember that you can't rely on this behavior

Denis Zavedeev
  • 7,627
  • 4
  • 32
  • 53
  • Got that far. But what if I want to keep track of the consumers storing in a set? The set would have to a signature of `Set>` but then checking `set.contains(User::setId)` does not compile. – Ricardo Marimon Feb 09 '19 at 15:58
  • @RicardoMarimon you can use signature `Set>`, but seems it won't help in your case because comparing lambdas is not so easy (see this [question](https://stackoverflow.com/questions/24095875/is-there-a-way-to-compare-lambdas)). Recently I was thinking about the same design but unfortunately seems it is not so easy to implement. Maybe you can consider using [hibernate validator](http://hibernate.org/validator/) in conjunction with your binder? Or maybe you can elaborate more about your requirements on `Binder` so I could suggest something else? – Denis Zavedeev Feb 09 '19 at 16:09
  • thanks for the reference to the question. I was under the impression that you could compare lambdas and from what I have read, there are no guarantees. Maybe in the case of methods references that I'm using. I'm going to explore further since I got it to work with `public boolean isRequired(BiConsumer consumer) { return requires.contains(consumer); }` with a `Set`. – Ricardo Marimon Feb 09 '19 at 16:17
  • @RicardoMarimon `set.contains(User::setId)` does not compile but `set.contains((BiConsumer) User::setId)` compiles fine – Denis Zavedeev Feb 09 '19 at 18:24