Search code examples
javafunctional-programmingmethod-reference

How can a parameterless method toUpperCase() implement Function.apply()?


I'm learning Java 8 with Lambda, Streams, and method reference. Regarding the example below,

Optional<String> s = Optional.of("test");
System.out.println(s.map(String::toUpperCase).get());

I don't understand how is it possible to use String::toUpperCase as an input for this map() method.

This is the method implementation:

public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
        return empty();
    } else {
        return Optional.ofNullable(mapper.apply(value));
    }
}

So it requires a function interface, and it has this apply() method: R apply(T t); This method has an input argument.

And toUpperCase() method doesn't have any argument:

public String toUpperCase() {
    return toUpperCase(Locale.getDefault());
}

If the abstract method apply(T t) has one argument, then the implemented method should have one argument of the same type. How can parameterless method toUpperCase() implement the apply(T t) method from a function interface?


I try to recreate the same conditions:

I create a functional interface:

public interface Interf {
    String m1(String value);
}

Then I create a class with the method reference for m1():

public class Impl {
    public String value;

    public String toUpp() {
        return value.toUpperCase();
    }
}

And here is a class for test:

public class Test {
    public static void main(String[] args) {
        Interf i = String::toUpperCase;
        System.out.println(i.m1("hey"));

        Interf i1 = Impl::toUpp;
        System.out.println(i.m1("hello"));
    }
}

There isn't any issue at this statement: Interf i = String::toUpperCase; but there is a compilation error on this line: Interf i1 = Impl::toUpp;. It says:

Non-static method cannot be referenced from a static context

But toUpperCase() is also a non-static method. And even if I make the toUpp() static, it is still not working, it is working only if I add a String argument as an input argument for toUpp(). But then why is it working for String::toUpperCase?


