8

I don't understand how to use lambdas to pass a method as a parameter.

Considering the following (not compiling) code, how can I complete it to get it work ?

public class DumbTest {
    public class Stuff {
        public String getA() {
            return "a";
        }
        public String getB() {
            return "b";
        }
    }

    public String methodToPassA(Stuff stuff) {
        return stuff.getA();
    }

    public String methodToPassB(Stuff stuff) {
        return stuff.getB();
    }

    //MethodParameter is purely used to be comprehensive, nothing else...
    public void operateListWith(List<Stuff> listStuff, MethodParameter method) {
        for (Stuff stuff : listStuff) {
            System.out.println(method(stuff));
        }
    }

    public DumbTest() {
        List<Stuff> listStuff = new ArrayList<>();
        listStuff.add(new Stuff());
        listStuff.add(new Stuff());

        operateListWith(listStuff, methodToPassA);
        operateListWith(listStuff, methodToPassB);
    }

    public static void main(String[] args) {
        DumbTest l = new DumbTest();

    }
}
Barium Scoorge
  • 1,938
  • 3
  • 27
  • 48
  • Lambdas only work with functional interfaces (an `interface` with only 1 method, like `Runnable`). I do not see you declaring a functional interface. Also, when you get an error, tell us what error you are getting and where it's at. – Vince Aug 27 '15 at 17:21
  • I'm not decalring a functional interface because i'm not understanding it when i wrote this post.. this code is not compiling, it must be read as pseudo code for some parts – Barium Scoorge Aug 28 '15 at 07:33

4 Answers4

5

Declare your method to accept a parameter of an existing functional interface type which matches your method signature:

public void operateListWith(List<Stuff> listStuff, Function<Stuff, String> method) {
    for (Stuff stuff : listStuff) {
        System.out.println(method.apply(stuff));
    }
}

and call it as such:

operateListWith(listStuff, this::methodToPassA);

As a further insight, you don't need the indirection of methodToPassA:

operateListWith(listStuff, Stuff::getA);
Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • Why would you do this? Why wouldn't you just do `listStuff.stream().map(method).forEach(System.out::println)`? Isn't that the entire point of lambdas? – durron597 Aug 27 '15 at 20:10
  • 3
    I actually prefer OP's way to learning: separating the lambda (a language feature) form Streams (an API which uses it). Lambdas are a far wider story than just the Streams API. Yes, in _production_ code, one would write it your way. – Marko Topolnik Aug 27 '15 at 20:11
  • 1
    I'm not using stream because i'd like to understand how to pass a method as a parameter, as Marko guessed it – Barium Scoorge Aug 28 '15 at 07:35
  • This does not work like this ; i get "The method apply(String) in the type Function is not applicable for the arguments (DumbTest.Stuff)" compilation error – Barium Scoorge Aug 28 '15 at 07:36
  • Yes, you need `` in fact. Fixed. – Marko Topolnik Aug 28 '15 at 07:37
3

Your MethodParameter should be some interface you define with a single method. This is referred to as a functional interface. You can then pass your methods in. A quick demonstration:

public interface Test{
    void methodToPass(string stuff);
}

[...]

public class DumbTest{
     //MethodParameter is purely used to be comprehensive, nothing else...
    public void operateListWith(List<Stuff> listStuff, Test method) {
        for (Stuff stuff : listStuff) {
            System.out.println(method(stuff));
        }
    }
    public DumbTest() {
        List<Stuff> listStuff = new ArrayList<>();
        //fill list
        operateListWith(listStuff, methodToPassA);
        operateListWith(listStuff, methodToPassB);
    }
}
William Morrison
  • 10,953
  • 2
  • 31
  • 48
2

The definition of MethodParameter is missing from your source code. To be used with lambda expressions, it must be a functional interface, for example:

@FunctionalInterface
interface MethodParameter {
    String apply(Stuff input);
}

(The @FunctionalInterface annotation is optional.)

To use the method, you have call the method from the interface:

System.out.println(method.apply(stuff));

And thirdly, a method reference always needs a context. In your case you have to do:

operateListWith(listStuff, this::methodToPassA);
operateListWith(listStuff, this::methodToPassB);
Hoopje
  • 12,677
  • 8
  • 34
  • 50
1

You need to use method references.

You don't need to create a method like operateListWith, that's sort of the whole idea. Instead, you can operate on each value using forEach by doing something like this:

listStuff.stream.forEach(object::methodToPassA);

For example:

public class StreamExample {
  public static void main(String[] args) {
    List<String> list = Arrays.asList("Hello", "What's Up?", "GoodBye");
    list.stream().forEach(System.out::println);
  }
}

Output:

Hello
What's Up?
GoodBye

In your case, you can get the value inside Stuff using .map, and then operate on it using forEach, like this:

public class DumbTest {
  public class Stuff {
    public String getA() {
      return "a";
    }

    public String getB() {
      return "b";
    }
  }

  public String methodToPassA(Stuff stuff) {
    return stuff.getA();
  }

  public String methodToPassB(Stuff stuff) {
    return stuff.getA();
  }

  public DumbTest() {
    List<Stuff> listStuff = Arrays.asList(new Stuff(), new Stuff());

    listStuff.stream()
        .map(this::methodToPassA)
        .forEach(System.out::println);
  }

  public static void main(String[] args) {
    DumbTest l = new DumbTest();
  }
}
Community
  • 1
  • 1
durron597
  • 31,968
  • 17
  • 99
  • 158