- 此问题在此处已有答案**:
How does method reference casting work?(3个答案)
3天前关闭。
此帖子已于3天前编辑并提交审核,未能重新打开帖子:
原始关闭原因未解决
我正在学习Java 8中的Lambda、Streams和方法引用。
Optional<String> s = Optional.of("test");
System.out.println(s.map(String::toUpperCase).get());
我不明白怎么可能使用String::toUpperCase
作为map()
方法的输入。
以下是方法实现:
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));
}
}
所以它需要一个函数接口,并且有这个apply()方法:R apply(T t);
这个方法有一个输入参数。toUpperCase()
方法没有任何参数:
public String toUpperCase() {
return toUpperCase(Locale.getDefault());
}
如果abstract
方法apply(T t)
有 * 一个 * 参数,那么实现的方法应该有 * 一个 * 相同类型的参数。无参数方法toUpperCase()
如何从函数接口实现apply(T t)
方法?
我试着重现同样的情况:
我创建了一个函数接口:
public interface Interf {
String m1(String value);
}
然后我创建一个类,其中包含m1()的方法引用:
public class Impl {
public String value;
public String toUpp() {
return value.toUpperCase();
}
}
下面是一个测试类:
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"));
}
}
此语句没有任何问题:Interf i = String::toUpperCase;
,但此行存在编译错误:Interf i1 = Impl::toUpp;
。它说:
不能从静态上下文引用非静态方法
但是toUpperCase()
也是一个非静态方法,即使我把toUpp()
设为静态,它仍然不起作用,只有当我添加一个String参数作为toUpp()
的输入参数时,它才起作用,但是为什么它对String::toUpperCase
起作用呢?
2条答案
按热度按时间7cwmlq891#
TL; DR
根据方法引用实现的Functional接口所施加的约定,方法引用预期使用的参数不必须与方法引用中使用的方法参数相同。
这个答案是从这个常见的误解到理解方法引用的所有语法风格的一个旅程。
让我们从Method引用的定义开始,采取一些小步骤来消除误解。
什么是方法引用
以下是根据Java语言规范§ 15.13的方法引用的定义。
着重号后加
因此,Method引用是一种引用方法调用而不调用方法的方式,或者换句话说,它是一种通过委托现有功能来描述特定行为的方式。
让我们绕个小圈子,看看Method引用的兄弟,一个Lambda表达式。
Lambda表达式也是一种描述行为的方式(不执行它),并且lambdas和Method引用都应该符合Functional接口。
考虑一下,我们有一个域类
Foo
和实用程序类FooUtils
。我们需要定义一个
UnaryOperator<Foo>
类型的函数,让我们从写一个lambda表达式开始:Lambda接收一个
Foo
的示例作为参数,并将其提供给现有的实用方法。非常简单,对吧?在lambda的主体中没有发生任何特殊的事情,并且由于已经将类型定义为UnaryOperator<Foo>
,lambda应该期望Foo
。所有事情都是绝对可预测的,不是吗?现在的问题是:我们能换个主题吗?当然,我们可以!
这就是Method引用的作用所在,它提供了一个简短的语法:
∮ ∮ ∮ ∮ ∮一米六一分
更多示例
让我们再看几个例子,假设我们有一个简单的对象来表示一个硬币,它有一个属性
isHeads
来描述硬币的哪一面(即heads or tails)。让我们生成一个硬币,为此我们可以使用
Supplier
的实现,它非常慷慨,供应商不接收参数,它产生一个值,让我们定义一个lambda和一个引用两者都不接收任何参数(根据供应商的合同),两者都引用无参数构造函数。
现在让我们考虑一下通过lambda和方法引用来确定硬币是否显示正面的 predicate :
同样,lambda和方法引用都符合 predicate 的契约,并且都接收
Coin
的示例作为参数(否则不可能,方法引用的简单语法并没有显示这一点)。到目前为止,一切顺利吗?让我们进一步尝试另一种方法来获得
Coin
,让我们定义一个Function
:现在lambda和引用都使用
boolean
值并使用参数化构造函数。没有注意到描述Supplier<Coin>
和Function<Boolean, Coin>
的方法引用看起来相同。提醒:Lambda表达式和方法引用本身都没有类型。它们是所谓的多表达式,这意味着编译器应根据它们出现的上下文来推断它们的类型。lambda和引用都应符合函数接口,并且它们实现的接口指定它们是谁以及它们正在做什么。
在前面描述的所有例子中,被方法引用消耗的参数看起来和被引用的方法所期望的参数是一样的,但是它们不必须是相同的,现在是时候检查一些例子了,在这些例子中,不需要消 debugging 觉。
让我们考虑一个
UnaryOperator
反转硬币:UnaryOperator
的所有实现都接收一个Coin
示例作为参数,并且由于reverse()
的调用而产生另一个硬币,reverse是无参数的这一事实不是问题,因为我们关心的是它产生什么,而不是消耗什么。让我们尝试定义一个更严格的方法引用,首先在
Coin
类中引入一个新的示例方法xor()
,它对于XOR-ing两枚硬币非常有用:现在,当两个对象开始工作时,我们有了更多的可能性,让我们从最简单的情况开始,定义一个
UnariOperator
:在上面的例子中,在函数外部定义的
Coin
示例用于通过instance-method执行转换。稍微复杂一点的情况是
BinaryOperator
对几个硬币进行异或运算,如下所示:现在,我们有两个参数作为输入,并且根据
BinaryOperator
的约定,Coin
示例应该作为输出生成。有趣的是,第一个参数充当了一个示例,
xor()
方法将在该示例上被调用,第二个参数被传递给该方法(注意xor()
只需要一个参数)。你可能会问,如果有另一种方法对硬币进行异或运算,
static
方法需要两个参数:那么编译器将无法解析方法引用,因为这里我们有多个可能适用的方法,并且由于参数类型相同,它们中没有一个可以被认为比另一个更具体,这将导致编译错误,但如果我们有其中一个(而不是两个一起),引用
Coin::xor
将工作正常。方法引用类型
基本上,我们已经走过的例子涵盖了所有类型的方法引用。现在,让我们列举它们。
official tutorial provided by Oracle re是四种方法引用:
1.* * 引用静态方法**
引用静态方法
xor(Coin coin1, Coin coin2)
的示例Coin::xor
。标准JDK类的示例:
1.* * 引用特定对象的示例方法**
举例说明这种情况的例子是示例方法
xor(Coin other)
的用法,其中在函数外部定义了一个coin,该coin在内部用于调用xor()
,并将函数参数传递到该方法中。标准JDK类的示例:
1.* * 对特定类型的任意对象的示例方法的引用**
在这种情况下,方法refernce对一个作为参数出现的示例进行操作(只有当我们使用lambda时才能引用它),因此,* containing type *(可以是实际类型或超类型之一)用于引用该示例。
一个例子是
Predicate
检查硬币是否显示正面Coin::isHeads
。标准JDK类的示例:
1.* * 引用构造函数**
我们已经用下列例子说明了这种情况:
Supplier<Coin>
引用没有作为Coin::new
实现的args构造函数;Function<Boolean, Coin>
,它通过传递传入的boolean
值(也表示为Coin::new
)来使用单参数构造函数。t98cgbkg2#
toUpperCase()方法如何从Function接口实现apply(T t)方法?
方法引用
String::toUpperCase
具有未绑定的接收器。Java 8: Difference between method reference Bound Receiver and UnBound Receiver则参数T t
将是接收器在你的例子中
通过调用
map(String::toUpperCase)
如果
value
(假设等于"Example String"
)是接收方,则mapper.apply("Example String");
将等于"Example String".toUpperCase();