:: toán tử (dấu hai chấm) trong Java 8


956

Tôi đã khám phá nguồn Java 8 và thấy phần mã đặc biệt này rất đáng ngạc nhiên:

//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::maxmột cái gì đó giống như một con trỏ phương thức? Làm thế nào để một staticphương thức bình thường được chuyển đổi thành IntBinaryOperator?


60
Đó là đường cú pháp để có các trình triển khai giao diện tự động tạo trình biên dịch dựa trên chức năng bạn cung cấp (để làm cho toàn bộ lambda dễ sử dụng hơn với các cơ sở mã hiện có).
NEET

4
java.dzone.com/articles/java-lambda-expressions-vs có thể giúp đỡ, đã không nhìn sâu vào chủ đề
Pontus Backlund

8
@Neet không chính xác là "cú pháp đường", trừ khi bạn có thể nói gì. tức là "x là cú pháp đường cho y".
Ingo

6
@Ingo nó tạo ra một đối tượng mới của lambda mỗi khi tôi sử dụng nó. TestingLambda$$Lambda$2/8460669TestingLambda$$Lambda$3/11043253được tạo ra trên hai lời mời.
Narendra Pathai

13
Lambdas và các tham chiếu phương thức không phải là "các lớp bên trong ẩn danh cũ đơn giản". Xem lập trình viên.stackexchange.com/a/181743/59134 . Có, nếu cần thiết, các lớp và thể hiện mới được tạo nhanh chóng, nếu cần, nhưng chỉ khi cần thiết.
Stuart Marks

Câu trả lời:


1022

Thông thường, người ta sẽ gọi reducephương thức bằng cách sử dụng Math.max(int, int)như sau:

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

Điều đó đòi hỏi rất nhiều cú pháp cho chỉ gọi Math.max. Đó là nơi biểu hiện lambda phát huy tác dụng. Vì Java 8, nó được phép làm điều tương tự theo cách ngắn hơn nhiều:

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

Cái này hoạt động ra sao? Trình biên dịch java "phát hiện", rằng bạn muốn thực hiện một phương thức chấp nhận hai ints và trả về một int. Điều này tương đương với các tham số chính thức của phương thức giao diện một và duy nhất IntBinaryOperator(tham số của phương thức reducebạn muốn gọi). Vì vậy, trình biên dịch thực hiện phần còn lại cho bạn - nó chỉ giả sử bạn muốn thực hiện IntBinaryOperator.

Nhưng khi Math.max(int, int)nó đáp ứng các yêu cầu chính thức IntBinaryOperator, nó có thể được sử dụng trực tiếp. Vì Java 7 không có bất kỳ cú pháp nào cho phép bản thân một phương thức được truyền dưới dạng đối số (bạn chỉ có thể truyền kết quả phương thức, nhưng không bao giờ tham chiếu phương thức), ::cú pháp được giới thiệu trong Java 8 cho phương thức tham chiếu:

reduce(Math::max);

Lưu ý rằng điều này sẽ được trình biên dịch diễn giải, không phải bởi JVM khi chạy! Mặc dù nó tạo ra các mã byte khác nhau cho cả ba đoạn mã, chúng đều bằng nhau về mặt ngữ nghĩa, vì vậy hai phiên bản cuối cùng có thể được coi là phiên bản ngắn (và có thể hiệu quả hơn) củaIntBinaryOperator triển khai ở trên!

(Xem thêm Bản dịch của Lambda Expressions )


489

::được gọi là Phương thức tham khảo. Nó về cơ bản là một tham chiếu đến một phương pháp duy nhất. Tức là nó đề cập đến một phương thức hiện có theo tên.

Giải thích ngắn :
Dưới đây là ví dụ về tham chiếu đến phương thức tĩnh:

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

Function<Double, Double> square = Hey::square;
double ans = square.apply(23d);

squarecó thể được truyền xung quanh giống như tham chiếu đối tượng và được kích hoạt khi cần. Trong thực tế, nó có thể dễ dàng được sử dụng như một tham chiếu đến các phương thức "thông thường" của các đối tượng như các phương thức static. Ví dụ:

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ở trên là giao diện chức năng . Để hiểu đầy đủ ::, điều quan trọng là phải hiểu giao diện chức năng là tốt. Rõ ràng, một giao diện chức năng là một giao diện chỉ với một phương thức trừu tượng.

Ví dụ về các giao diện chức năng bao gồm Runnable, CallableActionListener.

Functionở trên là một giao diện chức năng chỉ với một phương thức : apply. Nó nhận một đối số và tạo ra một kết quả.


Lý do tại sao ::s là tuyệt vời là rằng :

Tham chiếu phương thức là các biểu thức có cùng cách xử lý với biểu thức lambda (...), nhưng thay vì cung cấp phần thân phương thức, chúng tham chiếu một phương thức hiện có theo tên.

Ví dụ, thay vì viết cơ thể lambda

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

Bạn chỉ có thể làm

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

Trong thời gian chạy, hai squarephương thức này hoạt động giống hệt nhau. Mã byte có thể hoặc không giống nhau (mặc dù, đối với trường hợp trên, mã byte tương tự được tạo ra; biên dịch mã ở trên và kiểm tra với javap -c).

Tiêu chí chính duy nhất để đáp ứng là: phương thức bạn cung cấp phải có chữ ký tương tự như phương thức của giao diện chức năng bạn sử dụng làm tham chiếu đối tượng .

Dưới đây là bất hợp pháp:

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

squaremong đợi một đối số và trả về a double. Các getphương pháp trong Nhà cung cấp trả về giá trị nhưng không mất một cuộc tranh cãi. Do đó, điều này dẫn đến một lỗi.

Một tham chiếu phương thức đề cập đến phương thức của một giao diện chức năng. (Như đã đề cập, các giao diện chức năng có thể chỉ có một phương thức mỗi phương thức).

Một số ví dụ khác: acceptphương thức trong Người tiêu dùng nhận đầu vào nhưng không trả về bất cứ điều gì.

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

Ở trên, getRandomkhông có đối số và trả về a double. Vì vậy, bất kỳ giao diện chức năng nào thỏa mãn các tiêu chí: không tranh luận và trả vềdouble có thể được sử dụng.

Một vi dụ khac:

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

Trong trường hợp các loại tham số :

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;

Tham chiếu phương thức có thể có các kiểu khác nhau, nhưng về cơ bản, tất cả chúng đều có nghĩa giống nhau và có thể được hình dung đơn giản là lambdas:

  1. Một phương thức tĩnh ( ClassName::methName)
  2. Một phương thức thể hiện của một đối tượng cụ thể ( instanceRef::methName)
  3. Một siêu phương thức của một đối tượng cụ thể ( super::methName)
  4. Một phương thức thể hiện của một đối tượng tùy ý thuộc một loại cụ thể (ClassName::methName )
  5. Một tham chiếu hàm tạo lớp (ClassName::new )
  6. Một tham chiếu hàm tạo ( TypeName[]::new)

Để tham khảo thêm, hãy xem http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html .


6
Cám ơn vì đã giải thích. Tóm lại: '::' sử dụng để trích xuất một phương thức thỏa mãn FunctionalInterface (lambda): ClassX :: staticMethodX hoặc instanceX :: instanceMethodX "
jessarah 13/07/2016

55

Đúng là như vậy. Các ::nhà điều hành được sử dụng cho phương pháp tham khảo. Vì vậy, người ta có thể trích xuất các phương thức tĩnh từ các lớp bằng cách sử dụng nó hoặc các phương thức từ các đối tượng. Toán tử tương tự có thể được sử dụng ngay cả đối với các nhà xây dựng. Tất cả các trường hợp được đề cập ở đây được minh họa trong mẫu mã dưới đây.

Các tài liệu chính thức từ Oracle có thể được tìm thấy ở đây .

Bạn có thể có một cái nhìn tổng quan hơn về những thay đổi JDK 8 trong này bài viết. Trong phần tham chiếu Phương thức / Trình xây dựng, một ví dụ mã cũng được cung cấp:

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

