2

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?

Holger
301k43 gold badges483 silver badges832 bronze badges
asked Jan 17, 2017 at 20:52
8
  • 2
    See also here Commented Jan 17, 2017 at 21:59
  • 1
    String::startsWith would take 3 arguments; 1. the String instance on which to invoke, 2. the String parameter prefix and 3. the int parameter toffset. But a Bipredicate<String, Integer> can only account for 2 of those. String::isEmpty, takes 1 parameter, the instance to invoke on, so a Predicate<String> will work. Commented Jan 17, 2017 at 23:45
  • @JornVernee You are totally right. However, why does Predicate <String> predicate = String::isEmpty; work? Like why don't I need to do pass a new instance of String() (As your answer below to my original question may indicate)? Commented Jan 18, 2017 at 0:16
  • 1
    @EddieLin You will need to do that when you call it, the test method takes 1 parameter. It's doesn't have to be new though, any instance will work (in my answer, I just used new Evaluation() as an example) Commented Jan 18, 2017 at 0:21
  • 1
    Just consider that the method reference is the equivalent to (instance, i, j) -> instance.evaluate(i, j). Three parameters. Commented Jan 18, 2017 at 10:56

4 Answers 4

2

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.

answered Jan 17, 2017 at 21:01
Sign up to request clarification or add additional context in comments.

8 Comments

@Andreas Try again, I had a typo there. It works fine for me :)
@Andreas That's a different solution, but I want to explain why OP's version doesn't work, and how to make it work. My suggestion was to make the method static.
@Andreas My code shows how statically referencing an instance method actually works, it's long winded because there is no standard interface in java.util.function that you can use.
I was going to post this. This shows that you can reference an instance method with the class name, given that you can provide the correct functional interface.
@Andreas: this answer addresses the OP’s actual question. The argument of new Evaluation() is just an example, as the OP hasn’t provided an argument. But you can pass a different instance on each invocation...
|
2

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.

answered Jan 17, 2017 at 20:55

2 Comments

Was writing the same answer ;)
Since method doesn't use instance variables/methods, I'd say your answer is wrong, and that OP should simply make the method static instead. Alternatively, since the code is in a non-static method, why create a new instance? Use this::evaluate.
0

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;.

answered Jan 17, 2017 at 21:17

Comments

0

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
answered May 9, 2020 at 12:14

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.