11

I read the Java 8 tutorial on Lambda Expressions and do not quite understand the Method Reference example for "Reference to an instance method of an arbitrary object of a particular type"

In the same tutorial there is an example "Reference to an Instance Method of a Particular Object" which look likes.

public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
}
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

I can see this working because the method compareByName has the same signature as Comparator.compare, lambda (a, b) -> myComparisonProvider.compareByName(a, b) takes two arguments and calls a method with the same two arguments.

Now the "Reference to an instance method of an arbitrary object of a particular type" example uses String::compareToIgnoreCase

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

The signature for that method is int compareTo(String anotherString) and is different than Comparator.compare. The tutorial is not very clear but seem to imply you end up with a lambda such as (a, b) -> a.compareToIgnoreCase(b) I dont understand how the compiler decides what is acceptable for the second param of Arrays.sort I thought maybe it is smart enough to understand how to call that method, so I created an example.

public class LambdaTest {

    public static void main(String... args) {
        String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };

        Arrays.sort(stringArray, String::compareToIgnoreCase);      // This works

        // using Static methods
        Arrays.sort(stringArray, FakeString::compare);              // This compiles
        Arrays.sort(stringArray, FakeString::compareToIgnoreCase);  // This does not

        // using Instance methods
        LambdaTest lt = new LambdaTest();
        FakeString2 fs2 = lt.new FakeString2();
        Arrays.sort(stringArray, fs2::compare);                 // This compiles
        Arrays.sort(stringArray, fs2::compareToIgnoreCase);     // This does not

        for(String name : stringArray){
            System.out.println(name);
        }
    }

    static class FakeString {
         public static int compareToIgnoreCase(String a) {
             return 0;
         }


        public static int compare(String a, String b) {
            return String.CASE_INSENSITIVE_ORDER.compare(a, b);
        }
    }

    class FakeString2 implements Comparator<String> {
         public int compareToIgnoreCase(String a) {
             return 0;
         }

        @Override
        public int compare(String a, String b) {
            return String.CASE_INSENSITIVE_ORDER.compare(a, b);
        }
   }
}

Can some one explain why the above two Arrays.sort don't compile even though they are using methods that are the same as String.compareToIgnoreCase method

Holger
  • 285,553
  • 42
  • 434
  • 765
Rich
  • 153
  • 7
  • Please refer [String::compareToIgnoreCase on Stackoverflow](https://stackoverflow.com/questions/39073799/type-inference-in-method-reference) – Jaydip Halake Jul 07 '17 at 09:15

2 Answers2

8

This is the difference between a method reference on some object and a method reference on the object being processed.

First the Oracle examples

Lets look at this first case:

public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
}
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

Here, the method compareByName is called on the passed in instance of myComparisonProvider with each pair of arguments in the sort algorithm.

So here, when comparing a and b we actually call:

final int c = myComparisonProvider.compareByName(a,b);

Now, in the second case:

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

You are sorting a String[] so the method compareToIgnoreCase is called on the String instance currently being sorted with the other String as an argument.

So here, when comparing a and b we actually call:

final int c = a.compareToIgnoreCase(b);

So these are two different cases:

  • one where you pass in a method on an arbitrary object instance; and
  • one where you pass in a method to be called on the instance being processed.

Onto your examples

Now in your first example, you also have a String[] and you try and sort it. So:

Arrays.sort(stringArray, FakeString::compare);

So here, when comparing a and b we actually call:

final int c = FakeString.compare(a, b);

The only difference is compare is static.

Arrays.sort(stringArray, FakeString::compareToIgnoreCase);

Now, the String[] is not a FakeString[] so we cannot call this method on String. Therefore we must be calling a static method on FakeString. But we cannot do this either, because we require a method (String, String) -> int but we only have (String) -> int - compilation error.

In the second example the problem is exactly the same, as you still have a String[]. And compareToIgnoreCase has the wrong signature.

TL;DR:

The point you are missing is that in the String::compareToIgnoreCase example; the method is called on the String currently being processed.

Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
  • 1
    Upvoted for the tl;dr. I believe this was the main important point that the OP was missing. – Jean-François Savard Apr 19 '15 at 20:20
  • I just got it. had to read it several times. Even though it does make sense, Oracle official site is lacking lot more explanation on this subject specifically. You can't just throw this away on one line and expect everyone to understand it. that's just my honest opinion. – cesarmax May 27 '20 at 03:14
6

In FakeString, Your compareToIgnoreCase has a single String argument, so it can't come in place of a Comparator<String>, which requires a method with two String arguments.

In FakeString2, your compareToIgnoreCase has an implicit FakeString argument (this) and a String argument, so, again, it can't come in place of a Comparator<String>.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • 2
    But `String#compareToIgnoreCase` also have only one string argument. – Jean-François Savard Apr 19 '15 at 20:09
  • 5
    @Jean-FrançoisSavard `String#compareToIgnoreCase`, just like `String#compareTo` has an implicit second String argument - this, which allows it to be used where a method with two String arguments is required. – Eran Apr 19 '15 at 20:11
  • I think that if you develop on the idea of an `implicit` argument, you will get the expected answer. I'm also interested to read that even if now I can see why it works – Dici Apr 19 '15 at 20:12
  • @Eran I know that, as Dici also mentioned I think this is an important point to the answer, which is why I mentioned it. Someone who does not know that won't understand your sentences. – Jean-François Savard Apr 19 '15 at 20:13
  • @Eran your answer to @Jean-FrançoisSavard about the implicit `this` String argument made sense and I thought that was the explanation I was looking for. I modified my example above `Arrays.sort(fakeStringArray, FakeString::compareToIgnoreCase);` and also modified the FakeString class methods to compare on FakeString instead of String. I still get the compile error. – Rich Apr 19 '15 at 23:15
  • @Eran you are correct. In my last comment `Arrays.sort(fakeStringArray,FakeString::compareToIgnoreCase);` does not compile because the class `FakeString` is static. I tried it with the `FakeString2` class, by creating a `FakeString2[]` and then `Arrays.sort(fakeString2Array, FakeString2::compareToIgnoreCase);` does compile !!! – Rich Apr 20 '15 at 02:04
  • @Eran "String#compareToIgnoreCase, just like String#compareTo has an implicit second String argument", this is not mentioned in Java documentation...? although one can guess it has... – user1169587 Aug 16 '20 at 10:58
  • 1
    @user1169587 For every instance method of every class you can say that the instance for which the method is called is an implicit argument of that method. There's no reason to mention it in the Javadoc, since it's not an actual argument. The implicit argument becomes important when you use a method reference to an instance method. – Eran Aug 16 '20 at 11:05