Java 8中的::(双冒号)运算符

时间:2013-11-15 12:46:33

标签: java java-8

我正在探索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

18 个答案:

答案 0 :(得分:939)

通常情况下,可以使用reduce调用Math.max(int, int)方法,如下所示:

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中引入了::语法来引用方法:< / p>

reduce(Math::max);

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

(另见Translation of Lambda Expressions

答案 1 :(得分:447)

::称为方法参考。它基本上是对单个方法的引用。即它是指按名称的现有方法。

简短说明
下面是静态方法的引用示例:

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是一个只有一种抽象方法的接口。

功能接口的示例包括RunnableCallableActionListener

上面的

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


::很棒的原因是that

  

方法引用是与lambda表达式(...)具有相同处理的表达式,但它们不是提供方法体,而是通过名称引用现有方法。

E.g。而不是写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需要一个参数并返回doubleSupplier中的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;

方法引用可以有不同的样式,但从根本上说它们都意味着相同的东西,可以简单地将其视为lambdas:

  1. 静态方法(ClassName::methName
  2. 特定对象(instanceRef::methName
  3. 的实例方法
  4. 特定对象的超级方法(super::methName
  5. 特定类型(ClassName::methName
  6. 的任意对象的实例方法
  7. 类构造函数引用(ClassName::new
  8. 数组构造函数引用(TypeName[]::new
  9. 有关详细信息,请参阅http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html

答案 2 :(得分:51)

是的,这是事实。 ::运算符用于方法引用。因此,可以通过使用它或来自对象的方法从类中提取静态方法。即使对于构造函数,也可以使用相同的运算符。这里提到的所有案例都在下面的代码示例中举例说明。

可以找到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);
   }
}

答案 3 :(得分:24)

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

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

ClassName :: methodName 

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

objRef :: methodName

ClassName :: methodName

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

方法引用在评估时,创建功能接口的实例。

发现于:http://www.speakingcs.com/2014/08/method-references-in-java-8.html

答案 4 :(得分:20)

看起来有点晚了,但这是我的两分钱。 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("Farhan", "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                       |
+------------------------------------------------------------+--------------------------------------+

答案 5 :(得分:19)

这是Java 8中的方法引用.orax文档是here

如文件中所述......

  

方法引用Person :: compareByAge是对静态的引用   方法

     

以下是对a的实例方法的引用示例   特别的对象:

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

答案 6 :(得分:4)

:: Operator 是在java 8中引入的方法引用。方法引用是l​​ambda表达式的简写语法,只执行一个方法。这是方法参考的一般语法:

Object :: methodName

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

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

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

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

答案 7 :(得分:3)

::被称为方法引用。假设我们想要调用类Purchase的calculatePrice方法。然后我们可以把它写成:

Purchase::calculatePrice

它也可以看作是编写lambda表达式的简短形式因为方法引用被转换为lambda表达式。

答案 8 :(得分:2)

由于此处的许多答案都解释了良好的::行为,另外我想澄清:: 运算符不需要与引用功能接口具有完全相同的签名(如果用于实例)变量即可。让我们假设我们需要BinaryOperator,其类型为 TestObject 。以传统的方式,它实现如下:

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)。我们可以使用来自其他资源的testInstance但不能使用TestObject,即:

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;

它也不会编译,编译器仍然会寻找静态引用。但是下面的代码将编译正常,因为它传递的是一个测试:

public class TestObject {

    public SubTestObject testInstance(Object t){
        return (SubTestObject) t;
    }

}

BinaryOperator<TestObject> binary = TestObject::testInstance;
  

*我正在学习,所以我通过尝试看到了,如果我错了,请随时纠正我

答案 9 :(得分:2)

return reduce(Math::max); NOT EQUAL 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 ^_^

答案 10 :(得分:2)

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

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

答案 11 :(得分:2)

在java-8 Streams中,简单工作中的Reducer是一个函数,它将两个值作为输入并在一些计算后返回结果。这个结果将在下一次迭代中提供。

在Math:max函数的情况下,方法保持返回最多两个传递的值,最后你有最大的数字。

答案 12 :(得分:1)

我发现this source非常有趣。

事实上, Lambda 会变成 Double Colon 。 Double Colon更具可读性。 我们遵循以下步骤:

<强> STEP1:

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

<强> STEP2:

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

<强> STEP3:

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

答案 13 :(得分:1)

在较旧的Java版本中,您可以使用:

而不是“::”或lambd
FOSRestBundle

或传递给方法:

fos_rest.decoder.jsontoform

答案 14 :(得分:1)

所以我在这里看到了很多答案,这些答案坦率地说太过复杂了,这是一种轻描淡写的说法。

答案很简单: ::它称为方法引用 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, int y) 
                       {
                        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);

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

答案 15 :(得分:1)

关于::方法引用的作用,以前的答案很完整。综上所述,它提供了一种无需执行即可引用方法(或构造函数)的方法,并且在对其进行评估时,它会创建提供目标类型上下文的功能接口的实例。

下面是两个示例,这些示例使用ArrayList方法引用在:: WITH和WITHOUT中查找具有最大值的对象。解释在下面的注释中。


不使用::

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

答案 16 :(得分:0)

在Java 8中引入了双冒号即::运算符作为方法参考。方法引用是l​​ambda表达式的一种形式,用于通过名称来引用现有方法。

类名::方法名

例如:-

  • stream.forEach(element-> System.out.println(element))

通过使用Double Colon ::

  • stream.forEach(System.out :: println(element))

答案 17 :(得分:0)

双冒号 (::) 运算符,在 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);

第一种方法在编程时也经常使用。