JavaRush /Blog Java /Random-VI /Giao diện chức năng trong Java

Giao diện chức năng trong Java

Xuất bản trong nhóm
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. Giao diện chức năng trong Java - 1Phiê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 đó 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 @FunctionalInterfacekhô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
Khái niệm về giao diện ngụ ý rằng một đơn vị mã nhất định không thể triển khai bất kỳ phương thức nào. Nhưng bắt đầu với Java 8, người ta có thể sử dụng các phương thức tĩnh và mặc định trong giao diện. Các phương thức tĩnh được liên kết trực tiếp với một lớp và không yêu cầu một đối tượng cụ thể của lớp đó gọi một phương thức như vậy. Nghĩa là, những phương pháp này phù hợp một cách hài hòa với khái niệm về giao diện. Ví dụ: hãy thêm một phương thức tĩnh để kiểm tra một đối tượng null vào lớp trước:
@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
Trước Java 8, nếu chúng ta cần tạo một phương thức trong giao diện được các lớp khác kế thừa, chúng ta chỉ có thể tạo một phương thức trừu tượng được triển khai trong từng lớp cụ thể. Nhưng nếu phương pháp này giống nhau cho tất cả các lớp thì sao? Trong trường hợp này , các lớp trừu tượng thường được sử dụng nhất . Nhưng bắt đầu với Java 8, có một tùy chọn để sử dụng giao diện với các phương thức được triển khai - các phương thức mặc định. Khi kế thừa một giao diện, bạn có thể ghi đè các phương thức này hoặc giữ nguyên mọi thứ (để nguyên logic mặc định). Khi tạo một phương thức mặc định, chúng ta phải thêm từ khóa - 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
Trong bài So sánh các đối tượng , chúng ta đã nói về thực tế là tất cả các lớp đều kế thừa từ lớp 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 Convertervẫ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 Dogvà 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à, Converternó 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 trong Java - 2

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 Predicatecá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ố ( ): FunctionStringInteger
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? Giao diện chức năng trong Java - 3Và nhiều phương pháp Streamhoạ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ớp Stream- filterlấy làm đối số PredicateStreamchỉ 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ề truekhi được sử dụng trong một phương thức testgiao 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 evenNumberssẽ gồm các phần tử {2, 4, 6, 8}. Và, như chúng ta nhớ, collectnó 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ức Streamsử dụng giao diện chức năng Consumerpeek. Đây là ví dụ của chúng tôi cho Consumerin 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 peekhoạt động với Consumer, nên việc sửa đổi các chuỗi trong Streamsẽ không xảy ra mà peeksẽ trả về Streamcác phần tử ban đầu: giống như những gì chúng đã thực hiện. Do đó, danh sách peopleGreetingssẽ 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ức Streamsử dụng giao diện Supplierhà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ố Functionlà phương thức maplấ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 Functionin 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ụng UnaryOperatorlà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.
Ví dụ của chúng ta sẽ trông như thế này 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. Giao diện chức năng trong Java - 4Đó 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!
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION