Java 8和方法引用 - 特别是compareToIgnoreCase

11

我读了Java 8教程中关于Lambda表达式的部分,但是对于“特定类型的任意对象的实例方法引用”的方法引用示例还不太理解。

在同样的教程中,有一个名为“特定对象的实例方法引用”的示例,看起来是这样的:

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

我能看出这个方法是可行的,因为compareByName方法与Comparator.compare具有相同的签名,lambda (a, b) -> myComparisonProvider.compareByName(a, b)接收两个参数并调用一个带有相同两个参数的方法。

现在,“对特定类型任意对象的实例方法的引用”示例使用String::compareToIgnoreCase。

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

该方法的签名为int compareTo(String anotherString),与Comparator.compare不同。教程并不是很清楚,但似乎意味着您最终会得到一个lambda表达式,例如(a, b) -> a.compareToIgnoreCase(b)。我不明白编译器如何决定第二个参数对Arrays.sort是可接受的。我认为也许编译器足够智能,可以理解如何调用该方法,因此我创建了一个示例。

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

请问为什么上述两个 Arrays.sort 不编译,即使它们使用的方法与 String.compareToIgnoreCase 方法相同?


请参考 Stackoverflow 上的 String::compareToIgnoreCase - Jaydip Halake
2个回答

8
这是在某个对象上使用方法引用和在正在处理的对象上使用方法引用之间的区别。
首先看一下Oracle的例子。
让我们先看第一个案例:
public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
}
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

这里,compareByName 方法会在 sort 算法中的每一对参数上被调用,并且传入 myComparisonProvider 的实例。

因此,在比较 ab 时,我们实际上调用了:

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

现在,进入第二种情况:
String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

你正在对一个String[]进行排序,因此在当前正在排序的String实例上调用compareToIgnoreCase方法,并将另一个String作为参数。

因此,在比较ab时,我们实际上调用了:
final int c = a.compareToIgnoreCase(b);

以下是两种不同的情况:

  • 一种是在任意对象实例上传递方法;
  • 一种是在正在处理的实例上调用方法。

关于你的示例

在你的第一个示例中,你还有一个 String[] 并尝试对其进行排序。所以:

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

因此,在比较ab时,我们实际上调用了:

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

唯一的区别是 comparestatic
Arrays.sort(stringArray, FakeString::compareToIgnoreCase);

现在,String[]不是FakeString[],因此我们不能在String上调用该方法。因此,我们必须在FakeString上调用一个static方法。但我们也不能这样做,因为我们需要一个(String, String) -> int的方法,但我们只有一个(String) -> int的方法 - 编译错误。
在第二个例子中,问题完全相同,因为你仍然有一个String[]。并且compareToIgnoreCase具有错误的签名。
简而言之,你忽略了一个重要点:在String::compareToIgnoreCase示例中,该方法是在当前处理的String上调用的。

1
因为有 tl;dr 所以点赞。我认为这是 OP 未能注意到的主要重点。 - Jean-François Savard
我刚明白了,不过读了好几遍。虽然它是有意义的,但是 Oracle 官方网站在这个特定主题上缺乏更多的解释。你不能只用一行话就把它扔掉,然后期望每个人都能理解。这只是我的诚实看法。 - cesarmax

6
在FakeString中,您的compareToIgnoreCase只有一个String参数,因此无法替代需要有两个String参数的Comparator。
在FakeString2中,您的compareToIgnoreCase具有隐式的FakeString参数(this)和一个String参数,因此它也不能替代Comparator。

2
但是 String#compareToIgnoreCase 也只有一个字符串参数。 - Jean-François Savard
5
String#compareToIgnoreCaseString#compareTo一样,有一个隐式的第二个String参数 - this,这使它能够在需要两个String参数的方法中使用。 - Eran
我认为如果你在“隐式”参数的思路上进行开发,你会得到预期的答案。我也很有兴趣阅读即使现在我能够看到它为什么有效的内容。 - Dici
@Eran 我知道,正如Dici也提到的那样,我认为这是回答中一个重要的点,这就是为什么我提到它。不知道这一点的人将无法理解你的句子。 - Jean-François Savard
1
@user1169587 对于每个类的每个实例方法,您可以说调用该方法的实例是该方法的隐式参数。在Javadoc中没有必要提及它,因为它不是一个实际的参数。当您使用方法引用来引用实例方法时,隐式参数变得很重要。 - Eran
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接