Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 and method references - specifically compareToIgnoreCase

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

like image 377
Rich Avatar asked Apr 19 '15 20:04

Rich


People also ask

What is method reference and constructor references in Java 8?

A method reference is similar to lambda expression used to refer a method without invoking it while constructor reference used to refer to the constructor without instantiating the named class. A method reference requires a target type similar to lambda expressions.

What is difference between lambda expression and method reference in Java?

Lambda expression is an anonymous method (method without a name) that has used to provide the inline implementation of a method defined by the functional interface while a method reference is similar to a lambda expression that refers a method without executing it.

WHAT ARE method references and how are they useful?

Method references are a special type of lambda expressions. They're often used to create simple lambda expressions by referencing existing methods. There are four kinds of method references: Static methods. Instance methods of particular objects.


1 Answers

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.

like image 109
Boris the Spider Avatar answered Nov 16 '22 02:11

Boris the Spider