Win a copy of Programming with Types this week in the Angular and TypeScript forum
or The Design of Web APIs in the Web Services forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Liutauras Vilda
  • Bear Bibeault
  • Paul Clapham
  • Jeanne Boyarsky
Sheriffs:
  • Junilu Lacar
  • Knute Snortum
  • Henry Wong
Saloon Keepers:
  • Ron McLeod
  • Tim Moores
  • Stephan van Hulst
  • Tim Holloway
  • Carey Brown
Bartenders:
  • Frits Walraven
  • Joe Ess
  • salvin francis

Method references to Instance Methods and class Methods

 
Greenhorn
Posts: 4
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hello,

I have just started to go through 'Java. The Complete Reference' book 9th edition (JDK 8).
On chapter 15 p. 397 started learning 'Method References to Instance Methods' & played around. All examples in book make sense & works, but, I have trouble to understand why would following code works:




Whilst following method isn't:



I know, that failing code is using class to get instance method, not instance itself. However, as per book, this code works (using generics, but still I think similar functionality):




Compiler of failing java code gives result:

$ javac -Xdiags:verbose -d bin src/book/lambda/MethodReferenceInstance.java
src\book\lambda\MethodReferenceInstance.java:27: error: method stringOps in class MethodReferenceInstance cannot be applied to given types;
       System.out.println(stringOps(MyStringOps::strReverse, "Lambda"));
                          ^
 required: StringFunc,String
 found: MyStringOp[...]verse,String
 reason: argument mismatch; invalid method reference
     cannot find symbol
       symbol:   method strReverse(String)
       location: class MyStringOps
1 error

I was trying to find out regarding this compiler error, but it's not that easy... Wondering, would someone in simple terms could explain why above code is failing?

 
Saloon Keeper
Posts: 11010
243
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
It all makes sense if you mentally convert an instance method to a static method that takes an extra parameter. For instance, strReverse(String) can be seen as a static method strReverse(MyStringOps, String), where the first parameter refers to the instance that you call the instance method on. We will write its type as BiFunction<MyStringOps, String, String>.

MyStringOps::strReverse is not compatible with StringFunc, because the first is like a BiFunction<MyStringOps, String, String> and the second is like a Function<String, String>.

So what happens when we use the method reference operator on a variable rather than a type name?

myStringOps::strReverse already fills in the first parameter of the BiFunction<MyStringOps, String, String>, so it's really more like a Function<String, String>, and therefore compatible with StringFunc.

HighTemp.sameTemp is an instance method, so its type is similar to BiPredicate<HighTemp, HighTemp>. The counter method expects a MyFunc<T>, which is similar to BiPredicate<T, T>, so the method reference is already compatible without having to apply the method reference operator on an instance of HighTemp.
 
Sheriff
Posts: 14618
243
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
i'm not convinced that's what's happening though. I've been going through the JLS section on Method Reference Expressions https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13 to see which rules apply here but haven't been able to find anything yet. This is a good question.
 
Junilu Lacar
Sheriff
Posts: 14618
243
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
It seems the difference comes down to the use of generic type arguments but I'm not sure yet. It seems to me that the crux of the matter is around resolving the target reference:

JLS wrote: The target reference of an instance method (§15.12.4.1) may be provided by the method reference expression using an ExpressionName, a Primary, or super, or it may be provided later when the method is invoked.  ... Evaluation of a method reference expression produces an instance of a functional interface type (§9.8).

 
Stephan van Hulst
Saloon Keeper
Posts: 11010
243
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Generics are in no way involved. The same would happen if MyFunc wasn't generic and always used HighTemp.

I don't want to appear glib or condescending, I just don't have the opportunity to give a detailed technical explanation right now on account of being on my phone rather than behind my laptop.
 
Junilu Lacar
Sheriff
Posts: 14618
243
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I tried it just now and you're right about the generics having nothing to do with it.
 
Junilu Lacar
Sheriff
Posts: 14618
243
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Experimented with some more tweaks and I got it to compile and run but I don't know if I'm fully understanding the mechanics yet.

Here's my experiment: https://repl.it/repls/SpecificLightblueMiddleware

