我正在探索Java 8源代码,发现代码的这一特定部分非常令人惊讶:
//defined in IntPipeline.java
@Override
public final OptionalInt reduce(IntBinaryOperator op) {
return evaluate(ReduceOps.makeInt(op));
}
@Override
public final OptionalInt max() {
return reduce(Math::max); //this is the gotcha line
}
//defined in Math.java
public static int max(int a, int b) {
return (a >= b) ? a : b;
}
Math::max
类似于方法指针吗?普通 static
方法如何转换为 IntBinaryOperator
?
16 回答
我发现this source非常有趣 .
事实上, Lambda 变成了 Double Colon . Double Colon更具可读性 . 我们遵循以下步骤:
STEP1:
STEP2:
STEP3:
它似乎有点晚了,但这是我的两分钱 . lambda expression用于创建匿名方法 . 除了调用现有方法之外什么都不做,但直接通过名称引用该方法更为明确 . 并且method reference使我们能够使用方法引用运算符
::
来实现 .考虑以下简单类,其中每个员工都有一个名称和等级 .
假设我们有一个通过某种方法返回的员工列表,我们希望按其等级对员工进行排序 . 我们知道我们可以使用anonymous class作为:
其中getDummyEmployee()是一些方法:
现在我们知道Comparator是一个功能接口 . Functional Interface只有一个抽象方法(尽管它可能包含一个或多个默认或静态方法) . 所以我们可以使用lambda表达式:
看起来一切都很好但是如果类
Employee
也提供了类似的方法:在这种情况下,使用方法名称本身将更加清晰 . 因此我们可以通过使用方法引用直接引用方法:
根据docs,有四种方法引用:
是的,这是事实 .
::
运算符用于方法引用 . 因此,可以通过使用它或对象中的方法从类中提取 static 方法 . 即使对于构造函数,也可以使用相同的运算符 . 这里提到的所有案例都在下面的代码示例中举例说明 .可以在here找到Oracle的官方文档 .
您可以在this文章中更好地概述JDK 8的更改 . 在 Method/Constructor referencing 部分还提供了一个代码示例:
由于这里的许多答案都很好地解释了
::
行为,另外我想澄清一下::
operator doesnt need to have exactly same signature as the referring Functional Interface if it is used for instance variables . 让我们假设我们需要BinaryOperator,其类型为 TestObject . 以传统方式实现如下:正如您在匿名实现中看到的那样,它需要两个TestObject参数并返回一个TestObject对象 . 要通过使用
::
运算符来满足此条件,我们可以从静态方法开始:然后打电话:
好吧编译好了 . 如果我们需要实例方法呢?让我们用实例方法更新TestObject:
现在我们可以访问如下实例:
这段代码编译得很好,但是下面没有:
我的日食告诉我 "Cannot make a static reference to the non-static method testInstance(TestObject, TestObject) from the type TestObject ..."
很公平它的实例方法,但如果我们重载
testInstance
如下:并致电:
代码将编译正常 . 因为它将使用单个参数而不是双参数调用
testInstance
. 好的,我们的两个参数发生了什么?让我们打印输出,看看:哪个会输出:
好的,所以JVM足够聪明,可以调用param1.testInstance(param2) . 我们可以从另一个资源使用
testInstance
但不能使用TestObject,即:并致电:
它只是不编译,编译器会告诉: "The type TestUtil does not define testInstance(TestObject, TestObject)" . 因此,如果编译器不是同一类型,它将寻找静态引用 . 那么多态性怎么样?如果我们删除final修饰符并添加 SubTestObject 类:
并致电:
它也不会编译,编译器仍然会寻找静态引用 . 但是下面的代码将编译正常,因为它传递的是一个测试:
通常,可以使用
Math.max(int, int)
调用reduce
方法,如下所示:这需要很多语法才能调用
Math.max
. 这就是lambda表达式发挥作用的地方 . 从Java 8开始,它允许以更短的方式执行相同的操作:这是如何运作的? java编译器"detects",您要实现一个接受两个
int
并返回一个int
的方法 . 这相当于接口IntBinaryOperator
(您要调用的方法reduce
的参数)的唯一方法的形式参数 . 因此,编译器会为您完成剩下的工作 - 它只是假设您要实现IntBinaryOperator
.但由于
Math.max(int, int)
本身符合IntBinaryOperator
的形式要求,因此可以直接使用 . 因为Java 7没有任何允许方法本身作为参数传递的语法(您只能传递方法结果,但从不传递方法引用),所以在Java 8中引入了::
语法来引用方法:请注意,这将由编译器解释,而不是由运行时的JVM解释!虽然它产生了不同所有三个代码片段的字节码,它们在语义上是相等的,因此最后两个可以被认为是上面的
IntBinaryOperator
实现的短(并且可能更有效)版本!(另见Translation of Lambda Expressions)
这是Java 8中的方法引用.orax文档是here .
如文件中所述......
所以 I see here tons of answers that are frankly overcomplicated, and that's an understatement.
答案很简单: :: it's called a Method References https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
所以我不会复制粘贴,在链接上,如果向下滚动到表格,你可以找到所有信息 .
现在,让我们来看看什么是方法参考:
A::B 在某种程度上替代了以下内联lambda表达式: (params ...) -> A.B(params ...)
要将此与您的问题相关联,有必要了解java lambda表达式 . 哪个不难 .
内联lambda表达式类似于定义的 functional interface (which is an interface that has no more and no less than 1 method) . 我们来看看我的意思:
InterfaceX必须是一个功能接口 . 任何功能接口,InterfaceX对于该编译器唯一重要的是您定义格式:
InterfaceX可以是以下任何一种:
或这个
或更通用的:
让我们先看一下我们之前定义的第一个案例和内联lambda表达式 .
在Java 8之前,你可以用这种方式定义它:
从功能上讲,这是一回事 . 不同之处在于编译器如何看待它 .
现在我们看一下内联lambda表达式,让我们返回Method References(::) . 假设你有一个这样的类:
由于方法anyFunctions具有与InterfaceX callMe相同的类型,因此我们可以将这两个类型与Method Reference相提并论 .
我们可以这样写:
这相当于:
方法参考的一个很酷的事情和优点是,首先,在将它们分配给变量之前,它们是无类型的 . 因此,您可以将它们作为参数传递给任何等效的(具有相同的定义类型)功能接口 . 这恰恰是你的情况
::被称为方法引用 . 假设我们想调用类Purchase的calculatePrice方法 . 然后我们可以把它写成:
它也可以看作是编写lambda表达式的简短形式因为方法引用被转换为lambda表达式 .
:: Operator 是在java 8中引入的方法引用 . 方法引用是仅执行一个方法的lambda表达式的简写语法 . 这是方法引用的一般语法:
我们知道我们可以使用lambda expressions而不是使用匿名类 . 但有时,lambda表达式实际上只是对某些方法的调用,例如:
为了使代码更清晰,可以将lambda表达式转换为方法引用:
在运行时它们的行为完全相同 . 字节码可能/不相同(对于上面的Incase,它生成相同的字节码(上面的complie并检查javaap -c;))
在运行时,它们的行为完全相同 . 方法(math :: max);,它生成相同的数学运算(上面的complie并检查javap -c;))
在较旧的Java版本中,您可以使用以下命令代替“::”或lambd:
或者传递给方法:
::
称为方法参考 . 它基本上是对单个方法的引用 . 即它指的是名称的现有方法 .Short Explanation :
下面是静态方法的引用示例:
square
可以像对象引用一样传递,并在需要时触发 . 实际上,它可以像static
那样容易地用作对象方法的引用 . 例如:Function
上面是 functional interface . 要完全理解::
,了解功能接口也很重要 . 显然,functional interface是一个只有一个抽象方法的接口 .功能接口的示例包括
Runnable
,Callable
和ActionListener
.Function
上面是一个只有一种方法的功能界面:apply
. 它需要一个参数并产生一个结果 .::
s真棒的原因是that:例如 . 而不是写lambda身体
你可以干脆做
在运行时,这两个
square
方法的行为完全相同 . 字节码可能相同也可能不相同(但是,对于上述情况,生成相同的字节码;编译以上内容并查看javap -c
) .唯一满足的主要标准是: the method you provide should have a similar signature to the method of the functional interface you use as object reference .
以下是非法的:
square
需要一个参数并返回double
. Supplier中的get
方法需要一个参数但不返回任何内容 . 因此,这导致错误 .A method reference refers to the method of a functional interface. (如上所述,功能接口每个只能有一个方法) .
更多示例:Consumer中的
accept
方法接受输入但不返回任何内容 .上面,
getRandom
不带参数并返回double
. 因此,可以使用满足以下条件的任何功能接口: take no argument and return double .另一个例子:
In case of parameterized types :
方法引用可以有不同的样式,但从根本上它们都意味着相同的东西,可以简单地可视化为lambdas:
静态方法(
ClassName::methName
)特定对象的实例方法(
instanceRef::methName
)特定对象的超级方法(
super::methName
)特定类型的任意对象的实例方法(
ClassName::methName
)类构造函数引用(
ClassName::new
)数组构造函数引用(
TypeName[]::new
)有关进一步参考,请参阅http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html .
在java-8 Streams中,简单工作中的Reducer是一个函数,它将两个值作为输入并在一些计算后返回结果 . 这个结果在下一次迭代中被输入 .
在Math:max函数的情况下,方法保持返回最多两个传递的值,最后你手头有最大的数字 .
return reduce(Math::max);
是 NOT EQUAL 到return reduce(max());
但它意味着,像这样:
如果这样写,你可以节省47次击键
关于
::
方法参考的内容,之前的答案非常完整 . 总而言之,它提供了一种在不执行它的情况下引用方法(或构造函数)的方法,并且在评估时,它创建了提供目标类型上下文的功能接口的实例 .下面是两个示例,用于在
ArrayList
WITH中查找具有最大值的对象,并且不使用::
方法引用 . 解释见下面的评论 .没有使用
::
使用
::
::
是Java 8中包含的一个新运算符,用于引用现有类的方法 . 您可以引用类的静态方法和非静态方法 .对于引用静态方法,语法是:
对于引用非静态方法,语法是
和
引用方法的唯一先决条件是方法存在于功能接口中,该接口必须与方法引用兼容 .
在评估时,方法引用创建功能接口的实例 .
发现于:http://www.speakingcs.com/2014/08/method-references-in-java-8.html