34

Suppose there is a typical Java Bean:

class MyBean {
    void setA(String id) {
    }

    void setB(String id) { 
    }

    List<String> getList() {
    }
}

And I would like to create a more abstract way of calling the setters with the help of a BiConsumer:

Map<SomeEnum, BiConsumer<MyBean, String>> map = ...
map.put(SomeEnum.A, MyBean::setA);
map.put(SomeEnum.B, MyBean::setB);
map.put(SomeEnum.List, (myBean, id) -> myBean.getList().add(id));

Is there a way to replace the lambda (myBean, id) -> myBean.getList().add(id) with a chained method reference, something like (myBean.getList())::add or myBean::getList::add or something else?

Random42
  • 8,989
  • 6
  • 55
  • 86
  • What doest `map.put(SomeEnum.List, (myBean, id) -> myBean.getList().add(id));` do? – Andremoniy Mar 19 '15 at 13:44
  • 2
    No there isn’t. Method references do not support chaining. In your example it wouldn’t be clear which of the two methods ought to receive the second parameter. Anyway, why do you want that? – Holger Mar 19 '15 at 13:44
  • @Andremoniy Puts in a map, a `BiConsumer` given in the form of a lambda. – Random42 Mar 19 '15 at 13:51
  • @Holger I would want to do that for the same reason I would write method reference instead of a lambda. When calling `String str = obj.getList().get(0);` would `str` reference to what `getList()` or what `get(0)` returns? I believe the semantics could be the same for an eventual chained method referencing. – Random42 Mar 19 '15 at 13:54
  • 1
    @m3th0dman: you’re talking about the return value. I asked about the *parameter*. In your example you assume that the second parameter of the `BiConsumer` goes to the second method but there is no reason why a compiler should assume the same. – Holger Mar 19 '15 at 13:56
  • @Holger Well I believe a compiler implementing this should be consistent whit method calling. Either way the associativity of the `::` operator would not be a problem if such a feature will be implemented. – Random42 Mar 19 '15 at 14:01
  • 5
    You call it “consistent” because it’s your expectation. But there is no rule that explains why your hypothetical two-method-reference should behave like `(x,y)->x.foo().bar(y)` rather than `(x,y)->x.foo(y).bar()`. And method references may also refer to `static` methods, so it could also be `(x,y)->Type.foo(x).bar(y)` or `(x,y)->Type.foo(x, y).bar()` or `(x,y)-> Type.foo().bar(x,y)` or `(x,y)->Type2.bar(Type1.foo(x), y)` or `(x,y)-> Type2.bar(Type1.foo(x, y))` or `(x,y)->Type2.bar(Type1.foo(), x, y)` or `(x,y)-> Type2.bar(x.foo(), y)` or `(x,y)->Type2.bar(x.foo(y))` – Holger Mar 19 '15 at 14:15

1 Answers1

24

No, method references do not support chaining. In your example it wouldn’t be clear which of the two methods ought to receive the second parameter.


But if you insist on it…

static <V,T,U> BiConsumer<V,U> filterFirstArg(BiConsumer<T,U> c, Function<V,T> f) {
    return (t,u)->c.accept(f.apply(t), u);
}

BiConsumer<MyBean, String> c = filterFirstArg(List::add, MyBean::getList);

The naming of the method suggest to view it as taking an existing BiConsumer (here, List.add) and prepend a function (here, MyBean.getList()) to its first argument. It’s easy to imagine how an equivalent utility method for filtering the second argument or both at once may look like.

However, it’s mainly useful for combining existing implementations with another operation. In your specific example, the use site is not better than the ordinary lambda expression

BiConsumer<MyBean, String> c = (myBean, id) -> myBean.getList().add(id);
River
  • 8,585
  • 14
  • 54
  • 67
Holger
  • 285,553
  • 42
  • 434
  • 765