Reputation: 197
I am unable to grasp the concept of Method references in case of instance methods in Java
For example in the example below, the compiler is giving error in the list line.
I have seen the examples of String::toUpperCase.
I am confused over the point that (1) String is a class and toUpperCase is instance method. Java allows String::toUpperCase (2) Why it is not allowing in my case:- AppTest::makeUppercase
package mja;
import java.util.function.Function;
public class AppTest {
public String makeUppercase(String source){
return source.toUpperCase();
}
public void printFormattedString(String string, Function<String, String> formatter){
System.out.println(formatter.apply(string));
}
public static void main(String[] args) {
AppTest appTest = new AppTest();
String source = "Hello World!";
// Below statement compiled successfully
appTest.printFormattedString(source, appTest::makeUppercase);
// Getting error that non-static method can't be referenced from static context
appTest.printFormattedString(source, AppTest::makeUppercase);
}
}
Upvotes: 11
Views: 7285
Reputation: 1051
Why it is not allowing AppTest::makeUppercase
?
The short answer is that AppTest::makeUppercase
isn't valid "reference to an instance method of an arbitrary object of a particular type".
AppTest::makeUppercase
must implement interface Function<AppTest, String>
to be valid reference.
Details:
There are 4 kinds of method references in Java:
ContainingClass::staticMethodName
- reference to a static methodcontainingObject::instanceMethodName
- reference to an instance method of a particular objectContainingType::methodName
- reference to an instance method of an arbitrary object of a particular typeClassName::new
- reference to a constructorEvery single kind of method reference requires corresponding Function
interface implementation.
You use as a parameter the reference to an instance method of an arbitrary object of a particular type.
This kind of method reference has no explicit parameter variable in a method reference and requires implementation of the interface Function<ContainingType, String>
. In other words, the type of the left operand has to be AppTest
to make AppTest::makeUppercase
compilable. String::toUpperCase
works properly because the type of parameter and type of the instance are the same - String
.
import static java.lang.System.out;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
class ReferenceSource {
private String value;
public ReferenceSource() {
}
public ReferenceSource(String value) {
this.value = value;
}
public String doInstanceMethodOfParticularObject(final String value) {
return ReferenceSource.toUpperCase(value);
}
public static String doStaticMethod(final String value) {
return ReferenceSource.toUpperCase(value);
}
public String doInstanceMethodOfArbitraryObjectOfParticularType() {
return ReferenceSource.toUpperCase(this.value);
}
private static String toUpperCase(final String value) {
return Optional.ofNullable(value).map(String::toUpperCase).orElse("");
}
}
public class Main {
public static void main(String... args) {
// #1 Ref. to a constructor
final Supplier<ReferenceSource> refConstructor = ReferenceSource::new;
final Function<String, ReferenceSource> refParameterizedConstructor = value -> new ReferenceSource(value);
final ReferenceSource methodReferenceInstance = refConstructor.get();
// #2 Ref. to an instance method of a particular object
final UnaryOperator<String> refInstanceMethodOfParticularObject = methodReferenceInstance::doInstanceMethodOfParticularObject;
// #3 Ref. to a static method
final UnaryOperator<String> refStaticMethod = ReferenceSource::doStaticMethod;
// #4 Ref. to an instance method of an arbitrary object of a particular type
final Function<ReferenceSource, String> refInstanceMethodOfArbitraryObjectOfParticularType = ReferenceSource::doInstanceMethodOfArbitraryObjectOfParticularType;
Arrays.stream(new String[] { "a", "b", "c" }).map(refInstanceMethodOfParticularObject).forEach(out::print);
Arrays.stream(new String[] { "d", "e", "f" }).map(refStaticMethod).forEach(out::print);
Arrays.stream(new String[] { "g", "h", "i" }).map(refParameterizedConstructor).map(refInstanceMethodOfArbitraryObjectOfParticularType)
.forEach(out::print);
}
}
Additionally, you could take a look at this and that thread.
Upvotes: 7
Reputation: 4591
For simplicity, let us edit your class as below.
public class AppTest {
private String name;
public AppTest(String name){ this.name = name; }
public String makeUppercase() { //I have removed the argument here!!
return this.name.toUpperCase();
}
psvm main(){
AppTest appTest = new AppTest("Hello");
Stream.of(appTest).map(AppTest::makeUppercase).forEach(System.out::println);
//Here makeUppercase works of objects of type AppData similar to how String::toUpperCase works on object of type String!
}
}
This is accepted. Why?
Here, AppTest::makeUppercase
is an instance method that operates on this
instance of AppTest
.
Why was yours not working?
appTest.printFormattedString(source, AppTest::makeUppercase);
This was not working because you are required to pass an implementation of Function
. And, makeUpperCase() Function
was not accessible from a non-static context since the method makeUpperCase()
works on objects of type AppData
. So, you need AppData
instance to call this method!
Maybe you should change your method to be static and use it like this,
appTest.printFormattedString("Hello", AppTest::makeUppercase);
Why is the following code working?
appTest.printFormattedString(source, appTest::makeUppercase);
Because, you created an instance of AppTest
and accessing the makeUppercase
method (which is the implementation) and passing it as an argument to printFormattedString
.
You need objects of a particular type to access the non-static method. But, You do not need objects of a particular type to access the static method.
String::toUpperCase
works on instances of String
. But you cannot access this method without having a String
object to work on. Refer my comment in the code block to understand this better.
Upvotes: 1
Reputation: 1680
String::toUpperCase
is short version of
text -> {
return text.toUpperCase();
}
is again short version of
new Functon<String, String> (String text) {
Override
public String apply(String text) {
return text.toUpperCase();
}
}
so when you want AppTest::myMethod
you need
public class AppTest {
public String myMethod(){
return this.toString();
}
public void printFormattedString2(AppTest appTest, Function<AppTest, String> formatter){
System.out.println(formatter.apply(appTest));
}
public static void main(String[] args) {
AppTest appTest = new AppTest();
appTest.printFormattedString2(appTest, AppTest::myMethod);
}
}
because whole version looks so
appTest.printFormattedString2(appTest, new Function<AppTest, String>() {
@Override
public String apply(AppTest text) {
return text.makeUppercase2();
}
});
Upvotes: 1