Java 8 中的 ::(双冒号)运算符
Posted
技术标签:
【中文标题】Java 8 中的 ::(双冒号)运算符【英文标题】::: (double colon) operator in Java 8 【发布时间】:2013-11-28 20:48:06 【问题描述】:我正在探索 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
?
【问题讨论】:
让编译器根据您提供的函数自动生成接口实现是一种语法糖(使整个 lambda 更容易与现有代码库一起使用)。 java.dzone.com/articles/java-lambda-expressions-vs 可能会有所帮助,但没有深入探讨该主题 @Neet 它不完全是“语法糖”,除非你能说出什么。即“x 是 y 的语法糖”。 @Ingo 每次我使用它时都会创建一个新的 lambda 对象。TestingLambda$$Lambda$2/8460669
和 TestingLambda$$Lambda$3/11043253
是在两次调用中创建的。
Lambda 和方法引用不是“普通的旧匿名内部类”。见programmers.stackexchange.com/a/181743/59134。是的,如有必要,新的类和实例会在必要时即时创建,但仅限于必要时。
【参考方案1】:
通常,使用Math.max(int, int)
调用reduce
方法如下:
reduce(new IntBinaryOperator()
int applyAsInt(int left, int right)
return Math.max(left, right);
);
这需要很多语法才能调用Math.max
。这就是 lambda 表达式发挥作用的地方。从 Java 8 开始,它允许以更短的方式做同样的事情:
reduce((int left, int right) -> Math.max(left, right));
这是如何工作的? java 编译器“检测到”您想要实现一个接受两个int
s 并返回一个int
的方法。这相当于接口IntBinaryOperator
的唯一方法的形参(你要调用的方法reduce
的参数)。所以编译器会为你完成剩下的工作——它只是假设你想要实现IntBinaryOperator
。
但由于Math.max(int, int)
本身满足IntBinaryOperator
的形式要求,所以可以直接使用。因为 Java 7 没有任何允许将方法本身作为参数传递的语法(您只能传递方法结果,但不能传递方法引用),所以在 Java 8 中引入了 ::
语法来引用方法:
reduce(Math::max);
请注意,这将由编译器解释,而不是在运行时由 JVM 解释!尽管它为所有三个代码 sn-ps 生成不同的字节码,但它们在语义上是相等的,因此最后两个可以被认为是上述 IntBinaryOperator
实现的短版本(并且可能更有效)!
(另见Translation of Lambda Expressions)
【讨论】:
【参考方案2】:::
称为方法参考。它基本上是对单一方法的引用。即它通过名称引用现有方法。
简短说明: 下面是引用静态方法的示例:
class Hey
public static double square(double num)
return Math.pow(num, 2);
Function<Double, Double> square = Hey::square;
double ans = square.apply(23d);
square
可以像对象引用一样被传递并在需要时触发。事实上,它可以像static
一样容易地用作对象的“普通”方法的引用。例如:
class Hey
public double square(double num)
return Math.pow(num, 2);
Hey hey = new Hey();
Function<Double, Double> square = hey::square;
double ans = square.apply(23d);
上面的Function
是一个功能接口。要完全理解::
,理解函数式接口也很重要。很明显,functional interface 是一个只有一个抽象方法的接口。
功能接口的示例包括Runnable
、Callable
和ActionListener
。
Function
上面是一个函数式接口,只有一个方法:apply
。它接受一个参数并产生一个结果。
::
s 厉害的原因是that:
方法引用是与 lambda 表达式 (...) 具有相同处理方式的表达式,但它们不提供方法主体,而是按名称引用现有方法。
例如而不是编写 lambda 主体
Function<Double, Double> square = (Double x) -> x * x;
你可以这样做
Function<Double, Double> square = Hey::square;
在运行时,这两个square
方法的行为完全相同。字节码可能相同,也可能不同(尽管对于上述情况,生成相同的字节码;编译上述内容并使用javap -c
检查)。
唯一要满足的主要标准是:您提供的方法应该与您用作对象引用的功能接口的方法具有相似的签名。
以下是非法的:
Supplier<Boolean> p = Hey::square; // illegal
square
需要一个参数并返回一个double
。 Supplier 中的 get
方法返回一个值但不带参数。因此,这会导致错误。
方法引用是指功能接口的方法。(如前所述,功能接口每个只能有一个方法)。
更多示例:Consumer 中的 accept
方法接受输入但不返回任何内容。
Consumer<Integer> b1 = System::exit; // void exit(int status)
Consumer<String[]> b2 = Arrays::sort; // void sort(Object[] a)
Consumer<String> b3 = MyProgram::main; // void main(String... args)
class Hey
public double getRandom()
return Math.random();
Callable<Double> call = hey::getRandom;
Supplier<Double> call2 = hey::getRandom;
DoubleSupplier sup = hey::getRandom;
// Supplier is functional interface that takes no argument and gives a result
在上面,getRandom
不接受任何参数并返回 double
。因此,任何满足以下条件的函数式接口:不带参数并返回double
都可以使用。
另一个例子:
Set<String> set = new HashSet<>();
set.addAll(Arrays.asList("leo","bale","hanks"));
Predicate<String> pred = set::contains;
boolean exists = pred.test("leo");
如果是参数化类型:
class Param<T>
T elem;
public T get()
return elem;
public void set(T elem)
this.elem = elem;
public static <E> E returnSame(E elem)
return elem;
Supplier<Param<Integer>> obj = Param<Integer>::new;
Param<Integer> param = obj.get();
Consumer<Integer> c = param::set;
Supplier<Integer> s = param::get;
Function<String, String> func = Param::<String>returnSame;
方法引用可以有不同的样式,但从根本上说,它们都意味着相同的东西,并且可以简单地可视化为 lambda:
-
静态方法 (
ClassName::methName
)
特定对象的实例方法 (instanceRef::methName
)
特定对象的超级方法 (super::methName
)
特定类型的任意对象的实例方法 (ClassName::methName
)
类构造函数引用 (ClassName::new
)
数组构造函数引用 (TypeName[]::new
)
如需进一步参考,请参阅http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html。
【讨论】:
感谢您的解释。综上所述:'::'用于提取满足FunctionalInterface(lambda)的方法:ClassX::staticMethodX, or instanceX::instanceMethodX" 嗨@Jatin,这不是因为方法不是First-class citizen而违反OOP范式吗?【参考方案3】:是的,这是真的。 ::
运算符用于方法引用。因此,可以通过使用它从类中提取静态方法或从对象中提取方法。相同的运算符甚至可以用于构造函数。这里提到的所有案例都在下面的代码示例中举例说明。
Oracle的官方文档可以在here找到。
您可以在this 文章中更好地了解 JDK 8 的变化。在方法/构造函数引用部分还提供了一个代码示例:
interface ConstructorReference
T constructor();
interface MethodReference
void anotherMethod(String input);
public class ConstructorClass
String value;
public ConstructorClass()
value = "default";
public static void method(String input)
System.out.println(input);
public void nextMethod(String input)
// operations
public static void main(String... args)
// constructor reference
ConstructorReference reference = ConstructorClass::new;
ConstructorClass cc = reference.constructor();
// static method reference
MethodReference mr = cc::method;
// object method reference
MethodReference mr2 = cc::nextMethod;
System.out.println(cc.value);
【讨论】:
一个很好的解释是在这里找到的:doanduyhai.wordpress.com/2012/07/14/… @RichardTinglemethod(Math::max);
是调用,方法的定义类似于public static void method(IntBinaryOperator op)System.out.println(op.applyAsInt(1, 2));
。这就是它的使用方式。
对于熟悉 C# 的人来说,它类似于 DelegateType d = new DelegateType(MethodName);【参考方案4】:
似乎有点晚了,但这是我的两分钱。 lambda expression 用于创建匿名方法。它除了调用现有方法之外什么也不做,但直接通过名称来引用该方法会更清楚。而method reference 使我们能够使用方法引用运算符::
来做到这一点。
考虑以下简单的类,其中每个员工都有一个姓名和等级。
public class Employee
private String name;
private String grade;
public Employee(String name, String grade)
this.name = name;
this.grade = grade;
public String getName()
return name;
public void setName(String name)
this.name = name;
public String getGrade()
return grade;
public void setGrade(String grade)
this.grade = grade;
假设我们有一个通过某种方法返回的员工列表,我们想按员工的等级对员工进行排序。我们知道我们可以将anonymous class 用作:
List<Employee> employeeList = getDummyEmployees();
// Using anonymous class
employeeList.sort(new Comparator<Employee>()
@Override
public int compare(Employee e1, Employee e2)
return e1.getGrade().compareTo(e2.getGrade());
);
其中 getDummyEmployee() 是一些方法:
private static List<Employee> getDummyEmployees()
return Arrays.asList(new Employee("Carrie", "C"),
new Employee("Fanishwar", "F"),
new Employee("Brian", "B"),
new Employee("Donald", "D"),
new Employee("Adam", "A"),
new Employee("Evan", "E")
);
现在我们知道Comparator 是一个函数式接口。 Functional Interface 是只有一个抽象方法的那个(尽管它可能包含一个或多个默认或静态方法)。 Lambda 表达式提供了@FunctionalInterface
的实现,所以一个函数式接口只能有一个抽象方法。我们可以使用 lambda 表达式:
employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // lambda exp
看起来一切都很好,但是如果 Employee
类也提供类似的方法呢:
public class Employee
private String name;
private String grade;
// getter and setter
public static int compareByGrade(Employee e1, Employee e2)
return e1.grade.compareTo(e2.grade);
在这种情况下使用方法名本身会更清楚。因此我们可以直接使用方法引用来引用方法:
employeeList.sort(Employee::compareByGrade); // method reference
根据docs,有四种方法引用:
+----+-------------------------------------------------------+--------------------------------------+
| | Kind | Example |
+----+-------------------------------------------------------+--------------------------------------+
| 1 | Reference to a static method | ContainingClass::staticMethodName |
+----+-------------------------------------------------------+--------------------------------------+
| 2 |Reference to an instance method of a particular object | containingObject::instanceMethodName |
+----+-------------------------------------------------------+--------------------------------------+
| 3 | Reference to an instance method of an arbitrary object| ContainingType::methodName |
| | of a particular type | |
+----+-------------------------------------------------------+--------------------------------------+
| 4 |Reference to a constructor | ClassName::new |
+------------------------------------------------------------+--------------------------------------+
【讨论】:
我不明白 compareByGrade 需要两个参数,如何简单地称为 Employee::compareByGrade。它如何知道要比较哪两个员工?我猜,因为它是在 sort() 中调用的,编译器会自动迭代数组的所有成员?你将如何反其道而行之,指定两个要比较的特定对象? @NathanielHoyt 检查此***.com/questions/12386075/… @NathanielHoyt Javas 标准 List 接口有一个 sort 方法,它接受一个 Comparator 来对 List 进行排序。这就是此代码中使用的内容。这是在 Java 8 中添加的。【参考方案5】:::
是 Java 8 中包含的一个新运算符,用于引用现有类的方法。您可以引用类的静态方法和非静态方法。
对于引用静态方法,语法是:
ClassName :: methodName
对于引用非静态方法,语法为
objRef :: methodName
和
ClassName :: methodName
引用方法的唯一前提是方法存在于功能接口中,该接口必须与方法引用兼容。
方法引用在评估时会创建功能接口的实例。
发现于:http://www.speakingcs.com/2014/08/method-references-in-java-8.html
【讨论】:
【参考方案6】:这是 Java 8 中的方法参考。oracle 文档是here。
如文档中所述...
方法引用 Person::compareByAge 是对静态的引用 方法。
下面是一个引用一个实例方法的例子 特定对象:
class ComparisonProvider
public int compareByName(Person a, Person b)
return a.getName().compareTo(b.getName());
public int compareByAge(Person a, Person b)
return a.getBirthday().compareTo(b.getBirthday());
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
方法引用 myComparisonProvider::compareByName 调用方法 compareByName 这是对象 myComparisonProvider 的一部分。 JRE 推断 方法类型参数,在本例中为 (Person, Person)。
【讨论】:
但是方法 'compareByAge' 不是静态的。 @abbas compareByName 也不是。因此,您可以使用对象通过引用运算符访问这些非静态方法。如果它们是静态的,您可以使用类名,例如 ComparisionProvider::someStaticMethod【参考方案7】::: Operator 在 java 8 中被引入用于方法引用。方法引用是 lambda 表达式的简写语法,它只执行一个方法。下面是方法引用的一般语法:
Object :: methodName
我们知道我们可以使用lambda expressions 而不是使用匿名类。但有时,lambda 表达式实际上只是对某个方法的调用,例如:
Consumer<String> c = s -> System.out.println(s);
为了使代码更清晰,您可以将该 lambda 表达式转换为方法引用:
Consumer<String> c = System.out::println;
【讨论】:
伟大而简单的提示!转到消费者的accept电话:c.accept(s);
谢谢。简单的解释/示例,告诉我我想知道和需要知道什么。所以::
基本上是一个更短的 lambda 表达式。所以object::nonstaticmethod
是()-> object.nonstaticmethod()
或event-> object.nonstaticmethod()
。对于class.method()
,只需class::method
。【参考方案8】:
所以我在这里看到了很多坦率地说过于复杂的答案,这是轻描淡写的。
答案很简单::: 它被称为方法引用 https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
所以我不会复制粘贴,在链接上,如果你向下滚动到表格,你可以找到所有信息。
现在,让我们简要了解一下什么是方法引用:
A::b 有点替换了以下内联 lambda 表达式:(params ...) -> Ab(params ...)
要将此与您的问题相关联,有必要了解 java lambda 表达式。这并不难。
内联 lambda 表达式类似于定义的 函数式接口(这是一个没有更多也不少于 1 个方法的接口)。 让我们看一下我的意思:
InterfaceX f = (x) -> x*x;
InterfaceX 必须是函数式接口。任何功能接口,对于该编译器而言,InterfaceX 唯一重要的是您定义格式:
InterfaceX 可以是以下任何一种:
interface InterfaceX
public Integer callMe(Integer x);
或者这个
interface InterfaceX
public Double callMe(Integer x);
或更通用的:
interface InterfaceX<T,U>
public T callMe(U x);
让我们以第一个呈现的案例和我们之前定义的内联 lambda 表达式为例。
在 Java 8 之前,您可以这样定义它:
InterfaceX o = new InterfaceX()
public int callMe (int x)
return x*x;
;
从功能上讲,它是一回事。不同之处在于编译器如何理解这一点。
现在我们已经了解了内联 lambda 表达式,让我们回到方法引用 (::)。假设您有这样的课程:
class Q
public static int anyFunction(int x)
return x+5;
由于方法 anyFunctions 与 InterfaceX callMe 具有相同的类型,因此我们可以使用方法引用将这两者等同起来。
我们可以这样写:
InterfaceX o = Q::anyFunction;
这相当于:
InterfaceX o = (x) -> Q.anyFunction(x);
方法引用的一个很酷的事情和优点是,首先,在您将它们分配给变量之前,它们是无类型的。因此,您可以将它们作为参数传递给任何外观等效(具有相同定义类型)的功能接口。这正是您的情况
【讨论】:
【参考方案9】::: 被称为方法引用。假设我们要调用类 Purchase 的 calculatePrice 方法。那么我们可以这样写:
Purchase::calculatePrice
也可以看成是写 lambda 表达式的简写形式,因为方法引用被转换成 lambda 表达式。
【讨论】:
我可以做嵌套方法引用吗?例如groupingBy( Order::customer::name ) 您不能以这种方式进行嵌套方法引用【参考方案10】:我发现this source 很有趣。
事实上,正是Lambda变成了双冒号。 双冒号更具可读性。 我们遵循以下步骤:
第 1 步:
// We create a comparator of two persons
Comparator c = (Person p1, Person p2) -> p1.getAge().compareTo(p2.getAge());
第二步:
// We use the interference
Comparator c = (p1, p2) -> p1.getAge().compareTo(p2.getAge());
第三步:
// The magic using method reference
Comparator c = Comparator.comparing(Person::getAge);
【讨论】:
好像Person::getAge()
应该是Person::getAge
。【参考方案11】:
双冒号(::)操作符,在Java中也称为方法引用操作符,用于在类的帮助下直接引用方法来调用方法。它们的行为与 lambda 表达式完全相同。它与 lambda 表达式的唯一区别是它使用名称直接引用方法,而不是为方法提供委托。
语法:
<Class name>::<method name>
这个符号可以用来代替Lamda表达式
程序:
// Java code to print the elements of Stream
// without using double colon operator
import java.util.stream.*;
class MyClass
public static void main(String[] args)
// Get the stream
Stream<String> stream
= Stream.of("Testing","Program");
// Print the stream
stream.forEach(s -> System.out.println(s));
输出:
Testing
Program
stream.forEach(s -> System.out.println(s));
行可以替换为
stream.forEach(System.out::println);
第一种方法在编程时也经常使用。
【讨论】:
【参考方案12】:return reduce(Math::max);
不等于与return reduce(max());
但这意味着,像这样:
IntBinaryOperator myLambda = (a, b)->(a >= b) ? a : b;//56 keystrokes I had to type -_-
return reduce(myLambda);
如果你这样写,你可以节省 47 次击键
return reduce(Math::max);//Only 9 keystrokes ^_^
【讨论】:
【参考方案13】:由于这里的许多答案都很好地解释了::
的行为,另外我想澄清::
如果将操作员用于实例变量,则它不需要与引用的功能接口具有完全相同的签名。假设我们需要一个类型为 TestObject 的BinaryOperator。在传统方式中,它的实现方式如下:
BinaryOperator<TestObject> binary = new BinaryOperator<TestObject>()
@Override
public TestObject apply(TestObject t, TestObject u)
return t;
;
正如您在匿名实现中看到的,它需要两个 TestObject 参数并返回一个 TestObject 对象。要通过使用::
运算符来满足这个条件,我们可以从静态方法开始:
public class TestObject
public static final TestObject testStatic(TestObject t, TestObject t2)
return t;
然后调用:
BinaryOperator<TestObject> binary = TestObject::testStatic;
好的,它编译得很好。如果我们需要一个实例方法呢?让我们用实例方法更新 TestObject:
public class TestObject
public final TestObject testInstance(TestObject t, TestObject t2)
return t;
public static final TestObject testStatic(TestObject t, TestObject t2)
return t;
现在我们可以访问实例如下:
TestObject testObject = new TestObject();
BinaryOperator<TestObject> binary = testObject::testInstance;
这段代码编译得很好,但下面没有:
BinaryOperator<TestObject> binary = TestObject::testInstance;
我的eclipse告诉我“无法从类型TestObject ...中对非静态方法testInstance(TestObject, TestObject)进行静态引用”
它是一个实例方法,但如果我们重载testInstance
,如下所示:
public class TestObject
public final TestObject testInstance(TestObject t)
return t;
public final TestObject testInstance(TestObject t, TestObject t2)
return t;
public static final TestObject testStatic(TestObject t, TestObject t2)
return t;
然后调用:
BinaryOperator<TestObject> binary = TestObject::testInstance;
代码可以正常编译。因为它会用单参数而不是双参数调用testInstance
。好的,我们的两个参数发生了什么?让我们打印出来看看:
public class TestObject
public TestObject()
System.out.println(this.hashCode());
public final TestObject testInstance(TestObject t)
System.out.println("Test instance called. this.hashCode:"
+ this.hashCode());
System.out.println("Given parameter hashCode:" + t.hashCode());
return t;
public final TestObject testInstance(TestObject t, TestObject t2)
return t;
public static final TestObject testStatic(TestObject t, TestObject t2)
return t;
将输出:
1418481495
303563356
Test instance called. this.hashCode:1418481495
Given parameter hashCode:303563356
好的,JVM 足够聪明,可以调用 param1.testInstance(param2)。我们可以使用来自另一个资源而不是 TestObject 的testInstance
,即:
public class TestUtil
public final TestObject testInstance(TestObject t)
return t;
然后调用:
BinaryOperator<TestObject> binary = TestUtil::testInstance;
它不会编译并且编译器会告诉:“TestUtil 类型没有定义 testInstance(TestObject, TestObject)”。因此,如果它不是同一类型,编译器将查找静态引用。好的,多态性呢?如果我们移除 final 修饰符并添加我们的 SubTestObject 类:
public class SubTestObject extends TestObject
public final TestObject testInstance(TestObject t)
return t;
然后调用:
BinaryOperator<TestObject> binary = SubTestObject::testInstance;
它也不会编译,编译器仍然会寻找静态引用。但是下面的代码可以正常编译,因为它通过了 is-a 测试:
public class TestObject
public SubTestObject testInstance(Object t)
return (SubTestObject) t;
BinaryOperator<TestObject> binary = TestObject::testInstance;
*我只是在学习,所以我试着看看,如果我错了,请随时纠正我
【讨论】:
【参考方案14】:在 java-8 Streams Reducer 中,简单的工作是一个函数,它以两个值作为输入并在一些计算后返回结果。此结果将在下一次迭代中输入。
在 Math:max 函数的情况下,方法不断返回传递的两个值的最大值,最后你手头有最大的数字。
【讨论】:
【参考方案15】:在较旧的 Java 版本中,您可以使用:而不是“::”或 lambd:
public interface Action
void execute();
public class ActionImpl implements Action
@Override
public void execute()
System.out.println("execute with ActionImpl");
public static void main(String[] args)
Action action = new Action()
@Override
public void execute()
System.out.println("execute with anonymous class");
;
action.execute();
//or
Action actionImpl = new ActionImpl();
actionImpl.execute();
或者传递给方法:
public static void doSomething(Action action)
action.execute();
【讨论】:
【参考方案16】:在运行时,它们的行为完全相同。字节码可能相同/不同(对于上面的 Incase,它会生成相同的字节码(编译上面并检查 javaap -c;))
在运行时它们的行为完全相同。method(math::max);,它生成相同的数学(编译上面并检查 javap -c;))
【讨论】:
【参考方案17】:关于::
方法参考的作用,前面的答案非常完整。总而言之,它提供了一种在不执行的情况下引用方法(或构造函数)的方法,并且在评估时,它会创建一个提供目标类型上下文的功能接口的实例。
下面是两个示例,用于在 ArrayList
WITH 和不使用 ::
方法引用中查找具有最大值的对象。解释在下面的 cmets 中。
不使用::
import java.util.*;
class MyClass
private int val;
MyClass (int v) val = v;
int getVal() return val;
class ByVal implements Comparator<MyClass>
// no need to create this class when using method reference
public int compare(MyClass source, MyClass ref)
return source.getVal() - ref.getVal();
public class FindMaxInCol
public static void main(String args[])
ArrayList<MyClass> myClassList = new ArrayList<MyClass>();
myClassList.add(new MyClass(1));
myClassList.add(new MyClass(0));
myClassList.add(new MyClass(3));
myClassList.add(new MyClass(6));
MyClass maxValObj = Collections.max(myClassList, new ByVal());
使用::
import java.util.*;
class MyClass
private int val;
MyClass (int v) val = v;
int getVal() return val;
public class FindMaxInCol
static int compareMyClass(MyClass source, MyClass ref)
// This static method is compatible with the compare() method defined by Comparator.
// So there's no need to explicitly implement and create an instance of Comparator like the first example.
return source.getVal() - ref.getVal();
public static void main(String args[])
ArrayList<MyClass> myClassList = new ArrayList<MyClass>();
myClassList.add(new MyClass(1));
myClassList.add(new MyClass(0));
myClassList.add(new MyClass(3));
myClassList.add(new MyClass(6));
MyClass maxValObj = Collections.max(myClassList, FindMaxInCol::compareMyClass);
【讨论】:
【参考方案18】:双冒号即::
运算符在Java 8 中作为方法参考 引入。方法引用是 lambda 表达式 的一种形式,用于通过名称引用现有方法。
类名::方法名
例如:-
stream.forEach(element -> System.out.println(element))
通过使用双冒号::
stream.forEach(System.out::println(element))
【讨论】:
以上是关于Java 8 中的 ::(双冒号)运算符的主要内容,如果未能解决你的问题,请参考以下文章
Java 8 Lambda表达式之方法引用 ::双冒号操作符