Java8 Lambdas vs Anonymous các lớp


111

Vì Java8 đã được phát hành gần đây và các biểu thức lambda hoàn toàn mới của nó trông thực sự tuyệt vời, tôi đã tự hỏi liệu điều này có nghĩa là sự sụp đổ của các lớp Anonymous mà chúng ta đã từng quen thuộc hay không.

Tôi đã nghiên cứu một chút về điều này và tìm thấy một số ví dụ thú vị về cách các biểu thức Lambda sẽ thay thế các lớp đó một cách có hệ thống, chẳng hạn như phương thức sắp xếp của Bộ sưu tập, được sử dụng để lấy một phiên bản Ẩn danh của Bộ so sánh để thực hiện sắp xếp:

Collections.sort(personList, new Comparator<Person>(){
  public int compare(Person p1, Person p2){
    return p1.firstName.compareTo(p2.firstName);
  }
});

Bây giờ có thể được thực hiện bằng Lambdas:

Collections.sort(personList, (Person p1, Person p2) -> p1.firstName.compareTo(p2.firstName));

Và có vẻ ngắn gọn đáng ngạc nhiên. Vì vậy, câu hỏi của tôi là, có lý do gì để tiếp tục sử dụng các lớp đó trong Java8 thay vì Lambdas không?

BIÊN TẬP

Cùng một câu hỏi nhưng ở chiều ngược lại, lợi ích của việc sử dụng Lambdas thay vì các lớp Anonymous là gì, vì Lambdas chỉ có thể được sử dụng với các giao diện phương thức duy nhất, liệu tính năng mới này chỉ là một phím tắt chỉ được sử dụng trong một số trường hợp hay nó thực sự hữu ích?


5
Chắc chắn, đối với tất cả những lớp ẩn danh cung cấp các phương pháp có tác dụng phụ.
tobias_k

11
Chỉ đối với thông tin của bạn, bạn cũng có thể xây dựng bộ so sánh như:, Comparator.comparing(Person::getFirstName)if getFirstName()sẽ là một phương thức trả về firstName.
skiwi

1
Hoặc các lớp học vô danh với nhiều phương pháp, hoặc ...
Đánh dấu Rotteveel

1
Tôi muốn bỏ phiếu cho gần vì quá rộng, đặc biệt là do các câu hỏi bổ sung sau khi CHỈNH SỬA .
Đánh dấu Rotteveel vào

1
Một bài viết chuyên sâu hay về chủ đề này: infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
Ram Patra

Câu trả lời:


108

Một lớp bên trong ẩn danh (AIC) có thể được sử dụng để tạo một lớp con của một lớp trừu tượng hoặc một lớp cụ thể. AIC cũng có thể cung cấp một giao diện triển khai cụ thể, bao gồm cả việc bổ sung trạng thái (các trường). Một thể hiện của AIC có thể được tham chiếu bằng cách sử dụng thistrong các thân phương thức của nó, vì vậy các phương thức khác có thể được gọi trên nó, trạng thái của nó có thể bị thay đổi theo thời gian, v.v. Không điều nào trong số này áp dụng cho lambdas.

Tôi đoán rằng phần lớn việc sử dụng AIC là để cung cấp các triển khai không trạng thái của các hàm đơn lẻ và do đó có thể được thay thế bằng các biểu thức lambda, nhưng có những cách sử dụng AIC khác mà lambdas không thể được sử dụng. AIC ở đây để ở lại.

CẬP NHẬT

Một sự khác biệt khác giữa AIC và các biểu thức lambda là AIC giới thiệu một phạm vi mới. Nghĩa là, các tên được phân giải từ các lớp cha và giao diện của AIC và có thể ẩn tên xuất hiện trong môi trường bao bọc từ vựng. Đối với lambdas, tất cả các tên đều được giải quyết từ vựng.


1
Lambdas có thể có trạng thái. Về mặt này, tôi không thấy có sự khác biệt nào giữa Lambdas và AIC.
nosid

1
@nosid AIC, giống như các phiên bản của bất kỳ lớp nào, có thể giữ trạng thái trong các trường và trạng thái này có thể truy cập (và có thể thay đổi bằng) bất kỳ phương thức nào của lớp. Trạng thái này tồn tại cho đến khi đối tượng là GC'd, tức là nó có phạm vi vô định, vì vậy nó có thể tồn tại qua các lần gọi phương thức. Trạng thái duy nhất có mức độ vô hạn của lambda được bắt tại thời điểm lambda gặp phải; trạng thái này là bất biến. Các biến cục bộ trong lambda có thể thay đổi được, nhưng chúng chỉ tồn tại khi đang tiến hành lệnh gọi lambda.
Stuart Marks

