Niteesh Bhargava
Niteesh Bhargava

Reputation: 197

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

Upvotes: 11

Views: 7285

Answers (3)

Oleks
Oleks

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:

  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.

Upvotes: 7

Mohamed Anees A
Mohamed Anees A

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

marina
marina

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

Related Questions