một lời giải thích tốt là một trong những tìm thấy ở đây: doanduyhai.wordpress.com/2012/07/14/iêu
Olimpiu POP

1
@RichardTingle method(Math::max);là cách gọi và định nghĩa phương thức sẽ như thế nào public static void method(IntBinaryOperator op){System.out.println(op.applyAsInt(1, 2));}. Đó là cách nó được sử dụng.
Narendra Pathai

2
Đối với những người quen thuộc với C #, nó tương tự với DelegateType d = new DelegateType (MethodName);
Adrian Zanescu

27

Có vẻ như nó hơi muộn nhưng đây là hai xu của tôi. Một biểu thức lambda được sử dụng để tạo các phương thức ẩn danh. Nó không làm gì ngoài việc gọi một phương thức hiện có, nhưng rõ ràng hơn là tham chiếu phương thức trực tiếp bằng tên của nó. Và tham chiếu phương thức cho phép chúng ta làm điều đó bằng cách sử dụng toán tử tham chiếu phương thức ::.

Hãy xem xét các lớp đơn giản sau đây, nơi mỗi nhân viên có một tên và lớp.

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;
    }
}

Giả sử chúng tôi có một danh sách các nhân viên được trả về theo một số phương pháp và chúng tôi muốn sắp xếp các nhân viên theo cấp bậc của họ. Chúng tôi biết rằng chúng tôi có thể sử dụng lớp ẩn danh như:

    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());
           }
    });

trong đó getDummyEmployee () là một số phương thức như:

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")
                );
    }

Bây giờ chúng ta biết rằng So sánh là một Giao diện chức năng. Một giao diện chức năng là một với chính xác một phương pháp trừu tượng (mặc dù nó có thể chứa một hoặc nhiều phương pháp mặc định hoặc tĩnh). Biểu thức Lambda cung cấp việc thực hiện @FunctionalInterfaceđể giao diện chức năng có thể chỉ có một phương thức trừu tượng. Chúng ta có thể sử dụng biểu thức lambda như:

employeeList.sort((e1,e2) -> e1.getGrade().compareTo(e2.getGrade())); // lambda exp

Có vẻ như tất cả đều tốt nhưng nếu lớp Employeecũng cung cấp phương thức tương tự:

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

Trong trường hợp này sử dụng tên phương thức sẽ rõ ràng hơn. Do đó chúng ta có thể trực tiếp tham khảo phương thức bằng cách sử dụng tham chiếu phương thức như:

employeeList.sort(Employee::compareByGrade); // method reference

Theo tài liệu, có bốn loại tham chiếu phương thức:

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

25

::là một toán tử mới có trong Java 8 được sử dụng để chỉ một phương thức của một lớp hiện có. Bạn có thể tham khảo các phương thức tĩnh và phương thức không tĩnh của một lớp.

Để tham chiếu các phương thức tĩnh, cú pháp là:

ClassName :: methodName 

Để tham chiếu các phương thức không tĩnh, cú pháp là

objRef :: methodName

ClassName :: methodName

Điều kiện tiên quyết duy nhất để giới thiệu một phương thức là phương thức đó tồn tại trong một giao diện chức năng, phải tương thích với tham chiếu phương thức.

Tham chiếu phương thức, khi được đánh giá, tạo một thể hiện của giao diện chức năng.

Tìm thấy trên: http://www.speakingcs.com/2014/08/method-references-in-java-8.html


22

Đây là một tài liệu tham khảo phương thức trong Java 8. Tài liệu oracle có ở đây .

Như đã nêu trong tài liệu ...

Tham chiếu phương thức Person :: soByAge là một tham chiếu đến một phương thức tĩnh.

Sau đây là một ví dụ về tham chiếu đến một phương thức thể hiện của một đối tượng cụ thể:

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); 

Phương thức tham chiếu myComparisonProvider :: soByName gọi phương thức so sánhByName là một phần của đối tượng myComparisonProvider. JRE đưa ra các đối số kiểu phương thức, trong trường hợp này là (Người, Người).


