协慌网

登录 贡献 社区

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

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 。它需要一个参数并产生一个结果。


为什么原因:: s 为真棒是

方法引用是与 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需要一个参数并返回一个doubleSupplier 中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;

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

  1. 静态方法( ClassName::methName
  2. 特定对象的实例方法( instanceRef::methName
  3. 特定对象的超级方法( super::methName
  4. 特定类型的任意对象的实例方法( ClassName::methName
  5. 类构造函数引用( ClassName::new
  6. 数组构造函数引用( 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);
   }
}