::(双冒号)操作符在Java 8

我在探索Java 8源代码时,发现这部分代码非常令人惊讶:

// Defined in IntPipeline.java@Overridepublic final OptionalInt reduce(IntBinaryOperator op) {return evaluate(ReduceOps.makeInt(op));}
@Overridepublic final OptionalInt max() {return reduce(Math::max); // This is the gotcha line}
// Defined in Math.javapublic static int max(int a, int b) {return (a >= b) ? a : b;}

Math::max是一个方法指针吗?普通的static方法如何转换为IntBinaryOperator方法?

456334 次浏览

是的,那是真的。::操作符用于方法引用。因此,可以使用它从类中提取静态方法,也可以从对象中提取方法。相同的操作符甚至可以用于构造函数。下面的代码示例演示了这里提到的所有情况。

来自Oracle的官方文档可以在在这里中找到。

您可以在文章中更好地了解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 referenceConstructorReference reference = ConstructorClass::new;ConstructorClass cc = reference.constructor();
// static method referenceMethodReference mr = cc::method;
// object method referenceMethodReference mr2 = cc::nextMethod;
System.out.println(cc.value);}}

这是Java 8中的一个方法引用。Oracle文档是在这里

如文件所述……

方法引用Person::compareByAge是对静态对象的引用方法。< / p >

下面是a的实例方法的引用示例特定对象:< / p >

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).

通常,使用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并返回一个int的方法。这等价于接口IntBinaryOperator的唯一方法的形式参数(您要调用的方法reduce的参数)。所以编译器会帮你完成剩下的工作——它只是假设你想实现IntBinaryOperator

但是由于Math.max(int, int)本身满足IntBinaryOperator的形式要求,所以可以直接使用它。因为Java 7没有任何允许方法本身作为参数传递的语法(你只能传递方法结果,但不能传递方法引用),Java 8引入了::语法来引用方法:

reduce(Math::max);

注意,这将由编译器解释,而不是由JVM在运行时解释!虽然它为所有三个代码段生成了不同的字节码,但它们在语义上是相等的,因此后两个可以被认为是上面IntBinaryOperator实现的简短(而且可能更有效)版本!

(参见Lambda表达式的翻译)

::被称为方法引用。它基本上是对单个方法的引用。也就是说,它通过名称引用一个现有的方法。

# 0:

下面是一个引用静态方法的例子:

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可以像对象引用一样传递,并在需要时触发。事实上,它可以很容易地用作“normal”的指代。对象的方法为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功能界面。要完全理解第1条,理解功能接口也很重要。显然,功能界面是一个只有一个抽象方法的接口。

功能接口的示例包括RunnableCallableActionListener

上面的Function是一个功能接口,只有一个方法:apply。它需要一个参数并产生一个结果。


0之所以很棒,原因是:

方法引用是与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供应商中的get方法返回一个值,但它不带参数。因此,这会导致一个错误。

方法引用是指功能接口的方法。(如前所述,函数接口只能有一个方法。)

再举一些例子:消费者中的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");

# 0:

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;

方法引用可以有不同的风格,但基本上它们都意味着相同的事情,可以简单地可视化为lambdas:

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

如需进一步参考,请参见 Lambda的状态

::是Java 8中包含的一个新操作符,用于引用现有类的方法。可以引用类的静态方法和非静态方法。

对于引用静态方法,语法为:

ClassName :: methodName

对于引用非静态方法,语法为

objRef :: methodName

ClassName :: methodName

引用方法的唯一前提是该方法存在于功能接口中,该接口必须与方法引用兼容。

计算方法引用时,将创建功能接口的实例。

这是在http://www.speakingcs.com/2014/08/method-references-in-java-8.html上找到的

在运行时,它们的行为完全相同。字节码可能不相同(对于上面的情况,它生成相同的字节码(编译上面的代码并检查javaap -c;))。

在运行时,它们的行为完全相同。方法(math::max)生成相同的数学结果(编译上面的代码并检查javap -c;))。

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 ^_^

::被称为方法引用。假设我们想调用类Purchase的calculatePrice方法。那么我们可以写成:

Purchase::calculatePrice

它也可以被视为lambda表达式的一种简写形式,因为方法引用被转换为lambda表达式。

由于这里的许多答案都很好地解释了::行为,另外,我想澄清的是,如果用于实例变量,::操作符不需要与引用的功能界面操作符具有完全相同的签名。让我们假设我们需要一个类型为TestObjectBinaryOperator。传统的实现方式是这样的:

BinaryOperator<TestObject> binary = new BinaryOperator<TestObject>() {
@Overridepublic 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;

我的0号告诉我1号

很好。它是一个实例方法,但如果我们重载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;}}

它将输出:

 1418481495303563356Test instance called. this.hashCode:1418481495Given 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);

因此,如果静态引用不是同一类型,编译器将查找它。那么多态性呢?如果我们删除最后修饰符并添加SubTestObject类:

public class SubTestObject extends TestObject {
public final TestObject testInstance(TestObject t) {return t;}
}

和电话:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