2
nhưng phương thức 'so sánhByAge' không tĩnh.
abbas

3
@abbas Cũng không phải là so sánhByName. Do đó, bạn truy cập các phương thức không tĩnh này thông qua toán tử tham chiếu bằng cách sử dụng một đối tượng. Nếu chúng là tĩnh, bạn có thể sử dụng tên lớp như so sánhProvider :: someStaticMethod
Seshadri R

6

:: Toán tử được giới thiệu trong java 8 để tham khảo phương thức. Tham chiếu phương thức là cú pháp tốc ký cho biểu thức lambda thực thi chỉ MỘT phương thức. Đây là cú pháp chung của một tham chiếu phương thức:

Object :: methodName

Chúng tôi biết rằng chúng tôi có thể sử dụng biểu thức lambda thay vì sử dụng một lớp ẩn danh. Nhưng đôi khi, biểu thức lambda thực sự chỉ là một lời kêu gọi một phương thức nào đó, ví dụ:

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

Để làm cho mã rõ ràng hơn, bạn có thể biến biểu thức lambda đó thành một tham chiếu phương thức:

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

3

:: được gọi là tham chiếu phương thức. Hãy nói rằng chúng tôi muốn gọi một phương thức tính toán của lớp Mua. Sau đó chúng ta có thể viết nó là:

Purchase::calculatePrice

Nó cũng có thể được xem như là hình thức viết ngắn của biểu thức lambda Bởi vì các tham chiếu phương thức được chuyển đổi thành biểu thức lambda.


Tôi có thể thực hiện các tham chiếu phương thức lồng nhau không? ví dụ:

bạn không thể tạo tham chiếu phương thức lồng nhau theo cách đó
Kirby

3

Tôi thấy nguồn này rất thú vị.

Trên thực tế, chính Lambda biến thành Double Double . Double Colon dễ đọc hơn. Chúng tôi làm theo các bước sau:

BƯỚC 1:

// We create a comparator of two persons
Comparator c = (Person p1, Person p2) -> p1.getAge().compareTo(p2.getAge());

BƯỚC 2:

// We use the interference
Comparator c = (p1, p2) -> p1.getAge().compareTo(p2.getAge());

BƯỚC 3:

// The magic using method reference
Comparator c = Comparator.comparing(Person::getAge);

3
Có vẻ như Person::getAge()nên Person::getAge.
Qwertiy

2

return reduce(Math::max);không bằng nhau đểreturn reduce(max());

Nhưng nó có nghĩa là, một cái gì đó như thế này:

IntBinaryOperator myLambda = (a, b)->{(a >= b) ? a : b};//56 keystrokes I had to type -_-
return reduce(myLambda);

Bạn chỉ có thể lưu 47 tổ hợp phím nếu bạn viết như thế này

return reduce(Math::max);//Only 9 keystrokes ^_^

2

Vì nhiều câu trả lời ở đây đã giải thích ::hành vi tốt , ngoài ra, tôi muốn làm rõ rằng :: toán tử không cần phải có chữ ký chính xác như Giao diện chức năng tham chiếu nếu nó được sử dụng cho các biến thể hiện . Giả sử chúng ta cần một BinaryOperator có loại TestObject . Theo cách truyền thống, nó được thực hiện như thế này:

BinaryOperator<TestObject> binary = new BinaryOperator<TestObject>() {

        @Override
        public TestObject apply(TestObject t, TestObject u) {

            return t;
        }
    };

Như bạn thấy trong triển khai ẩn danh, nó yêu cầu hai đối số TestObject và trả về một đối tượng TestObject. Để thỏa mãn điều kiện này bằng cách sử dụng ::toán tử, chúng ta có thể bắt đầu với một phương thức tĩnh:

public class TestObject {


    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

và sau đó gọi:

BinaryOperator<TestObject> binary = TestObject::testStatic;

Ok nó biên dịch tốt. Nếu chúng ta cần một phương thức ví dụ thì sao? Cho phép cập nhật TestObject bằng phương thức cá thể:

public class TestObject {

