简体   繁体   中英

Method reference for static and instance methods

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);
    }
}

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:

  1. ContainingClass::staticMethodName - reference to a static method
  2. containingObject::instanceMethodName - reference to an instance method of a particular object
  3. ContainingType::methodName - reference to an instance method of an arbitrary object of a particular type
  4. ClassName::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.

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();
    }
});

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM