1. Giao diện
Để hiểu chức năng lambda là gì, trước tiên bạn cần hiểu giao diện là gì. Vì vậy, hãy nhớ lại những điểm chính.
Một giao diện là một biến thể của khái niệm về một lớp. Giả sử một lớp bị cắt ngắn nhiều. Không giống như một lớp, một giao diện không thể có các biến riêng của nó (ngoại trừ các biến tĩnh). Bạn cũng không thể tạo các đối tượng có kiểu là một giao diện:
- Bạn không thể khai báo các biến của lớp
- Bạn không thể tạo đối tượng
Ví dụ:
interface Runnable
{
void run();
}
Sử dụng một giao diện
Vậy tại sao lại cần một giao diện? Các giao diện chỉ được sử dụng cùng với sự kế thừa. Các lớp khác nhau có thể kế thừa cùng một giao diện, hay như người ta vẫn nói - các lớp triển khai giao diện .
Nếu một lớp cài đặt một giao diện, thì nó phải cài đặt các phương thức được khai báo nhưng không được cài đặt bởi giao diện. Ví dụ:
interface Runnable
{
void run();
}
class Timer implements Runnable
{
void run()
{
System.out.println(LocalTime.now());
}
}
class Calendar implements Runnable
{
void run()
{
var date = LocalDate.now();
System.out.println("Today: " + date.getDayOfWeek());
}
}
Lớp Timer
thực hiện Runnable
giao diện, vì vậy nó phải khai báo bên trong chính nó tất cả các phương thức có trong Runnable
giao diện và thực hiện chúng, tức là viết mã trong phần thân phương thức. Đối với lớp học cũng vậy Calendar
.
Nhưng bây giờ Runnable
các biến có thể lưu trữ các tham chiếu đến các đối tượng thực hiện Runnable
giao diện.
Ví dụ:
Mã số | Ghi chú |
---|---|
|
Phương run() thức trong Timer lớp sẽ được gọi Phương run() thức trong Timer lớp sẽ được gọi Phương run() thức trong Calendar lớp sẽ được gọi |
Bạn luôn có thể gán một tham chiếu đối tượng cho một biến thuộc bất kỳ loại nào, miễn là loại đó là một trong các lớp tổ tiên của đối tượng. Đối với các lớp Timer
và Calendar
, có hai loại như vậy: Object
và Runnable
.
Nếu bạn gán một tham chiếu đối tượng cho một Object
biến, bạn chỉ có thể gọi các phương thức được khai báo trong Object
lớp. Và nếu bạn gán một tham chiếu đối tượng cho một Runnable
biến, bạn có thể gọi các phương thức của Runnable
kiểu đó.
Ví dụ 2:
ArrayList<Runnable> list = new ArrayList<Runnable>();
list.add (new Timer());
list.add (new Calendar());
for (Runnable element: list)
element.run();
Mã này sẽ hoạt động, bởi vì các đối tượng Timer
và Calendar
có các phương thức chạy hoạt động hoàn hảo. Vì vậy, gọi cho họ không phải là một vấn đề. Nếu chúng ta vừa thêm một phương thức run() vào cả hai lớp, thì chúng ta sẽ không thể gọi chúng theo cách đơn giản như vậy.
Về cơ bản, Runnable
giao diện chỉ được sử dụng làm nơi đặt phương thức chạy.
2. Sắp xếp
Hãy chuyển sang một cái gì đó thiết thực hơn. Ví dụ, hãy xem việc sắp xếp các chuỗi.
Để sắp xếp một tập hợp các chuỗi theo thứ tự bảng chữ cái, Java có một phương thức tuyệt vời được gọi làCollections.sort(collection);
Phương thức tĩnh này sắp xếp bộ sưu tập đã qua. Và trong quá trình sắp xếp, nó thực hiện so sánh theo cặp các phần tử của nó để hiểu liệu các phần tử có nên hoán đổi hay không.
Trong quá trình sắp xếp, các so sánh này được thực hiện bằng compareTo
phương thức (), mà tất cả các lớp tiêu chuẩn đều có: Integer
, String
, ...
Phương thức so sánh () của lớp Số nguyên so sánh các giá trị của hai số, trong khi phương thức so sánh () của lớp Chuỗi xem xét thứ tự bảng chữ cái của chuỗi.
Vì vậy, một tập hợp các số sẽ được sắp xếp theo thứ tự tăng dần, trong khi một tập hợp các chuỗi sẽ được sắp xếp theo thứ tự bảng chữ cái.
Các thuật toán sắp xếp thay thế
Nhưng nếu chúng ta muốn sắp xếp các chuỗi không theo thứ tự bảng chữ cái mà theo độ dài của chúng thì sao? Và nếu chúng ta muốn sắp xếp các số theo thứ tự giảm dần thì sao? Bạn làm gì trong trường hợp này?
Để xử lý các tình huống như vậy, Collections
lớp có một sort()
phương thức khác có hai tham số:
Collections.sort(collection, comparator);
Trong đó bộ so sánh là một đối tượng đặc biệt biết cách so sánh các đối tượng trong một bộ sưu tập trong quá trình sắp xếp . Thuật ngữ so sánh xuất phát từ từ so sánh tiếng Anh , do đó bắt nguồn từ so sánh , có nghĩa là "so sánh".
Vậy đối tượng đặc biệt này là gì?
Comparator
giao diện
Chà, tất cả đều rất đơn giản. Loại sort()
tham số thứ hai của phương thức làComparator<T>
Trong đó T là một tham số kiểu cho biết kiểu của các phần tử trong tập hợp và Comparator
là một giao diện có một phương thức duy nhấtint compare(T obj1, T obj2);
Nói cách khác, một đối tượng so sánh là bất kỳ đối tượng nào của bất kỳ lớp nào thực hiện giao diện Bộ so sánh. Giao diện Bộ so sánh trông rất đơn giản:
public interface Comparator<Type>
{
public int compare(Type obj1, Type obj2);
}
Phương compare()
thức so sánh hai đối số được truyền cho nó.
Nếu phương thức trả về một số âm, điều đó có nghĩa là obj1 < obj2
. Nếu phương thức trả về một số dương, điều đó có nghĩa là obj1 > obj2
. Nếu phương thức trả về 0, điều đó có nghĩa là obj1 == obj2
.
Đây là một ví dụ về một đối tượng so sánh so sánh các chuỗi theo độ dài của chúng:
public class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
StringLengthComparator
lớp
Để so sánh độ dài của chuỗi, chỉ cần trừ độ dài này cho độ dài kia.
Mã hoàn chỉnh cho một chương trình sắp xếp các chuỗi theo độ dài sẽ như sau:
public class Solution
{
public static void main(String[] args)
{ ArrayList<String> list = new ArrayList<String>(); Collections.addAll(list, "Hello", "how's", "life?"); Collections.sort(list, new StringLengthComparator()); } } class StringLengthComparator implements Comparator<String> { public int compare (String obj1, String obj2) { return obj1.length() – obj2.length(); } }
3. Đường cú pháp
Bạn nghĩ gì, mã này có thể được viết gọn hơn không? Về cơ bản, chỉ có một dòng chứa thông tin hữu ích — obj1.length() - obj2.length();
.
Nhưng mã không thể tồn tại bên ngoài một phương thức, vì vậy chúng tôi phải thêm một compare()
phương thức và để lưu trữ phương thức đó, chúng tôi phải thêm một lớp mới — StringLengthComparator
. Và chúng ta cũng cần chỉ định các loại biến... Mọi thứ dường như đều đúng.
Nhưng có nhiều cách để làm cho mã này ngắn hơn. Chúng tôi đã có một số đường cú pháp cho bạn. Hai muỗng!
Lớp bên trong ẩn danh
Bạn có thể viết mã so sánh ngay bên trong main()
phương thức và trình biên dịch sẽ thực hiện phần còn lại. Ví dụ:
public class Solution
{
public static void main(String[] args)
{ ArrayList<String> list = new ArrayList<String>(); Collections.addAll(list, "Hello", "how's", "life?"); Comparator<String> comparator = new Comparator<String>() { public int compare (String obj1, String obj2) { return obj1.length() – obj2.length(); } }; Collections.sort(list, comparator); } }
Bạn có thể tạo một đối tượng cài đặt Comparator
giao diện mà không cần tạo một lớp rõ ràng! Trình biên dịch sẽ tự động tạo và đặt tên tạm thời cho nó. Hãy so sánh:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Comparator<String> comparator = new StringLengthComparator();
class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
StringLengthComparator
lớp học
Màu giống nhau được sử dụng để biểu thị các khối mã giống hệt nhau trong hai trường hợp khác nhau. Sự khác biệt là khá nhỏ trong thực tế.
Khi trình biên dịch gặp khối mã đầu tiên, nó chỉ cần tạo khối mã thứ hai tương ứng và đặt cho lớp một số tên ngẫu nhiên.
4. Biểu thức Lambda trong Java
Giả sử bạn quyết định sử dụng một lớp bên trong ẩn danh trong mã của mình. Trong trường hợp này, bạn sẽ có một khối mã như thế này:
Comparator<String> comparator = new Comparator<String>()
{ public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Ở đây chúng tôi kết hợp việc khai báo một biến với việc tạo ra một lớp ẩn danh. Nhưng có một cách để làm cho mã này ngắn hơn. Ví dụ, như thế này:
Comparator<String> comparator = (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
};
Dấu chấm phẩy là cần thiết bởi vì ở đây chúng ta không chỉ có khai báo lớp ẩn mà còn có việc tạo một biến.
Ký hiệu như thế này được gọi là biểu thức lambda.
Nếu trình biên dịch gặp ký hiệu như thế này trong mã của bạn, thì nó chỉ tạo ra phiên bản dài dòng của mã (với một lớp bên trong ẩn danh).
Lưu ý rằng khi viết biểu thức lambda, chúng tôi đã bỏ qua không chỉ tên của lớp mà còn cả tên của phương thức.Comparator<String>
int compare()
Quá trình biên dịch sẽ không gặp vấn đề gì khi xác định phương thức , bởi vì biểu thức lambda chỉ có thể được viết cho các giao diện có một phương thức duy nhất . Nhân tiện, có một cách để vượt qua quy tắc này, nhưng bạn sẽ tìm hiểu về điều đó khi bắt đầu nghiên cứu sâu hơn về OOP (chúng ta đang nói về các phương pháp mặc định).
Hãy xem lại phiên bản dài dòng của mã, nhưng chúng ta sẽ tô xám phần có thể bỏ qua khi viết biểu thức lambda:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Có vẻ như không có gì quan trọng đã được bỏ qua. Thật vậy, nếu Comparator
giao diện chỉ có một compare()
phương thức, trình biên dịch hoàn toàn có thể khôi phục mã bị mờ từ mã còn lại.
Sắp xếp
Nhân tiện, bây giờ chúng ta có thể viết mã sắp xếp như thế này:
Comparator<String> comparator = (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
};
Collections.sort(list, comparator);
Hoặc thậm chí như thế này:
Collections.sort(list, (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
}
);
Chúng tôi chỉ cần ngay lập tức thay thế comparator
biến bằng giá trị được gán cho comparator
biến.
Loại suy luận
Nhưng đó không phải là tất cả. Mã trong các ví dụ này thậm chí có thể được viết gọn hơn. Đầu tiên, trình biên dịch có thể tự xác định rằng các biến obj1
và obj2
là Strings
. Và thứ hai, dấu ngoặc nhọn và câu lệnh trả về cũng có thể được bỏ qua nếu bạn chỉ có một lệnh duy nhất trong mã phương thức.
Phiên bản rút gọn sẽ như thế này:
Comparator<String> comparator = (obj1, obj2) ->
obj1.length() – obj2.length();
Collections.sort(list, comparator);
Và nếu thay vì sử dụng comparator
biến, chúng tôi ngay lập tức sử dụng giá trị của nó, thì chúng tôi sẽ nhận được phiên bản sau:
Collections.sort(list, (obj1, obj2) -> obj1.length() — obj2.length() );
Vâng, bạn nghĩ gì về điều đó? Chỉ một dòng mã không có thông tin thừa — chỉ các biến và mã. Không có cách nào để làm cho nó ngắn hơn! Hay là có?
5. Cách thức hoạt động
Trên thực tế, mã có thể được viết gọn hơn nữa. Nhưng nhiều hơn về điều này sau.
Bạn có thể viết một biểu thức lambda trong đó bạn sẽ sử dụng một loại giao diện với một phương thức duy nhất.
Ví dụ: trong mã , bạn có thể viết biểu thức lambda vì chữ ký của phương thức giống như sau:Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());
sort()
sort(Collection<T> colls, Comparator<T> comp)
Khi chúng tôi chuyển ArrayList<String>
bộ sưu tập làm đối số đầu tiên cho phương thức sắp xếp, trình biên dịch có thể xác định rằng loại của đối số thứ hai là . Và từ đó, kết luận rằng giao diện này có một phương thức duy nhất. Mọi thứ khác là một kỹ thuật.Comparator<String>
int compare(String obj1, String obj2)
GO TO FULL VERSION