It does appear that Stephan is correct though.
 
Stephan van Hulst
Saloon Keeper
Posts: 11010
243
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
is equivalent to
OR
depending on whether doSomething is static or not.

is equivalent to

Notice that in the case of an instance method, Foo::doSomething and foo::doSomething both have the same lambda expression body. The difference is in the parameter list. In the second case, the foo identifier in the lambda expression body refers to the foo variable in the enclosing lexical scope (the variable to which you applied the method reference operator).
 
Germans Zaharovs
Greenhorn
Posts: 4
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stephen,

So as far as I understood and played around, non-static referenced class methods would expect to receive first param in functional interface object itself e.g. :



Above code compiles and runs as expected now:)

So would it be correct to assume following:


Foo::doSomething;
is equivalent to

OR





I have tried to search for any other references apart of one mentioned by Junilu:


i'm not convinced that's what's happening though. I've been going through the JLS section on Method Reference Expressions https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13 to see which rules apply here but haven't been able to find anything yet. This is a good question.



Would appreciate, if someone would share this.

Thanks a lot Stephen & Junilu! You helped to understand topic far better.
 
Stephan van Hulst
Saloon Keeper
Posts: 11010
243
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I don't think you got it completely yet.

Functional interfaces don't care about whether methods are static or not. They just specify a parameter list and a return type.

The method reference operator returns a function with a parameter list and a return type. If the referenced method happens to be an instance method, the parameter list will contain one extra parameter, unless the operator is used on an object reference rather than a type name.

When you encounter a method reference, you can determine the function type like this:

1) Take only the parameters from the method signature.
2) If the method is an instance method, prepend an additional parameter to the front of the list.
3) If the method reference was retrieved by operating on an object reference instead of a type name, remove the first parameter from the parameter list.

Let's make an example out of myStringOps::strReverse:

1) strReverse takes a String and returns a String. We'll write this down as (String) -> String.
2) strReverse is an instance method of MyStringOps, so we prepend a parameter: (MyStringOps, String) -> String.
3) The left hand side of myStringOps::strReverse is an object reference, so we remove the first parameter again.

The resulting function type is (String) -> String. Note that we haven't done anything with functional interfaces yet. Now that we've resolved static modifiers and object references, we directly compare the function type to the functional interface. StringFunc (from the first post) represents a (String) -> String, so our method reference is compatible.
 
Germans Zaharovs
Greenhorn
Posts: 4
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hello Stephen, thanks for your help.

I have tried multiple times using different approaches of what you've written, but still can't get some of statements. If I correctly understand, that instance methods are eventually built using additional param of instance, but this shouldn't change functional interface.

By examples:



The above code fails. Wondering, where should I write, if not in functional interface?:


2) strReverse is an instance method of MyStringOps, so we prepend a parameter: (MyStringOps, String) -> String.



If functional interface rewritten code compiles:


I guess, maybe my English isn't good & I can't pick something... could you show by above examples, if it is possible to use instance method without using object & changed functional interface?
 
Stephan van Hulst
Saloon Keeper
Posts: 11010
243
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
You're kind of asking: "Is there a way to write 5 so that it's 3?"

If you can implement a functional interface by operating on the type name, do that. If you can implement a functional interface by operating on an object reference, do that. If you can't do either, use a lambda expression.

Changing a functional interface to accommodate a method reference is putting the cart before the horse.
 
Germans Zaharovs
Greenhorn
Posts: 4
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Now I understand, it's basically impossible)) I was confused of implementation of additional argument & thought it might possible. Surely it makes no sense to change functional interface to accommodate method reference:)

Many thanks!:)
 
Marshal
Posts: 67013
255
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:. . . : "Is there a way to write 5 so that it's 3?" . . . .

You can in some languages; the following would work in Forth and allow you to show that 2 and 2 make 5:-
 
A wop bop a lu bob a womp bam boom. Tutti frutti ad:
Sauce Labs - World's Largest Continuous Testing Cloud for Websites and Mobile Apps
https://coderanch.com/t/722574/Sauce-Labs-World-Largest-Continuous
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!