首页 文章

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

提问于
浏览
805

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

16 回答

  • 2

    我发现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
    Comparator c = Comparator.comparing(Person::getAge());
    
  • 1

    它似乎有点晚了,但这是我的两分钱 . 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表达式:

    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                       |
    +------------------------------------------------------------+--------------------------------------+
    
  • 4

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

    可以在here找到Oracle的官方文档 .

    您可以在this文章中更好地概述JDK 8的更改 . 在 Method/Constructor referencing 部分还提供了一个代码示例:

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

    由于这里的许多答案都很好地解释了 :: 行为,另外我想澄清一下 :: operator doesnt need to have exactly same signature as the referring Functional Interface if it is used for instance variables . 让我们假设我们需要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;
    

    我的日食告诉我 "Cannot make a static reference to the non-static method testInstance(TestObject, TestObject) from the type 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;
    

    它只是不编译,编译器会告诉: "The type TestUtil does not define 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;
    

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

  • 19

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

    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编译器"detects",您要实现一个接受两个 int 并返回一个 int 的方法 . 这相当于接口 IntBinaryOperator (您要调用的方法 reduce 的参数)的唯一方法的形式参数 . 因此,编译器会为您完成剩下的工作 - 它只是假设您要实现 IntBinaryOperator .

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

    reduce(Math::max);
    

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

    (另见Translation of Lambda Expressions

  • 2

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

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

    方法引用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);
    

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

  • 3

    所以 I see here tons of answers that are frankly overcomplicated, and that's an understatement.

    答案很简单: :: it's called a Method References https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

    所以我不会复制粘贴,在链接上,如果向下滚动到表格,你可以找到所有信息 .


    现在,让我们来看看什么是方法参考:

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

    要将此与您的问题相关联,有必要了解java lambda表达式 . 哪个不难 .

    内联lambda表达式类似于定义的 functional interface (which is an interface that has no more and no less than 1 method) . 我们来看看我的意思:

    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表达式,让我们返回Method References(::) . 假设你有一个这样的类:

    class Q {
            public static int anyFunction(int x)
                 {
                     return x+5;
                 } 
            }
    

    由于方法anyFunctions具有与InterfaceX callMe相同的类型,因此我们可以将这两个类型与Method Reference相提并论 .

    我们可以这样写:

    InterfaceX o =  Q::anyFunction;
    

    这相当于:

    InterfaceX o = (x) -> Q.anyFunction(x);
    

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

  • 1

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

    Purchase::calculatePrice
    

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

  • 2

    :: Operator 是在java 8中引入的方法引用 . 方法引用是仅执行一个方法的lambda表达式的简写语法 . 这是方法引用的一般语法:

    Object :: methodName
    

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

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

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

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

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

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

  • 0

    在较旧的Java版本中,您可以使用以下命令代替“::”或lambd:

    public interface Action {
        void execute();
    }
    
    public class ActionImpl implements Action {
    
        @Override
        public void execute() {
            System.out.println("execute with ActionImpl");
        }
    
    }
    
    public static void main(String[] args) {
        Action action = new Action() {
            @Override
            public void execute() {
                System.out.println("execute with anonymous class");
            }
        };
        action.execute();
    
        //or
    
        Action actionImpl = new ActionImpl();
        actionImpl.execute();
    }
    

    或者传递给方法:

    public static void doSomething(Action action) {
        action.execute();
    }
    
  • 15

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

    Short Explanation
    下面是静态方法的引用示例:

    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 . 要完全理解 :: ,了解功能接口也很重要 . 显然,functional interface是一个只有一个抽象方法的接口 .

    功能接口的示例包括 RunnableCallableActionListener .

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


    :: s真棒的原因是that

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

    例如 . 而不是写lambda身体

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

    你可以干脆做

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

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

    唯一满足的主要标准是: the method you provide should have a similar signature to the method of the functional interface you use as object reference .

    以下是非法的:

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

    square 需要一个参数并返回 double . Supplier中的 get 方法需要一个参数但不返回任何内容 . 因此,这导致错误 .

    A method reference refers to the method of a functional interface. (如上所述,功能接口每个只能有一个方法) .

    更多示例: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 . 因此,可以使用满足以下条件的任何功能接口: take no argument and return double .

    另一个例子:

    Set<String> set = new HashSet<>();
    set.addAll(Arrays.asList("leo","bale","hanks"));
    Predicate<String> pred = set::contains;
    boolean exists = pred.test("leo");
    

    In case of parameterized types

    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:

    • 静态方法( ClassName::methName

    • 特定对象的实例方法( instanceRef::methName

    • 特定对象的超级方法( super::methName

    • 特定类型的任意对象的实例方法( ClassName::methName

    • 类构造函数引用( ClassName::new

    • 数组构造函数引用( TypeName[]::new

    有关进一步参考,请参阅http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html .

  • 1

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

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

  • 412

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

    关于 :: 方法参考的内容,之前的答案非常完整 . 总而言之,它提供了一种在不执行它的情况下引用方法(或构造函数)的方法,并且在评估时,它创建了提供目标类型上下文的功能接口的实例 .

    下面是两个示例,用于在 ArrayList WITH中查找具有最大值的对象,并且不使用 :: 方法引用 . 解释见下面的评论 .


    没有使用 ::

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

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

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

    ClassName :: methodName
    

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

    objRef :: methodName
    

    ClassName :: methodName
    

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

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

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

相关问题