    public final TestObject testInstance(TestObject t, TestObject t2){
        return t;
    }

    public static final TestObject testStatic(TestObject t, TestObject t2){
        return t;
    }
}

Bây giờ chúng ta có thể truy cập thể hiện như dưới đây:

TestObject testObject = new TestObject();
BinaryOperator<TestObject> binary = testObject::testInstance;

Mã này biên dịch tốt, nhưng dưới đây không phải là:

BinaryOperator<TestObject> binary = TestObject::testInstance;

Nhật thực của tôi nói với tôi "Không thể tạo tham chiếu tĩnh cho phương thức testInstance (TestObject, TestObject) từ loại TestObject ..."

Đủ công bằng một phương thức cá thể, nhưng nếu chúng ta quá tải testInstancenhư dưới đây:

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;
    }
}

Và gọi:

BinaryOperator<TestObject> binary = TestObject::testInstance;

Mã sẽ biên dịch tốt. Bởi vì nó sẽ gọi testInstancevới tham số duy nhất thay vì gấp đôi. Ok vậy chuyện gì đã xảy ra với hai tham số của chúng ta? Cho phép in ra và xem:

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;
    }
}

Sẽ xuất ra:

 1418481495  
 303563356  
 Test instance called. this.hashCode:1418481495
 Given parameter hashCode:303563356

Ok vậy JVM đủ thông minh để gọi param1.testInstance (param2). Chúng tôi có thể sử dụng testInstancetừ một tài nguyên khác nhưng không phải TestObject, nghĩa là:

public class TestUtil {

    public final TestObject testInstance(TestObject t){
        return t;
    }
}

Và gọi:

BinaryOperator<TestObject> binary = TestUtil::testInstance;

Nó sẽ không biên dịch và trình biên dịch sẽ cho biết: "Loại TestUtil không định nghĩa testInstance (TestObject, TestObject)" . Vì vậy, trình biên dịch sẽ tìm kiếm một tham chiếu tĩnh nếu nó không cùng loại. Ok những gì về đa hình? Nếu chúng tôi xóa các sửa đổi cuối cùng và thêm lớp SubTestObject của chúng tôi :

public class SubTestObject extends TestObject {

    public final TestObject testInstance(TestObject t){
        return t;
    }

}

Và gọi:

BinaryOperator<TestObject> binary = SubTestObject::testInstance;

Nó cũng sẽ không biên dịch, trình biên dịch vẫn sẽ tìm tham chiếu tĩnh. Nhưng mã bên dưới sẽ biên dịch tốt vì nó đang vượt qua - một bài kiểm tra:

public class TestObject {

    public SubTestObject testInstance(Object t){
        return (SubTestObject) t;
    }

}

BinaryOperator<TestObject> binary = TestObject::testInstance;

* Tôi chỉ đang học nên tôi đã tìm ra bằng cách thử và xem, cứ tự nhiên sửa nếu tôi sai


2

Trong java-8 Streams Reducer trong các công việc đơn giản là một hàm lấy hai giá trị làm đầu vào và trả về kết quả sau một số tính toán. kết quả này được cho ăn trong lần lặp lại tiếp theo.

trong trường hợp Math: hàm max, phương thức tiếp tục trả về tối đa hai giá trị được truyền và cuối cùng bạn có số lớn nhất trong tay.


1

Trong thời gian chạy, chúng hoạt động giống hệt nhau. Mã byte có thể / không giống nhau (Đối với Incase ở trên, nó tạo ra mã byte tương tự (tuân thủ ở trên và kiểm tra javaap -c;))

Trong thời gian chạy, chúng hoạt động giống hệt nhau.method (math :: max);, nó tạo ra cùng một phép toán (tuân theo và kiểm tra javap -c;))


1

Trong các phiên bản Java cũ hơn, thay vì "::" hoặc lambd, bạn có thể sử dụng:

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();
}

Hoặc truyền vào phương thức:

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

1

Vì vậy, tôi thấy ở đây hàng tấn câu trả lời thẳng thắn quá mức, và đó là một cách đánh giá thấp.