1
@nosid Ah, vụ hack mảng một phần tử. Chỉ cần không cố gắng sử dụng bộ đếm của bạn từ nhiều chuỗi. Nếu bạn định phân bổ thứ gì đó trên heap và nắm bắt nó trong lambda, bạn cũng có thể sử dụng AIC và thêm một trường mà bạn có thể thay đổi trực tiếp. Sử dụng lambda theo cách này có thể hiệu quả, nhưng tại sao phải bận tâm khi bạn có thể sử dụng một đối tượng thực?
Stuart Marks

2
AIC sẽ tạo một tệp giống như thế này, A$1.classnhưng Lambda thì không. Tôi có thể thêm điều này trong Sự khác biệt không?
Asif Mushtaq

2
@UnKnown Đó chủ yếu là mối quan tâm về triển khai; nó không ảnh hưởng đến cách một chương trình với AICs vs lambdas, đó là những gì câu hỏi này chủ yếu là về. Lưu ý rằng một biểu thức lambda không tạo ra một lớp có tên như LambdaClass$$Lambda$1/1078694789. Tuy nhiên, lớp này được tạo ngay lập tức bởi lambda metafactory, không phải bởi javac, vì vậy không có .classtệp tương ứng . Tuy nhiên, một lần nữa, đây là một mối quan tâm thực hiện.
Stuart Marks

60

Lambdas mặc dù là một tính năng tuyệt vời, sẽ chỉ hoạt động với các loại SAM. Đó là, các giao diện chỉ với một phương thức trừu tượng duy nhất. Nó sẽ không thành công ngay khi giao diện của bạn chứa nhiều hơn 1 phương thức trừu tượng. Đó là nơi mà các lớp ẩn danh sẽ hữu ích.

Vì vậy, không, chúng ta không thể bỏ qua các lớp ẩn danh. Và chỉ FYI, sort()phương pháp của bạn có thể được đơn giản hóa hơn, bằng cách bỏ qua khai báo kiểu cho p1p2:

Collections.sort(personList, (p1, p2) -> p1.firstName.compareTo(p2.firstName));

Bạn cũng có thể sử dụng phương pháp tham khảo tại đây. Hoặc bạn thêm một compareByFirstName()phương thức trong Personlớp và sử dụng:

Collections.sort(personList, Person::compareByFirstName);

hoặc, thêm getter cho firstName, trực tiếp lấy phương thức Comparatorfrom Comparator.comparing():

Collections.sort(personList, Comparator.comparing(Person::getFirstName));

4
Tôi biết, nhưng tôi thích cái dài hơn về mặt dễ đọc, bởi vì nếu không, sẽ rất khó hiểu khi tìm ra những biến này đến từ đâu.
Amin Abu-Taleb

@ AminAbu-Taleb Tại sao nó sẽ khó hiểu. Đó là một cú pháp Lambda hợp lệ. Loại nào cũng được suy ra. Dù sao, đó là một lựa chọn cá nhân. Bạn có thể cung cấp các loại rõ ràng. Không vấn đề.
Rohit Jain

1
Có một sự khác biệt nhỏ giữa lambdas và các lớp ẩn danh: Các lớp ẩn danh có thể được chú thích trực tiếp với Chú thích kiểu Java 8 mới , chẳng hạn như new @MyTypeAnnotation SomeInterface(){};. Điều này là không thể đối với các biểu thức lambda. Để biết chi tiết, hãy xem câu hỏi của tôi tại đây: Chú thích giao diện chức năng của Biểu thức Lambda .
Balder

36

Hiệu suất lambda với các lớp Ẩn danh

Khi ứng dụng được khởi chạy, mỗi tệp lớp phải được tải và xác minh.

Các lớp ẩn danh được trình biên dịch xử lý như một kiểu con mới cho lớp hoặc giao diện nhất định, do đó, sẽ có một tệp lớp mới cho mỗi lớp.

Lambdas khác nhau ở việc tạo bytecode, chúng hiệu quả hơn, được sử dụng lệnh gọi động học đi kèm với JDK7.

Đối với Lambdas, lệnh này được sử dụng để trì hoãn dịch biểu thức lambda trong thời gian chạy bytecode cho đến khi thực hiện. (chỉ dẫn sẽ được gọi lần đầu tiên)

Kết quả là biểu thức Lambda sẽ trở thành một phương thức tĩnh (được tạo trong thời gian chạy). (Có một sự khác biệt nhỏ với trường hợp trạng thái và trường hợp trạng thái đầy đủ, chúng được giải quyết thông qua các đối số phương thức được tạo)


Mỗi lambda cũng cần một lớp mới, nhưng nó được tạo ra trong thời gian chạy, vì vậy theo nghĩa này, lambda không hiệu quả hơn các lớp ẩn danh. Lambdas được tạo thông qua invokedynamicđó thường chậm hơn so với invokespecialđược sử dụng để tạo các phiên bản mới của các lớp ẩn danh. Vì vậy, theo nghĩa này lambdas cũng chậm hơn (tuy nhiên, JVM có thể tối ưu hóa invokedynamiccác cuộc gọi hầu hết thời gian).
ZhekaKozlov