它也不会编译。编译器仍然会查找静态引用。但是下面的代码可以很好地编译,因为它通过了是一个测试:

public class TestObject {
public SubTestObject testInstance(Object t) {return (SubTestObject) t;}
}
BinaryOperator<TestObject> binary = TestObject::testInstance;

::操作符是在Java 8中引入的,用于方法引用。方法引用是只执行一个方法的lambda表达式的简写语法。下面是方法引用的一般语法:

Object :: methodName

我们知道可以使用lambda表达式来代替匿名类。但有时,lambda表达式实际上只是对某些方法的调用,例如:

Consumer<String> c = s -> System.out.println(s);

为了使代码更清晰,你可以将lambda表达式转换为方法引用:

Consumer<String> c = System.out::println;

a# 1用于创建匿名方法。它什么也不做,只是调用一个现有的方法,但是直接引用方法的名称会更清楚。而方法引用使我们能够使用方法引用操作符::来做到这一点。

考虑下面的简单类,其中每个员工都有姓名和级别。

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

假设我们有一个由某个方法返回的员工列表,并且我们希望根据员工的等级对其进行排序。我们知道我们可以使用匿名类作为:

    List<Employee> employeeList = getDummyEmployees();
// Using anonymous classemployeeList.sort(new Comparator<Employee>() {@Overridepublic 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"));}

现在我们知道1是2。功能界面是只包含一个抽象方法的方法(尽管它可能包含一个或多个默认方法或静态方法)。Lambda表达式提供了@FunctionalInterface的实现,因此一个函数接口只能有一个抽象方法。我们可以这样使用lambda表达式:

employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // Lambda expression

看起来一切都很好,但是如果类Employee也提供了类似的方法呢?

public class Employee {private String name;private String grade;// getter and setterpublic static int compareByGrade(Employee e1, Employee e2) {return e1.grade.compareTo(e2.grade);}}
在这种情况下,使用方法名本身会更清楚。因此,我们可以通过使用方法引用直接引用该方法:employeeList.sort(员工::compareByGrade);//方法引用

.使用实例

根据的文档,有四种方法引用:

+----+-------------------------------------------------------+--------------------------------------+|    | 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                       |+------------------------------------------------------------+--------------------------------------+

我发现这个源非常有趣。

事实上,是λ变成了双冒号。双冒号可读性更好。

我们遵循以下步骤:

步骤1

// We create a comparator of two personsComparator c = (Person p1, Person p2) -> p1.getAge().compareTo(p2.getAge());

步骤2

// We use the interferenceComparator c = (p1, p2) -> p1.getAge().compareTo(p2.getAge());

步骤3

// The magic using method referenceComparator c = Comparator.comparing(Person::getAge);

在Java 8中,简单的Streams Reducer是一个函数,它接受两个值作为输入,经过一些计算后返回结果。这个结果被输入到下一个迭代中。

对于数学:马克斯函数,该方法将不断返回传递的两个值中的最大值,最终您将得到最大的数字。

在旧的Java版本中,你可以使用:而不是"::"或lambd:

public interface Action {void execute();}
public class ActionImpl implements Action {
@Overridepublic void execute() {System.out.println("execute with ActionImpl");}
}
public static void main(String[] args) {Action action = new Action() {@Overridepublic 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();}

所以我在这里看到了很多答案,坦率地说,是0,这是一种保守的说法。

答案很简单:**::被称为方法引用。在方法参考中,如果向下滚动到表格,您可以找到所有信息。


现在,让我们来简单了解一下什么是方法引用:

b:: 有些取代了下面的内联lambda表达式:(参数…)->A.b(参数…)

要将此与您的问题联系起来,有必要理解Java lambda表达式。这并不难。

内联lambda表达式类似于定义函数接口(它是一个不多于也不少于一个方法的接口)。

让我们来看看我的意思:

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表达式,让我们返回到方法reference(::)。假设你有一个这样的类:

class Q {public static int anyFunction(int x){return x + 5;}}

由于方法anyFunctions与InterfaceX callMe具有相同的类型,我们可以用一个方法引用来等效这两个方法。

我们可以这样写:

InterfaceX o =  Q::anyFunction;

这就相当于:

InterfaceX o = (x) -> Q.anyFunction(x);

方法引用的一个很酷的优点是,在将它们分配给变量之前,它们都是无类型的。因此,您可以将它们作为参数传递给任何具有相同外观(具有相同定义类型)的函数接口。这正是你的情况。

关于::方法引用的作用,前面的回答非常完整。总而言之,它提供了一种在不执行方法(或构造函数)的情况下引用方法(或构造函数)的方法,并且在计算时,它创建了提供目标类型上下文的函数接口实例。

下面是使用::方法引用查找# 0,# 2和没有中值最大的对象的两个示例。解释见下面的评论。


没有使用::

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

双冒号,即::操作符,在Java 8中作为方法引用引入。方法引用是lambda表达式的一种形式,用于引用现有方法的名称。

类名::methodName

例子:

  • # 0

使用双冒号 ::

  • # 0