Solution

  • TL;DR

    Parameters that a Method reference is expected to consume according to the contract imposed by a Functional interface it implements are NOT necessarily the same as parameters of the method used in the Method reference.

    This answer is a journey from this common misconception towards understanding all syntactical flavors of Method references.

    Let's take tiny baby steps to dispel misunderstanding, starting from the definition of the Method reference.

    What is Method reference

    Here is the definition of the Method reference according to the Java Language Specification §15.13. Method Reference Expressions

    A method reference expression is used to refer to the invocation of a method without actually performing the invocation. Certain forms of method reference expression also allow class instance creation (§15.9) or array creation (§15.10) to be treated as if it were a method invocation.

    emphasis added

    So, a Method reference is a mean of referring the method-invocation without invoking a method. Or in other words, it's a way of describing a certain behavior by delegating to the existing functionality.

    Let's take a small detour and have a look at the sibling of a Method reference, a Lambda expression.

    Lambda expressions are also a way to describe behavior (without executing it), and both lambdas and Method references should conform to a Functional interface. Let's declare some lambdas.

    Consider, we have a domain class Foo and utility class FooUtils.

    public class FooUtils {
    
        public static Foo doSomethingWithFoo(Foo foo) {
            // do something
            return new Foo();
        }
    }
    

    And we need to define a function of type UnaryOperator<Foo>, let's start with writing a lambda expression:

    UnaryOperator<Foo> fooChanger = foo -> FooUtils.doSomethingWithFoo(foo);
    

    Lambda receives an instance of Foo as an argument and feeds it into the existing utility method. Quite simple, right? Nothing special happens inside the lambda's body, and since have defined the type as UnaryOperator<Foo> the lambda should expect Foo. Everything is absolutely predictable, isn't it? Now the question is: can we alternate that?

    Sure, we can!

    UnaryOperator<Foo> fooChanger = FooUtils::doSomethingWithFoo;
    

    That's where a Method reference comes to the rescue. It provides a shortened syntax by:

    1. Dropping the lambda's arguments (they are still there, we're simply not displaying them because we know what they are).

    2. Removing the parentheses after the method name. Again the method declaration is known (and let's assume that there are no ambiguities), and we are not performing any prior transformation with arguments, and we are not using any additional arguments apart from those that should come according to the contract of the Functional interface. Only in this case everything is predictable and can a method reference.

    Key takeaways:

    • you may think of method references as if they are shortened lambdas.
    • the arguments of the method reference are the same the equivalent lambda receives because they are implementations to the same interface. These parameters are still there implicitly, just dropped for the purpose of conciseness. And more importantly, parameters that a method reference consumes should not be confused with parameters expected method it refers to. In other word the first parameters an input of the method reference (and they are compliant with the contract defined by the interface) and the latter related to what happens inside the reference, and have no connection to the first ones.

    More examples

    Let's examine a few more examples. Let's say we have a simple object representing a coin with a single property isHeads describing which side the coin is showing (i.e. heads or tails).

    public static class Coin {
        public static final Random RAND = new Random();
    
        private final boolean isHeads;
    
        public Coin() {
            this.isHeads = RAND.nextBoolean();
        }
    
        private Coin(boolean isHeads) {
            this.isHeads = isHeads;
        }
    
        public Coin reverse() {
            return new Coin(!isHeads);
        }
    
        public boolean isHeads() {
            return isHeads;
        }
    }
    

    Let's generate a coin. For that we can use implement of Supplier which very generous, supplier doesn't receive arguments, it produces a value. Let's definable both a lambda and a reference

    Supplier<Coin> coinProducer = () -> new Coin(); // no argument required according to the contract of Supplier
    Supplier<Coin> coinProducer1 = Coin::new;       // Supplier expressed as a reference to no-args constructor
    

    Both don't receive any argument (as per contract of the Supplier), both refer to the no-arguments constructor.

    Now let's consider the predicates determining if the coin shows heads implemented via a lambda and a method reference:

    Predicate<Coin> isHeads = coin -> coin.isHeads();
    Predicate<Coin> isHeads1 = Coin::isHeads;
    

    Again, both the lambda and the method reference are compliant with the Predicate's contract and both receive an instance of Coin as an argument (it can't be otherwise, simply concise syntax of the method reference doesn't show that).

    So far, so good? Let's move further and try another way to obtain a Coin, let's define a Function:

    Function<Boolean, Coin> booleanToCoin = value -> new Coin(value);
    Function<Boolean, Coin> booleanToCoin1 = Coin::new;
    

    Now both the lambda and the reference are consuming a boolean value and making use of the parameterized constructor. Did not notice that method reference describing Supplier<Coin> and Function<Boolean, Coin> looks identical.

    Reminder: both Lambda expressions and Method references have no type by itself. They are so-called poly-expressions, which means their type should be inferred by the compiler based on the context in which they appear. Both the lambda and the reference should conform to a Functional interface, and the interface they implement dictates who they are and what they are doing.

    In all examples described earlier, arguments of consumed by a method reference appeared to be the same as the ones expected by the referenced method, but it's not mandatory for them to be the identical. It's time to examine a couple or examples where it not the case to dispel the illusions.

    Let's consider a UnaryOperator reversing a coin:

    UnaryOperator<Coin> coinFlipper = coin -> coin.reverse(); // UnaryOperator requires one argument
    UnaryOperator<Coin> coinFlipper1 = Coin::reverse;         // UnaryOperator still requires one argument expressed as a reference to no arg method
    

    All implementations of the UnaryOperator receive a Coin instance as an argument, and another coin is being produced as a result of the invocation of reverse(). The fact that reverse is parameterless is not an issue, because we concerned about what it produces, and not what it consumes.

    Let's try to define a tougher method reference. To begin with, introduce in the Coin class a new instance method called xor(), which is immensely useful for XOR-ing two coins:

    public Coin xor(Coin other) {
        return new Coin(isHeads ^ other.isHeads);
    }
    

    Now when two object come into play we have more possibilities, let's start with the simplest case one by defining a UnariOperator:

    final Coin baseCoin = new Coin();
    UnaryOperator<Coin> xorAgainstBase = coin -> baseCoin.xor(coin);
    UnaryOperator<Coin> xorAgainstBase1 = baseCoin::xor;
    

    In the above example an instance of Coin defined outside the function is used to perform the transformation via the instance-method.

    A little bit more complicated case would be a BinaryOperator for XOR-ing a couple of coins might look like this:

    BinaryOperator<Coin> xor = (coin1, coin2) -> coin1.xor(coin2);
    BinaryOperator<Coin> xor1 = Coin::xor;
    

    Now we have two arguments coming as an input and a Coin instance should be produce as an output as per BinaryOperators contract.

    The interesting thing is the first argument serves as an instance on which the method xor() would be invoked, and the second is passed to the method (note that xor() expect only one argument).

    You might ask what would happen if there would be another method for XOR-ing coins. A static method expecting two arguments:

    public static Coin xor(Coin coin1, Coin coin2) {
        return new Coin(coin1.isHeads ^ coin2.isHeads);
    }
    

    Then the compiler would fail to resolve the method reference, because here we have more the one potentially applicable method and none of them can be considered to be more specific than the other since the types of arguments are the same. That would cause a compilation error. But if we would have either of them (not both together), reference Coin::xor would work fine.

    Types of Method references

    Basically, the examples that we have walked through covered all the types of method references. Now, let's enumerate them.

    The official tutorial provided by Oracle re are four kinds of method references:

    1. Reference to a Static method

      Class::staticMethod
      

      Example Coin::xor which refers to the static method xor(Coin coin1, Coin coin2).

      Examples with standard JDK-classes:

      BinaryOperator<Integer> sum = Integer::sum; // (i1, i2) -> Integer.sum(i1, i2)
      
      BiFunction<CharSequence, Iterable<CharSequence>, String> iterableToString
          = String::join; // (delimiter, strings) -> String.join(delimiter, strings)
      
    2. Reference to an instance method of a particular object

      instance::instanceMethod
      

      The example illustrating this case would the usage of the instance method xor(Coin other) with a coin defined outside the function, which is internaly used to invoke xor() on it passing the function-argument into the method.

      final Coin baseCoin = new Coin();
      UnaryOperator<Coin> xorAgainstBase1 = baseCoin::xor; // same as coin -> baseCoin.xor(coin)
      

      Examples with standard JDK-classes:

      Set<Foo> fooSet = // initialing the Set
      Predicate<Foo> isPresentInFooSet = fooSet::contains;
      
    3. Reference to an Instance Method of an Arbitrary Object of a Particular Type

      Class::methodName
      

      In this case method refernce operates on an instance that comes as an argument (we would have reference to it only it we would use a lambda), therefore containing type, which can be tha actual type or one the super types, is used to refer to this instance.

      An example would a Predicate checking if the coin shows heads Coin::isHeads.

      Examples with standard JDK-classes:

      Function<List<String>, Stream<String>> toStream = Collection::stream;
      
      List<List<String>> lists = List.of(List.of("a", "b", "c"), List.of("x", "y", "z"));
      
      List<String> strings1 = lists.stream()
          .flatMap(toStream)
          .toList();
      
      // A slightly more complicate example taken from the linked tutorial
      // Equivalent lambda: (a, b) -> a.compareToIgnoreCase(b)
      
      String[] stringArray = { "Barbara", "James", "Mary" };
      Arrays.sort(stringArray, String::compareToIgnoreCase);
      
    4. Reference to a Constructor

      Class::new
      

    We have cove this case already with the following examples:

    • Supplier<Coin> refering to no args-constracor implemented as Coin::new;
    • Function<Boolean, Coin> which makes use of the single-arg constructor by passing incoming boolean value also expressed as Coin::new.