Eddie Lin
Eddie Lin

Reputation: 310

Using method references on a instance to be determined at runtime in Java

I was testing out rules of using method references, but the code I wrote would not compile. The compiler keeps giving telling me that I cannot reference a non-static method from a static context. However, in the Java Documents it explicitly wrote that it is possible to use "::" to "reference to an instance method of an arbitrary object of a particular type". Can anyone point out what's wrong with my code? Thank you!

package Test;
import java.util.function.BiPredicate;

class Evaluation {
    public boolean evaluate(int a, int b) {
        if (a-b ==5){
            return true ;
        }
        return false; 
    }

    public void methodTest() {
        BiPredicate<Integer, Integer> biPredicate = Evaluation::evaluate;
        System.out.println(biPredicate.test(6,1));
    }
}

Edit: After reading the answers, I was wondering if it is the case that referencing an instance method by the class name only works in some functional interfaces but not in other ones? For instance,

BiPredicate <String, Integer> biPredicate =  String::startsWith;

doesn't compile, while:

Predicate <String> predicate = String::isEmpty;

compiles. If this is the case, is there a page/tutorial/whatever that anyone can refer me to that explains which function interfaces are compatible and which are not?

Upvotes: 2

Views: 839

Answers (4)

GingerBeer
GingerBeer

Reputation: 948

I am probably way too late to answer this, but since the question is still unanswered I would like to attempt an answer.

I think there is a miss in what OP is trying to achieve.

I understand that OP is trying to understand, why something like this would work:

    String str = "abc";
    Predicate<String> methodRef = str::startsWith; 
    methodRef.test("s");

and then,

Predicate <String> predicate = String::isEmpty 

Works and in similar fashion, why wouldn't

Predicate <String> predicate =  String::startsWith;

Compile which is taking String class name compile.

That is simply because, Predicate basically, takes any argument and returns a boolean. This is not a correct setup for this problem.

You can instead try,

BiFunction<String, String, Boolean> methodRef2 = String::startsWith;
methodRef2.apply("sdsdfsd", "sdfsdf");

This would work, as startswith needs a source string, string to check and return value. Basically there are 4 ways to invoke method references in Java 8

  1. Static method calls.
  2. Instance method calls.
  3. Class method calls
  4. Constructors

Upvotes: 0

Lew Bloch
Lew Bloch

Reputation: 3433

I'm still trying to figure out the rule that applies, but the problem goes away if you use

BiPredicate<Integer, Integer> biPredicate = this::evaluate;

I'm puzzling through https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13 but as near as I can figure, because the Evaluation::evaluate forces the compiler to create an arbitrary object of the type Evaluation, and you're calling it from within an object of that type, that the rule is different. You need to call it from the specific object inside of which the methodTest method appears.

While I don't have the explanation, the solution is to use this::evaluate. That unambiguously ties the method reference to the object calling it.

Side note: You don't need to evaluate a boolean as a conditional in order to derive a boolean from the boolean. You could just return a - b == 5;.

Upvotes: 0

Michał Szewczyk
Michał Szewczyk

Reputation: 8168

If your method is an instance method, then you have to invoke it on some instance, for example:

public void methodTest(){
    BiPredicate<Integer, Integer> biPredicate = this::evaluate;
    System.out.println(biPredicate.test(6,1));
}

Since you are not using any instance variables or method, you can simply make it static and keep it like it is.

Upvotes: 2

Jorn Vernee
Jorn Vernee

Reputation: 33875

When statically referencing an instance method, the returned functor takes an additional argument that represents the instance.

interface Func {
    boolean evaluate(Evaluation instance, int a, int b);
}
...
Func biPredicate = Evaluation::evaluate;
System.out.println(biPredicate.evaluate(new Evaluation(), 6, 1));

But you will need to pass an instance of Evaluation when calling it.

Since your evaluate method does not use any instance fields, you might as well make it static, then you don't need to pass an instance, and can use just a BiPredicate<Integer, Integer> like you tried to.

Upvotes: 2

Related Questions