当前位置: 首页 > 知识库问答 >
问题:

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

司徒骞尧
2023-03-14

我在探索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是否类似于方法指针?普通的静态方法如何转换为IntBinaryOperator

共有2个答案

诸新霁
2023-03-14

::称为方法引用。它基本上是对单个方法的引用。即。它通过名称引用一个现有的方法。

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

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;

在运行时,这两个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;

方法引用可以有不同的样式,但从根本上说,它们都意味着相同的东西,并且可以简单地可视化为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。

贾兴学
2023-03-14

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

reduce(new IntBinaryOperator() {
    int applyAsInt(int left, int right) {
        return Math.max(left, right);
    }
});

这需要大量语法,只需调用math.max。这就是lambda表达式发挥作用的地方。自Java八号以来,允许以短得多的方式做同样的事情:

reduce((int left, int right) -> Math.max(left, right));

这是怎么工作的?Java编译器“检测”到您希望实现一个接受两个int并返回一个int的方法。这等同于接口IntBinaryOperator的one and only方法(您要调用的方法Reduce的参数)的形参。因此,编译器为您完成其余的工作--它只是假设您希望实现IntBinaryOperator

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

reduce(Math::max);

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

(另见Lambda表达式的翻译)

 类似资料:
  • 问题内容: 我正在探索Java 8源代码,发现代码的这一特殊部分非常令人惊讶: 是类似方法指针的东西吗?普通方法如何转换为? 问题答案: 通常,可以使用以下方法调用该方法: 仅调用就需要很多语法。那就是lambda表达式起作用的地方。从Java 8开始,它允许以更短的方式执行相同的操作: 这是如何运作的?Java编译器“检测”你要实现一个接受两个ints并返回一个的方法int。这等效于接口的唯一方

  • 刚刚在java中尝试了一些东西,发现了以下问题。 以下是我的代码。 父接口: 子接口: 实施1: 实施2: 主要方法: 我不确定我做错了什么,我在本地机器中安装了JDK 13并使用IntelliJ 2019.3和JDK 11。我检查了IntelliJ是否支持JDK 13 谢谢 错误更新我在那里留下了一个分号,删除了它,请再次检查。

  • 问题内容: 如您所知,有一个功能快捷方式的建议,因此您可以编写: 它将在es5中像这样工作: 我的问题是:是否可以通过这种方式传递参数? 我的意思是用上述快捷方式编写此代码的方法: 这是React中很常见的模式,因此最好将其缩短一点。 问题答案: 否。bind运算符(规范建议)有两种形式: 方法提取 “虚拟方法”调用 obj::function(…) ≡ function.call(obj, …)

  • 问题内容: 显然,Java中冒号以多种方式使用。有人介意解释它的作用吗? 例如这里: 您将如何以不同的方式编写此循环,以便不合并? 问题答案: 在Java代码中冒号有几个地方: 1)跳出标签): 2)三元条件: 3)每个循环: 4)断言: 5)切换语句中的情况: 6)方法参考

  • 当使用双冒号操作符引用重载的方法时,Java似乎不能确定要使用的正确方法。考虑这个例子: 对 的第一次调用不会编译,并给出以下错误: 然而,第二个调用编译得很好,这意味着问题在于的重载。只有一个<code>setter</code>重载是适用的,所以我不明白为什么这不起作用。 可以通过使用指定参数类型的lambda来解决这个问题,但这要详细得多。 有更好的方法来处理这种情况吗?还是我一直在解决这个

  • 问题内容: 关于使用问号“?”的两个问题 在打印功能的括号内加上冒号“:”运算符:它们是做什么的?另外,有人知道它们的标准术语吗,或者在哪里可以找到有关它们使用的更多信息?我读过它们类似于’if’‘else’语句。 问题答案: 这是三元条件运算符,可以在任何地方使用,而不仅仅是print语句。它有时被称为“三元运算符”,但它不是唯一的三元运算符,而是最常见的一个。 这是Wikipedia的一个很好