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

Nar*_*hai 912 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;
}
Run Code Online (Sandbox Code Playgroud)

Math::max什么样的方法指针?普通static方法如何转换为IntBinaryOperator

isn*_*bad 975

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

reduce(new IntBinaryOperator() {
    int applyAsInt(int left, int right) {
        return Math.max(left, right);
    }
});
Run Code Online (Sandbox Code Playgroud)

这需要很多语法才能调用Math.max.这就是lambda表达式发挥作用的地方.从Java 8开始,它允许以更短的方式执行相同的操作:

reduce((int left, int right) -> Math.max(left, right));
Run Code Online (Sandbox Code Playgroud)

这是如何运作的?java编译器"检测",你想要实现一个接受两个ints并返回一个的方法int.这相当于接口的唯一方法IntBinaryOperator(reduce您要调用的方法的参数)的形式参数.所以编译器会为您完成剩下的工作 - 它只是假设您要实现IntBinaryOperator.

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

reduce(Math::max);
Run Code Online (Sandbox Code Playgroud)

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

(另见Lambda表达式的翻译)


Jat*_*tin 471

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

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

class Hey {
     public static double square(double num){
        return Math.pow(num, 2);
    }
}

Function<Double, Double> square = Hey::square;
double ans = square.apply(23d);
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

Function上面是一个功能界面.要完全理解::,理解功能接口也很重要.显然,功能接口是只有一个抽象方法的接口.

功能接口的例子包括Runnable,Callable,和ActionListener.

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


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

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

例如,而不是写lambda体

Function<Double, Double> square = (Double x) -> x * x;
Run Code Online (Sandbox Code Playgroud)

你可以干脆做

Function<Double, Double> square = Hey::square;
Run Code Online (Sandbox Code Playgroud)

在运行时,这两种square方法的行为完全相同.字节码可能相同也可能不相同(但是,对于上面的情况,生成相同的字节码;编译上面的内容并检查javap -c).

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

以下是非法的:

Supplier<Boolean> p = Hey::square; // illegal
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

上面,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");
Run Code Online (Sandbox Code Playgroud)

如果是参数化类型:

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;
Run Code Online (Sandbox Code Playgroud)

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

  • 谢谢你的解释.总结:'::'用于提取满足FunctionalInterface(lambda)的方法:ClassX :: staticMethodX,或instanceX :: instanceMethodX" (5认同)
  • @Jatin,您好,这不是违反 OOP 范式吗,因为方法不是 [First-class Citizen](https://en.wikipedia.org/wiki/First-class_citizen)? (2认同)

Oli*_*POP 53

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

可以在此处找到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);
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 对于熟悉C#的人来说,它类似于DelegateType d = new DelegateType(MethodName); (2认同)

sre*_*ath 26

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

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

ClassName :: methodName 
Run Code Online (Sandbox Code Playgroud)

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

objRef :: methodName
Run Code Online (Sandbox Code Playgroud)

ClassName :: methodName
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 该链接(实际上)已损坏:*“此域名待售:$2,695”* (2认同)

i_a*_*ero 26

它似乎有点晚了,但这是我的两分钱.甲lambda表达式用于创建匿名方法.除了调用现有方法之外什么都不做,但直接通过名称引用该方法更为明确.和方法的参考,使我们在使用方法参照运营商来做::.

考虑以下简单类,其中每个员工都有一个名称和等级.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

假设我们有一个通过某种方法返回的员工列表,我们希望按其等级对员工进行排序.我们知道我们可以使用匿名类:

    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());
           }
    });
Run Code Online (Sandbox Code Playgroud)

其中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")
                );
    }
Run Code Online (Sandbox Code Playgroud)

现在我们知道Comparator是一个功能接口.甲功能接口是具有正好一个抽象方法(尽管它可含有一个或多个默认的或静态的方法).Lambda表达式提供了实现,@FunctionalInterface因此功能接口只能有一个抽象方法.我们可以使用lambda表达式:

employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // lambda exp
Run Code Online (Sandbox Code Playgroud)

看起来一切都很好但是如果课程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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,使用方法名称本身将更加清晰.因此我们可以通过使用方法引用直接引用方法:

employeeList.sort(Employee::compareByGrade); // method reference
Run Code Online (Sandbox Code Playgroud)

根据文档,有四种方法参考:

+----+-------------------------------------------------------+--------------------------------------+
|    | 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                       |
+------------------------------------------------------------+--------------------------------------+
Run Code Online (Sandbox Code Playgroud)


dav*_*rld 21

这是Java 8中的方法参考.oracle文档在这里.

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

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

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

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); 
Run Code Online (Sandbox Code Playgroud)

方法引用myComparisonProvider :: compareByName调用方法compareByName,它是对象myComparisonProvider的一部分.JRE推断出方法类型参数,在本例中是(Person,Person).

  • @abbas Nor是compareByName.因此,您可以使用对象通过引用运算符访问这些非静态方法.如果它们是静态的,您可以使用类名称,如ComparisionProvider :: someStaticMethod (3认同)
  • 但是'compareByAge'方法不是静态的. (2认同)

Ner*_*ian 8

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

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


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

A::b 在某种程度上替代了以下内联 lambda 表达式(parameters ...) -> Ab(parameter ...)

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

内联 lambda 表达式类似于已定义的函数式接口(这是一种最多且不少于一个方法的接口)。

让我们简短地看一下我的意思:

InterfaceX f = (x) -> x*x;
Run Code Online (Sandbox Code Playgroud)

InterfaceX 必须是函数式接口。对于任何函数式接口,InterfaceX 对于该编译器唯一重要的是您定义格式:

InterfaceX 可以是以下任意一个:

interface InterfaceX
{
    public Integer callMe(Integer x);
}
Run Code Online (Sandbox Code Playgroud)

或这个:

interface InterfaceX
{
    public Double callMe(Integer x);
}
Run Code Online (Sandbox Code Playgroud)

或者更通用:

interface InterfaceX<T, U>
{
    public T callMe(U x);
}
Run Code Online (Sandbox Code Playgroud)

让我们采用第一个案例和我们之前定义的内联 lambda 表达式。

在 Java 8 之前,您可以类似地这样定义它:

 InterfaceX o = new InterfaceX(){
                        public int callMe(int x)
                        {
                            return x*x;
                        }
                    };
Run Code Online (Sandbox Code Playgroud)

从功能上来说,这是一样的。区别更多在于编译器如何看待这一点。

现在我们已经了解了内联 lambda 表达式,让我们返回到方法引用 (::)。假设你有一堂这样的课:

class Q {
    public static int anyFunction(int x)
    {
        return x + 5;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我们可以这样写:

InterfaceX o =  Q::anyFunction;
Run Code Online (Sandbox Code Playgroud)

这相当于:

InterfaceX o = (x) -> Q.anyFunction(x);
Run Code Online (Sandbox Code Playgroud)

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


小智 6

::运算符是Java 8中引入的方法参考。方法参考是仅执行一个方法的lambda表达式的简写语法。这是方法参考的一般语法:

Object :: methodName
Run Code Online (Sandbox Code Playgroud)

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

Consumer<String> c = s -> System.out.println(s);
Run Code Online (Sandbox Code Playgroud)

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

Consumer<String> c = System.out::println;
Run Code Online (Sandbox Code Playgroud)