-1

I am trying to use a list of function references as a lookup table (avoiding the need for a long switch statement). The code worked for a list of static methods, but when I tried to use non-static (i.e. instance) methods in the list, Java gives several errors regarding the types not matching.

Here is a minimal example:

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

public class MethodReferences {
    // My original list of static references
    private final static List<Function<Integer, Integer>> lookupTable = Arrays.asList(MethodReferences::f1, MethodReferences::f2);

    // This doesn't work
    // private final List<Function<Integer, Integer>> lookupTable = Arrays.asList(MethodReferences::f3, MethodReferences::f4);

    private static int f1(int x) { return x * 2; }

    private static int f2(int x) { return x * 3; }

    private int f3(int x) { return x * 2; }

    private int f4(int x) { return x * 3; }

    public void run() {
        System.out.println(lookupTable.get(1).apply(3));
    }

    public static void main(String[] args) {
        MethodReferences testClass = new MethodReferences();

        testClass.run();
    }
}

The errors I received were all for the line containing the non-static definition:

Type mismatch: cannot convert from List<Object> to List<Function<Integer,Integer>>

and:

The target type of this expression must be a functional interface

I tried using this:: instead of MethodReferences:: before the function names. The code then compiled, but when it runs, nothing happens, probably because this has to be used within non-static functions. I then moved the initialisation of the array (still using this:: to within the class constructor, but it continued to produce no output when run.

I've checked through the documentation and tutorials on method references in Java, but I cannot find an examples of creating references to instance methods within the class it is defined in (and I cannot find any examples of lists of function references either).

I'm aware that in the main method, you can do testClass::f1, but for my specific situation (not the example code) I do not even have a main class (the class is instantiated by another library), so this approach isn't possible. The methods have to be non-static because I need to be able to modify instance variables within them.


Edit: It turns out that using this:: does work for the example code, although I am still unsure as to why it is valid (surely you can only use this within a non-static function?)

ThePythonator
  • 154
  • 2
  • 13
  • 2
    sry, cannot reproduce: it compiles & runs as expected. (`private final List> lookupTable = Arrays.asList(this::f3, this::f4);`, prints `9`) ... `$ ./java.exe -version java version "18.0.2.1" 2022-08-18 Java(TM) SE Runtime Environment (build 18.0.2.1+1-1) Java HotSpot(TM) 64-Bit Server VM (build 18.0.2.1+1-1, mixed mode, sharing) ` – xerx593 Nov 26 '22 at 12:17
  • Ok, I tried it on a different machine and I'm not sure why it wasn't working initially. I can confirm that it works with OpenJDK 11 and 17, however I am certain that it didn't work when being instantiated by another class. Using a BiFunction definitely works though. – ThePythonator Nov 26 '22 at 12:32
  • Does this answer your question? [Method reference for static and instance methods](https://stackoverflow.com/questions/66930573/method-reference-for-static-and-instance-methods) – Didier L Nov 26 '22 at 12:42
  • @DidierL It definitely sheds a little more light on it, but unfortunately it isn't clear that for functions with an input parameter, you need `BiFunction` instead, and the class must go first. – ThePythonator Nov 26 '22 at 12:51
  • Well you need a `BiFunction` if you want to to let the caller choose which instance of the containing class to use, otherwise using a simple `Function` with `this::` makes it easier both sides. – Didier L Nov 26 '22 at 15:07

2 Answers2

0

For instance method references which use the ClassName::functionName format, instead of instanceName::functionName, you also need to pass the specific instance of the class to the function when calling .apply().

This means that your method references are actually need to be a BiFunction<MethodReferences, Integer, Integer>, even though there is only one explicit parameter to the function.

When calling the method, you also need to pass this into apply:

import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;

public class MethodReferences {
    // To refer to non-static methods by class name,
    // you must pass in the instance explicitly:
    private final List<BiFunction<MethodReferences, Integer, Integer>> lookupTable = Arrays.asList(MethodReferences::f3, MethodReferences::f4);

    private int f3(int x) {
        return x * 2;
    }

    private int f4(int x) {
        return x * 3;
    }

    public void run() {
        // We need to pass this in, because it isn't implicit
        // for ClassName::functionName references:
        System.out.println(lookupTable.get(1).apply(3));
    }

    public static void main(String[] args) {
        MethodReferences testClass = new MethodReferences();

        testClass.run();
    }
}
ThePythonator
  • 154
  • 2
  • 13
0

You need to use BiFunction instead of Function. The first argument is the implicit this argument.

public class MethodReferences {
    private final static List<BiFunction<MethodReferences, Integer, Integer>> lookupTable
        = Arrays.asList(MethodReferences::f3, MethodReferences::f4);

    private int f3(int x) { return x * 2; }
    private int f4(int x) { return x * 3; }

    public void run() {
        System.out.println(lookupTable.get(1).apply(this, 3));
    }

    public static void main(String[] args) {
        MethodReferences testClass = new MethodReferences();
        testClass.run();
    }
}

output:

9