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);
}
}
-
Where is the "String::toUpperCase" example? I think you'll see the context matters. One is probably in a streaming context where the type of the function is known.matt– matt2021年04月03日 11:25:39 +00:00Commented Apr 3, 2021 at 11:25
-
Hi @matt , see this for example , compiler is not giving any error in this line. IntStream.of(1, 2, 3).mapToObj(Integer::toString) .forEach(System.out::println);Niteesh Bhargava– Niteesh Bhargava2021年04月03日 12:06:09 +00:00Commented Apr 3, 2021 at 12:06
3 Answers 3
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 constructor
Every 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.
Comments
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();
}
});
4 Comments
container::reference) can refer to either the type of an input, or the reference's location. So MyClass::staticMethod could satisify the functional interface requirements for both (MyClass instance) -> instance.staticMethod(); and () -> MyClass.staticMethod();, the former due to backward compat.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.