Xin chào! Trong nhiệm vụ Java Syntax Pro, chúng tôi đã nghiên cứu các biểu thức lambda và nói rằng chúng không gì khác hơn là việc triển khai một phương thức chức năng từ một giao diện chức năng. Nói cách khác, đây là việc triển khai một số lớp ẩn danh (không xác định), phương thức chưa được thực hiện của nó. Và nếu trong các bài giảng của khóa học, chúng ta đã đi sâu vào các thao tác với biểu thức lambda, thì bây giờ chúng ta sẽ xem xét, có thể nói, mặt khác: cụ thể là chính những giao diện này. Phiên bản thứ tám của Java đã giới thiệu khái niệm về giao diện chức năng . Cái này là cái gì? Một giao diện với một phương thức chưa được triển khai (trừu tượng) được coi là có chức năng. Nhiều giao diện dùng được ngay lập tức nằm trong định nghĩa này, chẳng hạn như giao diện đã thảo luận trước đó Thuộc tính
Người tiêu dùng
Nhà cung cấp
Chức năng
Toán tử đơn nhất
Comparator
. Và cả những giao diện do chúng tôi tự tạo ra, chẳng hạn như:
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
}
Chúng ta có một giao diện có nhiệm vụ chuyển đổi các đối tượng thuộc loại này thành các đối tượng thuộc loại khác (một loại bộ chuyển đổi). Chú thích @FunctionalInterface
không phải là thứ gì đó quá phức tạp hay quan trọng, vì mục đích của nó là báo cho trình biên dịch biết rằng giao diện này hoạt động bình thường và không được chứa nhiều hơn một phương thức. Nếu một giao diện có chú thích này có nhiều hơn một phương thức chưa được triển khai (trừu tượng), trình biên dịch sẽ không bỏ qua giao diện này vì nó sẽ coi đó là mã sai. Các giao diện không có chú thích này có thể được coi là có chức năng và sẽ hoạt động, nhưng @FunctionalInterface
điều này không hơn gì một sự bảo hiểm bổ sung. Chúng ta hãy quay trở lại lớp học Comparator
. Nếu bạn nhìn vào mã của nó (hoặc tài liệu ), bạn có thể thấy rằng nó có nhiều hơn một phương thức. Sau đó, bạn hỏi: làm thế nào nó có thể được coi là một giao diện chức năng? Giao diện trừu tượng có thể có các phương thức không nằm trong phạm vi của một phương thức duy nhất:
- tĩnh
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
}
Sau khi nhận được phương thức này, trình biên dịch không phàn nàn, điều đó có nghĩa là giao diện của chúng tôi vẫn hoạt động.
- phương pháp mặc định
default
:
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
default void writeToConsole(T t) {
System.out.println("Текущий an object - " + t.toString());
}
}
Một lần nữa, chúng ta thấy rằng trình biên dịch không bắt đầu phàn nàn và chúng ta không vượt quá giới hạn của giao diện chức năng.
- Phương thức lớp đối tượng
Object
. Điều này không áp dụng cho các giao diện. Nhưng nếu chúng ta có một phương thức trừu tượng trong giao diện khớp với chữ ký với một số phương thức của lớp Object
, thì phương thức (hoặc các phương thức) đó sẽ không phá vỡ hạn chế giao diện chức năng của chúng ta:
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
default void writeToConsole(T t) {
System.out.println("Текущий an object - " + t.toString());
}
boolean equals(Object obj);
}
Và một lần nữa, trình biên dịch của chúng tôi không phàn nàn, vì vậy giao diện Converter
vẫn được coi là có chức năng. Bây giờ câu hỏi là: tại sao chúng ta cần giới hạn bản thân ở một phương thức chưa được triển khai trong giao diện chức năng? Và sau đó chúng ta có thể triển khai nó bằng lambdas. Hãy xem xét điều này với một ví dụ Converter
. Để làm điều này, hãy tạo một lớp Dog
:
public class Dog {
String name;
int age;
int weight;
public Dog(final String name, final int age, final int weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
}
Và một con tương tự Raccoon
(gấu trúc):
public class Raccoon {
String name;
int age;
int weight;
public Raccoon(final String name, final int age, final int weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
}
Giả sử chúng ta có một đối tượng Dog
và chúng ta cần tạo một đối tượng dựa trên các trường của nó Raccoon
. Nghĩa là, Converter
nó chuyển đổi một đối tượng thuộc loại này sang loại khác. Nó sẽ trông như thế nào:
public static void main(String[] args) {
Dog dog = new Dog("Bobbie", 5, 3);
Converter<Dog, Raccoon> converter = x -> new Raccoon(x.name, x.age, x.weight);
Raccoon raccoon = converter.convert(dog);
System.out.println("Raccoon has parameters: name - " + raccoon.name + ", age - " + raccoon.age + ", weight - " + raccoon.weight);
}
Khi chúng tôi chạy nó, chúng tôi nhận được kết quả đầu ra sau cho bảng điều khiển:
Raccoon has parameters: name - Bobbbie, age - 5, weight - 3
Và điều này có nghĩa là phương pháp của chúng tôi đã hoạt động chính xác.
Giao diện chức năng Java 8 cơ bản
Chà, bây giờ chúng ta hãy xem xét một số giao diện chức năng mà Java 8 mang đến cho chúng ta và được sử dụng tích cực cùng với API Stream.Thuộc tính
Predicate
- một giao diện chức năng để kiểm tra xem một điều kiện nhất định có được đáp ứng hay không. Nếu điều kiện được đáp ứng, trả về true
, nếu không - false
:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Ví dụ: hãy xem xét việc tạo một Predicate
cái sẽ kiểm tra tính chẵn lẻ của một số loại Integer
:
public static void main(String[] args) {
Predicate<Integer> isEvenNumber = x -> x % 2==0;
System.out.println(isEvenNumber.test(4));
System.out.println(isEvenNumber.test(3));
}
Đầu ra của bảng điều khiển:
true
false
Người tiêu dùng
Consumer
(từ tiếng Anh - "người tiêu dùng") - một giao diện chức năng lấy đối tượng loại T làm đối số đầu vào, thực hiện một số hành động nhưng không trả về kết quả nào:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Ví dụ: hãy xem xét , nhiệm vụ của nó là xuất lời chào tới bảng điều khiển với đối số chuỗi được truyền: Consumer
public static void main(String[] args) {
Consumer<String> greetings = x -> System.out.println("Hello " + x + " !!!");
greetings.accept("Elena");
}
Đầu ra của bảng điều khiển:
Hello Elena !!!
Nhà cung cấp
Supplier
(từ tiếng Anh - nhà cung cấp) - một giao diện chức năng không nhận bất kỳ đối số nào mà trả về một đối tượng thuộc loại T:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Ví dụ, hãy xem xét Supplier
, nó sẽ tạo ra các tên ngẫu nhiên từ danh sách:
public static void main(String[] args) {
ArrayList<String> nameList = new ArrayList<>();
nameList .add("Elena");
nameList .add("John");
nameList .add("Alex");
nameList .add("Jim");
nameList .add("Sara");
Supplier<String> randomName = () -> {
int value = (int)(Math.random() * nameList.size());
return nameList.get(value);
};
System.out.println(randomName.get());
}
Và nếu chạy cái này, chúng ta sẽ thấy kết quả ngẫu nhiên từ danh sách các tên trong bảng điều khiển.
Chức năng
Function
— giao diện chức năng này lấy một đối số T và chuyển nó thành một đối tượng thuộc loại R, kết quả được trả về là:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Ví dụ: hãy lấy , chuyển đổi số từ định dạng chuỗi ( ) sang định dạng số ( ): Function
String
Integer
public static void main(String[] args) {
Function<String, Integer> valueConverter = x -> Integer.valueOf(x);
System.out.println(valueConverter.apply("678"));
}
Khi chúng tôi chạy nó, chúng tôi nhận được kết quả đầu ra sau cho bảng điều khiển:
678
Tái bút: nếu chúng ta chuyển không chỉ số mà còn chuyển cả các ký tự khác vào chuỗi, một ngoại lệ sẽ được đưa ra - NumberFormatException
.
Toán tử đơn nhất
UnaryOperator
- một giao diện chức năng lấy một đối tượng loại T làm tham số, thực hiện một số thao tác trên nó và trả về kết quả của các thao tác dưới dạng một đối tượng cùng loại T:
@FunctionalInterface
public interface UnaryOperator<T> {
T apply(T t);
}
UnaryOperator
, sử dụng phương thức của nó apply
để bình phương một số:
public static void main(String[] args) {
UnaryOperator<Integer> squareValue = x -> x * x;
System.out.println(squareValue.apply(9));
}
Đầu ra của bảng điều khiển:
81
Chúng tôi đã xem xét năm giao diện chức năng. Đây không phải là tất cả những gì chúng tôi có sẵn bắt đầu với Java 8 - đây là những giao diện chính. Phần còn lại của những cái có sẵn là những chất tương tự phức tạp của chúng. Danh sách đầy đủ có thể được tìm thấy trong tài liệu chính thức của Oracle .
Giao diện chức năng trong Stream
Như đã thảo luận ở trên, các giao diện chức năng này được kết hợp chặt chẽ với API Stream. Làm thế nào, bạn hỏi? Và nhiều phương phápStream
hoạt động cụ thể với các giao diện chức năng này. Hãy xem cách các giao diện chức năng có thể được sử dụng trong Stream
.
Phương thức với vị ngữ
Ví dụ: hãy lấy phương thức lớpStream
- filter
lấy làm đối số Predicate
và Stream
chỉ trả về những phần tử thỏa mãn điều kiện Predicate
. Trong ngữ cảnh của Stream
-a, điều này có nghĩa là nó chỉ đi qua những phần tử được trả về true
khi được sử dụng trong một phương thức test
giao diện Predicate
. Đây là ví dụ của chúng tôi cho Predicate
, nhưng đối với bộ lọc các phần tử trong Stream
:
public static void main(String[] args) {
List<Integer> evenNumbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8)
.filter(x -> x % 2==0)
.collect(Collectors.toList());
}
Kết quả danh sách evenNumbers
sẽ gồm các phần tử {2, 4, 6, 8}. Và, như chúng ta nhớ, collect
nó sẽ thu thập tất cả các phần tử vào một bộ sưu tập nhất định: trong trường hợp của chúng ta là vào List
.
Phương pháp với người tiêu dùng
Một trong những phương thứcStream
sử dụng giao diện chức năng Consumer
là peek
. Đây là ví dụ của chúng tôi cho Consumer
in sẽ trông như thế nào Stream
:
public static void main(String[] args) {
List<String> peopleGreetings = Stream.of("Elena", "John", "Alex", "Jim", "Sara")
.peek(x -> System.out.println("Hello " + x + " !!!"))
.collect(Collectors.toList());
}
Đầu ra của bảng điều khiển:
Hello Elena !!!
Hello John !!!
Hello Alex !!!
Hello Jim !!!
Hello Sara !!!
Nhưng vì phương thức này peek
hoạt động với Consumer
, nên việc sửa đổi các chuỗi trong Stream
sẽ không xảy ra mà peek
sẽ trả về Stream
các phần tử ban đầu: giống như những gì chúng đã thực hiện. Do đó, danh sách peopleGreetings
sẽ bao gồm các phần tử "Elena", "John", "Alex", "Jim", "Sara". Ngoài ra còn có một phương thức thường được sử dụng foreach
, tương tự như phương thức này peek
, nhưng điểm khác biệt là nó là phương thức cuối cùng.
Phương pháp với nhà cung cấp
Một ví dụ về phương thứcStream
sử dụng giao diện Supplier
hàm là generate
, tạo ra một chuỗi vô hạn dựa trên giao diện hàm được truyền cho nó. Hãy sử dụng ví dụ của chúng tôi Supplier
để in năm tên ngẫu nhiên vào bảng điều khiển:
public static void main(String[] args) {
ArrayList<String> nameList = new ArrayList<>();
nameList.add("Elena");
nameList.add("John");
nameList.add("Alex");
nameList.add("Jim");
nameList.add("Sara");
Stream.generate(() -> {
int value = (int) (Math.random() * nameList.size());
return nameList.get(value);
}).limit(5).forEach(System.out::println);
}
Và đây là kết quả chúng tôi nhận được trong bảng điều khiển:
John
Elena
Elena
Elena
Jim
Ở đây chúng tôi đã sử dụng phương thức này limit(5)
để đặt giới hạn cho phương thức generate
, nếu không chương trình sẽ in các tên ngẫu nhiên ra bảng điều khiển vô thời hạn.
Phương thức có hàm
Một ví dụ điển hình của phương thức cóStream
đối số Function
là phương thức map
lấy các phần tử thuộc một kiểu, thực hiện điều gì đó với chúng và chuyển chúng đi, nhưng đây có thể là các phần tử thuộc một kiểu khác. Một ví dụ với Function
in có thể trông như thế nào Stream
:
public static void main(String[] args) {
List<Integer> values = Stream.of("32", "43", "74", "54", "3")
.map(x -> Integer.valueOf(x)).collect(Collectors.toList());
}
Kết quả là chúng ta nhận được một danh sách các số, nhưng ở dạng Integer
.
Phương thức với UnaryOperator
Là một phương thức sử dụngUnaryOperator
làm đối số, hãy lấy một phương thức lớp Stream
- iterate
. Phương thức này tương tự như phương thức generate
: nó cũng tạo ra một chuỗi vô hạn nhưng có hai đối số:
- đầu tiên là phần tử bắt đầu tạo chuỗi;
- thứ hai là
UnaryOperator
, biểu thị nguyên tắc tạo ra các phần tử mới từ phần tử đầu tiên.
UnaryOperator
, nhưng trong phương thức iterate
:
public static void main(String[] args) {
Stream.iterate(9, x -> x * x)
.limit(4)
.forEach(System.out::println);
}
Khi chúng tôi chạy nó, chúng tôi nhận được kết quả đầu ra sau cho bảng điều khiển:
9
81
6561
43046721
Nghĩa là, mỗi phần tử của chúng ta được nhân với chính nó, v.v. cho bốn số đầu tiên. Đó là tất cả! Sẽ thật tuyệt nếu sau khi đọc bài viết này, bạn tiến thêm một bước nữa để hiểu và thành thạo API Stream trong Java!
GO TO FULL VERSION