11

I am using lamdbas so I can consistently set the properties of a ModelObject according to the values I can retrieve from three different objects. The code works like this:

public class Processor {

    private void bar(Setter setter, MyClass myObject) {
        String variable = myObject.getStringByABunchOfMethods();
        setter.setVariable(variable);
    }

    protected void foo(...) {
        ...
        bar(value -> model.setA(CONSTANT, value), aObject);
        bar(value -> model.setB(value), bObject);
        bar(value -> model.setC(value), cObject);
        ...
    }

    private interface Setter {
        public void setVariable(String string);
    }

}

public interface IModel {
    public void setA(String arg0, String arg1);
    public void setB(String arg0);
    public void setC(String arg0);
}

I have read here that it is possible to rewrite bar(value -> model.setB(value), bObject); to bar(model::setB, bObject). I think this looks better and more concise, but I haven't found a way to rewrite the setA method to a double :: notation. Can anyone tell me if this is possible, and if so: how is this possible?

Community
  • 1
  • 1
ivospijker
  • 702
  • 1
  • 7
  • 22

4 Answers4

15

from https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html and https://www.codementor.io/eh3rrera/tutorials/using-java-8-method-reference-du10866vx

There would be 4 different kinds of method references. The corresponding lambda and method reference:

  • (args) -> Class.staticMethod(args), Class::staticMethod
  • (obj, args) -> obj.instanceMethod(args), ObjectType::instanceMethod
  • (args) -> obj.instanceMethod(args), obj::instanceMethod
  • (args) -> new ClassName(args), ClassName::new

The lambda value -> model.setA(CONSTANT, value) does not correspond with any of the lambdas above, so it is not possible to rewrite it as a method reference.

toongeorges
  • 1,844
  • 17
  • 14
  • The reason why I accepted this answer is: while the other answers are clever and useful, they don't make my code more understandable or cleaner. So I'll stick to using the `->` notation. – ivospijker Jan 17 '17 at 14:06
4

To use the double colon notation, the method that you're referencing must have the same signature as the required method. So you can't use :: unless you change your IModel:

You can add an overload of setA in IModel:

default void setA(String arg0) {
    setA(CONSTANT, arg0);
}

Then, you can reference that overload:

bar(model::setA, aObject);

.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • To change the `IModel` would be too much work in order to use a cleaner notation in this case. I hadn't seen the `default` keyword before, so I'll look into that for the future! – ivospijker Jan 17 '17 at 14:00
3

Not with the way the Setter functional interface is written. Unlike setB and setC, the setA method expects two arguments whereas the interface Setter has a method expecting only one argument. You can add another interface that accepts two arguments:

private interface SetterWithDefault {
    public void setVariable(String defaultString, String string);
}

Then you can call it via the bar method:

private void bar(SetterWithDefault setter, String defaultString, MyClass myObject) {
    String variable = myObject.getStringByABunchOfMethods();
    setter.setVariable(defaultString, variable);
}

Then you can call bar as follows:

bar(model::setA, CONSTANT, aObject);

Note: You can keep the other bar method. The new one can be an overload.

M A
  • 71,713
  • 13
  • 134
  • 174
  • But what should be done with the other `bar` calls that the OP listed without the `CONSTANT` parameter? – Sweeper Jan 17 '17 at 13:47
  • 1
    @Sweeper He can keep the other `bar` method. It's simply an overloaded method. – M A Jan 17 '17 at 13:48
3

You don't need a special interface: setB is a Consumer<String> and setA is a BiConsumer<String, String>. You can then adapt a BiConsumer to a Consumer:

Either with a default method in your interface (if setA is always called with a constant, why not ?):

interface Model {
    public void setA(String arg0, String arg1);
    default void setA(String arg1) {setA(CONSTANT, arg1);}
    public void setB(String arg0);
    public void setC(String arg0);
}

Either using an adapter of BiConsumer to Consumer:

static <T, U> Consumer<V> adapt(T t, BiConsumer<T, U> biConsumer) {
  Objects.requireNonNull(biConsumer, "biConsumer");
  Objects.requireNonNull(t, "t");
  return t -> biConsummer.accept(t, u);
}

And using it like this:

 protected void foo(...) {
   ...
   bar(adapt(CONSTANT, model::setA), aObject);
   bar(model::setB, bObject);
   bar(model::setC, cObject);
   ...
 }

Note: I used adapt as an example name, but that's a bad name when you mix it with other adapt overload method (because of generic and type erasure). I personally name it like fixLeftValue.

Beware that the adapt will be generated each time you invoke foo.

NoDataFound
  • 11,381
  • 33
  • 59