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


通常,可以使用Math.max(int, int)调用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中引入了::语法来引用方法:

reduce(Math::max);

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

(另请参阅Lambda表达式的译文)


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

简短说明 :下面是一个对静态方法的引用的例子:

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上面是一个功能性的接口 。 那么要完全解释:: ,了解功能接口是很重要的。 显然,函数接口只是一个抽象方法的接口。

例如: RunnableCallableActionListener等等。

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


::真棒的原因是因为:

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

即就像写lambda身体一样:

Function<Double, Double> square = (Double x) -> x * x;

你可以简单地做:

Function<Double, Double> square = Hey::square;

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

满足的唯一主要标准是: 您提供的方法应该与您用作对象引用的函数接口的方法具有类似的签名

以下是非法的:

Supplier<Boolean> p = Hey::square; //illegal

square期待一个参数并返回一个双精度。 供应商中的get方法需要一个参数,但不返回任何内容。 所以这是一个错误。

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

一些更多的例子:Consumer中的accept方法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


    是的,那是真的。 ::运算符用于方法引用。 所以,可以通过使用它或从对象中提取方法从类中提取静态方法。 即使是构造函数也可以使用相同的运算符。 下面的代码示例举例说明了这里提到的所有情况。

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

    您可以更好地了解本文中的JDK 8更改。 在Method / Constructor引用部分中还提供了一个代码示例:

    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);
       }
    }
    
    链接地址: http://www.djcxy.com/p/36369.html

    上一篇: :: (double colon) operator in Java 8

    下一篇: java immutable class much slower