Câu trả lời khá đơn giản: :: nó được gọi là Tài liệu tham khảo Phương thức https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

Vì vậy, tôi sẽ không sao chép-dán, trên liên kết, bạn có thể tìm thấy tất cả thông tin nếu bạn cuộn xuống bàn.


Bây giờ, chúng ta hãy xem xét ngắn gọn về Tài liệu tham khảo Phương pháp là gì:

A :: B phần nào thay thế biểu thức lambda nội tuyến sau : (params ...) -> AB (params ...)

Để tương quan điều này với các câu hỏi của bạn, cần phải hiểu một biểu thức lambda java. Cái nào không khó.

Một biểu thức lambda nội tuyến tương tự như một giao diện chức năng được xác định (là một giao diện không có nhiều hơn và không ít hơn 1 phương thức) . Hãy xem qua ý tôi là:

InterfaceX f = (x) -> x*x; 

InterfaceX phải là một giao diện chức năng. Bất kỳ giao diện chức năng nào, điều duy nhất quan trọng về InterfaceX cho trình biên dịch đó là bạn xác định định dạng:

InterfaceX có thể là một trong số này:

interface InterfaceX
{
    public Integer callMe(Integer x);
}

hoặc cái này

interface InterfaceX
{
    public Double callMe(Integer x);
}

hoặc chung chung hơn:

interface InterfaceX<T,U>
{
    public T callMe(U x);
}

Chúng ta hãy lấy trường hợp được trình bày đầu tiên và biểu thức lambda nội tuyến mà chúng ta đã xác định trước đó.

Trước Java 8, bạn có thể đã định nghĩa nó tương tự theo cách này:

 InterfaceX o = new InterfaceX(){
                     public int callMe (int x, int y) 
                       {
                        return x*x;
                       } };

Về mặt chức năng, đó là điều tương tự. Sự khác biệt là nhiều hơn trong cách trình biên dịch nhận thức điều này.

Bây giờ chúng ta đã xem biểu thức lambda nội tuyến, hãy quay lại Phương thức tham khảo (: :). Giả sử bạn có một lớp học như thế này:

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

Vì phương thức anyFifts có cùng loại với CallMe của InterfaceX , nên chúng ta có thể tương đương hai loại đó với Tham chiếu Phương thức.

Chúng ta có thể viết nó như thế này:

InterfaceX o =  Q::anyFunction; 

và điều đó tương đương với điều này:

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

Một điều thú vị và lợi thế của Tài liệu tham khảo Phương pháp là lúc đầu, cho đến khi bạn gán chúng cho các biến, chúng không có lỗi. Vì vậy, bạn có thể chuyển chúng dưới dạng tham số cho bất kỳ giao diện chức năng tương đương (có cùng loại được xác định). Đó chính xác là những gì xảy ra trong trường hợp của bạn


1

Các câu trả lời trước khá đầy đủ về những gì :: tham khảo phương pháp nào. Tóm lại, nó cung cấp một cách để tham chiếu đến một phương thức (hoặc hàm tạo) mà không thực hiện nó, và khi được đánh giá, nó tạo ra một thể hiện của giao diện chức năng cung cấp bối cảnh loại mục tiêu.

Dưới đây là hai ví dụ để tìm một đối tượng có giá trị tối đa trong một ArrayListCÓ và KHÔNG sử dụng ::tham chiếu phương thức. Giải thích trong các ý kiến ​​dưới đây.


KHÔNG sử dụng ::

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());
    }
}

Với việc sử dụng ::

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

-1

::Toán tử dấu hai chấm nghĩa được giới thiệu trong Java 8 như là một tham chiếu phương thức . Tham chiếu phương thức là một dạng biểu thức lambda được sử dụng để chỉ phương thức hiện có theo tên của nó.

tên lớp :: tên phương thức

Ví dụ:-

  • stream.forEach(element -> System.out.println(element))

Bằng cách sử dụng Double Colon ::

  • stream.forEach(System.out::println(element))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.