3
@AndreiTomashpolskiy 1. Hãy lịch sự. 2. Đọc nhận xét này của một kỹ sư biên dịch: habrahabr.ru/post/313350/comments/#comment_9885460
ZhekaKozlov

@ZhekaKozlov, bạn không cần phải là một kỹ sư biên dịch để đọc mã nguồn JRE và sử dụng javap / trình gỡ lỗi. Những gì bạn đang thiếu là việc tạo ra một lớp trình bao bọc cho một phương thức lambda được thực hiện hoàn toàn trong bộ nhớ và không tốn kém chi phí nào, trong khi việc khởi tạo AIC liên quan đến việc giải quyết và tải tài nguyên lớp tương ứng (có nghĩa là lệnh gọi hệ thống I / O). Do đó, invokedynamicvới việc tạo lớp đặc biệt là nhanh chóng so với các lớp ẩn danh đã biên dịch.
Andrei Tomashpolskiy

@AndreiTomashpolskiy I / O không nhất thiết phải chậm
ZhekaKozlov

14

Có những khác biệt sau:

1) Cú pháp

Biểu thức Lambda trông gọn gàng so với Lớp bên trong ẩn danh (AIC)

public static void main(String[] args) {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("in run");
        }
    };

    Thread t = new Thread(r);
    t.start(); 
}

//syntax of lambda expression 
public static void main(String[] args) {
    Runnable r = ()->{System.out.println("in run");};
    Thread t = new Thread(r);
    t.start();
}

2. Phạm vi

Một lớp bên trong ẩn danh là một lớp, có nghĩa là nó có phạm vi cho biến được xác định bên trong lớp bên trong.

Trong khi đó, biểu thức lambda không phải là một phạm vi của riêng nó, mà là một phần của phạm vi bao quanh.

Quy tắc tương tự áp dụng cho super và từ khóa này khi sử dụng bên trong lớp bên trong ẩn danh và biểu thức lambda. Trong trường hợp lớp bên trong ẩn danh, từ khóa này đề cập đến phạm vi cục bộ và từ khóa super đề cập đến siêu lớp của lớp ẩn danh. Trong trường hợp của biểu thức lambda, từ khóa này đề cập đến đối tượng của kiểu bao quanh và super sẽ tham chiếu đến siêu lớp của lớp bao quanh.

//AIC
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = new Runnable() {
            @Override
            public void run() {
                int cnt = 5;    
                System.out.println("in run" + cnt);
            }
        };

        Thread t = new Thread(r);
        t.start();
    }

//Lambda
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = ()->{
            int cnt = 5; //compilation error
            System.out.println("in run"+cnt);};
        Thread t = new Thread(r);
        t.start();
    }

3) Hiệu suất

Trong thời gian chạy các lớp ẩn danh bên trong yêu cầu tải lớp, cấp phát bộ nhớ và khởi tạo đối tượng và gọi một phương thức không tĩnh trong khi biểu thức lambda là hoạt động thời gian biên dịch thuần túy và không phải chịu thêm chi phí trong thời gian chạy. Vì vậy, hiệu suất của biểu thức lambda tốt hơn so với các lớp ẩn danh bên trong. **

** Tôi nhận ra rằng điểm này không hoàn toàn đúng. Vui lòng tham khảo câu hỏi sau để biết chi tiết. Lambda vs hiệu suất lớp bên trong ẩn danh: giảm tải trên ClassLoader?


3

Lambda's trong java 8 đã được giới thiệu để lập trình chức năng. Nơi bạn có thể tránh mã viết sẵn. Tôi đã xem qua bài viết thú vị này trên lambda's.

http://radar.oreilly.com/2014/04/whats-new-in-java-8-lambdas.html

Bạn nên sử dụng các hàm lambda cho các logic đơn giản. Nếu thực hiện logic phức tạp bằng lambdas sẽ là một chi phí lớn trong việc gỡ lỗi mã trong trường hợp có vấn đề.


0

Lớp ẩn danh vẫn ở đó vì lambda phù hợp với các hàm với các phương thức trừu tượng đơn lẻ nhưng đối với tất cả các trường hợp khác, các lớp ẩn danh bên trong là vị cứu tinh của bạn.


-1
  • cú pháp lambda không yêu cầu viết mã rõ ràng mà java có thể suy ra.
  • Bằng cách sử dụng invoke dynamic, lambda không được chuyển đổi trở lại các lớp ẩn danh trong thời gian biên dịch (Java không phải tạo đối tượng, chỉ cần quan tâm đến chữ ký của phương thức, có thể liên kết với phương thức mà không cần tạo đối tượng
  • lambda nhấn mạnh hơn vào những gì chúng ta muốn làm thay vì những gì chúng ta phải làm trước khi chúng ta có thể làm được
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.