当前位置: 首页 > 面试题库 >

引用Java8中具有不同参数的方法

郭子航
2023-03-14
问题内容

我想知道带有方法引用和功能接口的所有这些东西如何在较低级别上工作。最简单的例子是我们有一些列表

List<String> list = new ArrayList<>();
list.add("b");
list.add("a");
list.add("c"):

现在我们要使用Collections类对其进行排序,因此我们可以调用:

Collections.sort(list, String::compareToIgnoreCase);

但是,如果我们定义自定义比较器,则可能类似于:

Comparator<String> customComp = new MyCustomOrderComparator<>();
Collections.sort(list, customComp::compare);

问题在于Collections.sort具有两个参数:List和Comparator。由于Comparator是功能性接口,因此可以用具有相同签名(参数和返回类型)的lambda表达式或方法引用替换。那么,我们如何也可以传递对compareTo的引用,后者仅接受一个参数,而这些方法的签名不匹配?方法引用在Java8中如何翻译?


问题答案:

从Oracle方法参考教程:

引用特定类型的任意对象的实例方法

以下是对特定类型的任意对象的实例方法的引用示例:

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

方法参考的等效lambda表达式String::compareToIgnoreCase将具有形式参数列表(String a, String b),其中a和b是用于更好地描述此示例的任意名称。该方法引用将调用该方法a.compareToIgnoreCase(b)

但是,::操作员的真正含义是什么?好吧,::运营商可以这样描述(从这个SO问题):

可以以不同的样式获得方法参考,但是它们的含义相同:

  1. 静态方法(ClassName::methodName
  2. 特定对象的实例方法(instanceRef::methodName
  3. 特定对象的超级方法(super::methodName
  4. 特定类型(ClassName::methodName)的任意对象的实例方法
  5. 类构造函数参考(ClassName::new
  6. 数组构造函数参考(TypeName[]::new

因此,这意味着该方法引用String::compareToIgnoreCase属于第二类(instanceRef::methodName),这意味着它可以转换为(a,b) -> a.compareToIgnoreCase(b)

我相信以下示例进一步说明了这一点。甲Comparator<String>包含运行在两个一种方法String的操作数并返回int。可以将其伪描述为(a,b) ==> return int(其中操作数为ab)。如果您以这种方式查看,则以下所有内容都属于该类别:

// Trad anonymous inner class
// Operands: o1 and o2. Return value: int
Comparator<String> cTrad = new Comparator<String>() {
    @Override
    public int compare(final String o1, final String o2) {
        return o1.compareToIgnoreCase(o2);
    }
};

// Lambda-style
// Operands: o1 and o2. Return value: int
Comparator<String> cLambda = (o1, o2) -> o1.compareToIgnoreCase(o2);

// Method-reference à la bullet #2 above. 
// The invokation can be translated to the two operands and the return value of type int. 
// The first operand is the string instance, the second operand is the method-parameter to
// to the method compareToIgnoreCase and the return value is obviously an int. This means that it
// can be translated to "instanceRef::methodName".
Comparator<String> cMethodRef = String::compareToIgnoreCase;

这个很棒的SO-
answer解释了lambda函数是如何编译的
。Jarandinor在那个答案中引用了Brian
Goetz优秀文档中的以下段落,该段落更多地描述了lambda翻译。

我们没有描述生成用于实现lambda表达式的对象的字节码(例如,调用内部类的构造函数),而是描述了构造lambda的方法,并将实际构造委托给语言运行时。该配方被编码在invokedynamic指令的静态和动态参数列表中。

基本上,这意味着本地运行时决定如何转换lambda。

Brian继续说:

方法引用与lambda表达式的处理方式相同,不同之处在于,大多数方法引用不需要分解为新方法;我们可以简单地为引用的方法加载一个常量方法句柄,并将其传递给元工厂

所以,lambda表达式被 到一个 新的方法 。例如

class A {
    public void foo() {
        List<String> list = ...
        list.forEach( s -> { System.out.println(s); } );
    }
}

上面的代码将 简化 为以下形式:

class A {
    public void foo() {
        List<String> list = ...
        list.forEach( [lambda for lambda$1 as Consumer] );
    }

    static void lambda$1(String s) {
        System.out.println(s);
    }
}

但是,Brian也在文档中对此进行了解释:

如果desugared方法是实例方法,则将接收方视为第一个参数

Brian继续解释说 ,lambda剩余的参数作为参数传递给所引用的方法

因此,借助Moandji Ezana的此项,可以将compareToIgnoreCaseas 的废除Comparator<String>分解为以下步骤:

  • Collections#sort对于一个List<String>期望Comparator<String>
  • Comparator<String>是方法的功能接口,int sort(String, String)相当于BiFunction<String, String, Integer>
  • 因此,比较器实例可以由BiFunction-compatible lambda提供:(String a, String b) -> a.compareToIgnoreCase(b)
  • String::compareToIgnoreCase引用带有String参数的实例方法,因此它与上述lambda兼容:String a成为接收方并String b成为方法参数

编辑: 从OP输入后,我添加了一个 低级 示例,说明了 废止



 类似资料:
  • 问题内容: 我们可以创建相同的GET URI,但使用不同的查询参数吗? 例如,我有两个REST GET URI: 现在,REST服务无法将两个GET方法识别为单独的,而仅将其声明为第一个GET方法。 为什么会这样呢? 有什么办法可以使两个具有不同查询参数的GET方法? 如果您可以引用任何资源,将不胜感激。 问题答案: 因为 资源 是由其 PATH 唯一标识的,而不是由其参数唯一标识的。您定义的两个

  • 我们可以创建相同的GET URI但使用不同的查询参数吗? 例如,我有两个REST GET URI: 现在REST服务没有将两个GET方法识别为单独的,并且只将其视为声明为第一个的1 GET方法。 为什么会这样 如果您能引用任何资源,我们将不胜感激。

  • 目前,我得到了以下函数,该函数遍历Pandas DataFrame()列并创建一个计数: 我有许多参数要分配给/调用函数——目前,我正在做以下工作: 这样做效果很好,但我想知道是否有更实用/有效的方法可以达到同样的效果。 我正在考虑将变量名添加到列表中,例如、、、等,并形成某种循环——如果这样做可行的话。。。 因此,也许有点像: 或者,有没有使用熊猫的方法?

  • 我想为相同的url模式创建两个具有不同参数的方法 Spring支持这一点,并且工作正常。SpringDoc没有。它使用2个参数创建一个endpoint。这是一个已知的问题吗?

  • 问题内容: 我正在尝试获得类似这样的功能的参考: 我有以下错误: 如何获得具有指定参数的功能? 问题答案: 由于有两个名称相同但签名不同的方法,因此您必须指定所需的方法: 或者(如@Antonio正确指出的): 如果您需要将类的实例作为第一个参数的咖喱函数,则可以以相同的方式进行,只有签名不同(将@Antonios注释与您的问题进行比较):

  • 问题内容: 我想验证以下行为的方法如下。 在我的@Test类中,我希望做这样的事情来验证是否使用“ exception.message”和再次使用“ exception.detail”进行了调用 但是Mockito抱怨​​如下 我如何告诉Mockito检查两个值? 问题答案: 进一步的阅读使我尝试使用ArgumentCaptors和以下作品,尽管比我想要的更为冗长。