JavaRush /Java Blog /Random-TW /Java 中的函數式介面

Java 中的函數式介面

在 Random-TW 群組發布
你好! 在 Java Syntax Pro 任務中,我們研究了 lambda 表達式,並表示它們只不過是函數式介面中函數式方法的實作。換句話說,這是某個匿名(未知)類別的實現,其未實現的方法。如果在課程講座中我們深入研究了 lambda 表達式的操作,那麼現在我們將考慮另一面:即這些介面。Java 的第八版引入了函數式介面Java 中的函數式介面 - 1的概念。這是什麼?具有一個未實現(抽象)方法的介面被認為是功能性的。許多開箱即用的介面都屬於這個定義,例如前面討論的介面。還有我們自己創建的接口,例如: Comparator
@FunctionalInterface
public interface Converter<T, N> {
   N convert(T t);
}
我們有一個接口,其任務是將一種類型的物件轉換為另一種類型的物件(一種適配器)。註解@FunctionalInterface並不是超級複雜或重要的東西,因為它的目的是告訴編譯器給定的介面是函數式的並且不應包含超過一個方法。如果帶有此註解的介面具有多個未實現(抽象)方法,則編譯器將不會跳過該接口,因為它會將其視為錯誤代碼。沒有這個註解的介面可以被認為是功能性的並且可以工作,但這 @FunctionalInterface無非是額外的保險。我們回去上課吧Comparator。如果你查看它的程式碼(或文件),你會發現它有不只一種方法。然後你會問:那麼,如何才能將其視為函數式介面呢?抽象介面可以具有不在單一方法範圍內的方法:
  • 靜止的
介面的概念意味著給定的程式碼單元不能實作任何方法。但從 Java 8 開始,可以在介面中使用靜態方法和預設方法。 靜態方法直接綁定到類,不需要該類的特定物件來呼叫此類方法。也就是說,這些方法和諧地融入了介面的概念。作為範例,讓我們向前一個類別新增一個用於檢查物件是否為 null 的靜態方法:
@FunctionalInterface
public interface Converter<T, N> {

   N convert(T t);

   static <T> boolean isNotNull(T t){
       return t != null;
   }
}
收到這個方法後,編譯器沒有抱怨,這意味著我們的介面仍然有效。
  • 預設方法
在Java 8之前,如果我們需要在介面中建立一個被其他類別繼承的方法,我們只能建立一個抽象方法,並在每個特定的類別中實作。但如果這個方法對所有類別都相同呢?在這種情況下,最常使用抽象類別。但從 Java 8 開始,可以選擇使用已實作方法的介面 - 預設方法。繼承介面時,您可以覆寫這些方法或保留所有內容(保留預設邏輯)。建立預設方法時,我們必須新增關鍵字 - 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());
   }
}
再次,我們看到編譯器沒有開始抱怨,我們也沒有超出函數式介面的限制。
  • 物件類別方法
在比較對象講座中,我們討論了所有類別都繼承自類別的事實Object。這不適用於接口。但是,如果我們在介面中有一個抽象方法,該方法的簽章與類別的某些方法相匹配Object,那麼這樣的方法(或多個方法)將不會打破我們的功能介面限制:
@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);
}
同樣,我們的編譯器不會抱怨,因此該介面Converter仍然被認為是函數式的。現在的問題是:為什麼我們需要將自己限制在函數式介面中的一個未實現的方法?然後我們就可以使用 lambda 來實現它。讓我們來看一個例子Converter。為此,我們創建一個類別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;
  }
}
還有一隻類似的Raccoon(浣熊):
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;
  }
}
假設我們有一個對象Dog,並且需要根據其欄位建立一個對象Raccoon。也就是說,Converter它將一種類型的物件轉換為另一種類型。它會是什麼樣子:
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);
}
當我們運行它時,我們會在控制台上得到以下輸出:

Raccoon has parameters: name - Bobbbie, age - 5, weight - 3
這意味著我們的方法工作正常。Java 中的函數式介面 - 2

基本 Java 8 函數式介面

好吧,現在讓我們來看看 Java 8 為我們帶來的幾個函數式接口,它們與 Stream API 結合使用。

謂詞

Predicate— 用於檢查是否符合特定條件的功能介面。如果滿足條件,則返回true,否則返回 - false
@FunctionalInterface
public interface Predicate<T> {
   boolean test(T t);
}
作為範例,考慮創建一個Predicate將檢查多種類型的奇偶校驗的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));
}
控制台輸出:

true
false

消費者

Consumer(來自英語 - “consumer”) - 一個函數接口,它將類型 T 的對像作為輸入參數,執行一些操作,但不返回任何內容:
@FunctionalInterface
public interface Consumer<T> {
   void accept(T t);
}
例如,考慮,其任務是使用傳遞的字串參數將問候語輸出到控制台: Consumer
public static void main(String[] args) {
   Consumer<String> greetings = x -> System.out.println("Hello " + x + " !!!");
   greetings.accept("Elena");
}
控制台輸出:

Hello Elena !!!

供應商

Supplier(來自英文 - 提供者) - 一個不接受任何參數但傳回 T 類型物件的函數式介面:
@FunctionalInterface
public interface Supplier<T> {
   T get();
}
例如,考慮Supplier,它將從列表中產生隨機名稱:
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());
}
如果我們運行它,我們將看到控制台中名稱列表的隨機結果。

功能

Function— 此函數介面接受參數 T 並將其轉換為類型 R 的對象,該物件作為結果返回:
@FunctionalInterface
public interface Function<T, R> {
   R apply(T t);
}
舉個例子,它將數字從字串格式 ( ) 轉換為數字格式 ( ): FunctionStringInteger
public static void main(String[] args) {
   Function<String, Integer> valueConverter = x -> Integer.valueOf(x);
   System.out.println(valueConverter.apply("678"));
}
當我們運行它時,我們會在控制台上得到以下輸出:

678
PS:如果我們不僅向字串中傳遞數字,還向字串中傳遞其他字符,則會拋出異常- NumberFormatException

一元運算符

UnaryOperator— 一個函數式接口,它接受類型 T 的對像作為參數,對其執行一些操作,並以相同類型 T 的對象的形式返回操作結果:
@FunctionalInterface
public interface UnaryOperator<T> {
   T apply(T t);
}
UnaryOperator,它使用它的方法apply來計算數字的平方:
public static void main(String[] args) {
   UnaryOperator<Integer> squareValue = x -> x * x;
   System.out.println(squareValue.apply(9));
}
控制台輸出:

81
我們研究了五個功能介面。從 Java 8 開始,這並不是我們可用的全部 - 這些是主要介面。其餘可用的都是其複雜的類似物。完整的清單可以在 Oracle 官方文件中找到。

Stream 中的函數接口

如上所述,這些功能介面與 Stream API 緊密耦合。你問如何?Java 中的函數式介面 - 3因此,許多方法Stream專門與這些功能介面一起工作。讓我們看看如何在Stream.

帶謂詞的方法

例如,讓我們採用類別方法Stream-filter它接受參數PredicateStream僅傳回滿足條件的元素Predicate。在 -a 的上下文中Stream,這意味著它僅傳遞在介面方法true中使用時傳回的那些元素。這就是我們的範例的樣子,但對於 中的元素過濾器: testPredicatePredicateStream
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());
}
因此,列表evenNumbers將包含元素 {2, 4, 6, 8}。而且,正如我們所記得的,collect它將所有元素收集到某個集合中:在我們的例子中,收集List

與消費者的方法

Stream中使用函數式介面的方法之一Consumerpeek. 這就是我們的Consumerin範例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());
}
控制台輸出:

Hello Elena !!!
Hello John !!!
Hello Alex !!!
Hello Jim !!!
Hello Sara !!!
但由於該方法peek適用於,因此不會發生Consumer字串的修改,而是會傳回原始元素:與它們到達時相同。因此,該清單將包含元素「Elena」、「John」、「Alex」、「Jim」、「Sara」。還有一種常用的方法,與方法類似,但不同的是它是final-terminal。StreampeekStreampeopleGreetingsforeachpeek

與供應商的方法

Stream使用函數式介面的方法的範例Suppliergenerate,它根據傳遞給它的函數式介面產生無限序列。讓我們使用我們的範例Supplier將五個隨機名稱列印到控制台:
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);
}
這是我們在控制台中得到的輸出:

John
Elena
Elena
Elena
Jim
這裡我們用 方法limit(5)對 method 進行了限制generate,否則程式會無限期地將隨機名稱列印到控制台。

方法與函數

Stream帶有參數的方法的一個典型範例Functionmap採用一種類型的元素、對它們執行某些操作並將它們傳遞出去的方法,但這些元素已經可以是不同類型的元素。Functionin的範例可能如下所示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());
}
結果,我們得到了一個數字列表,但是在Integer.

使用 UnaryOperator 的方法

作為用作參數的方法UnaryOperator,我們採用類別方法Stream- iterate。此方法與 方法類似generate:它也產生一個無限序列,但有兩個參數:
  • 第一個是序列產生開始的元素;
  • 第二個是UnaryOperator,表示從第一個元素產生新元素的原理。
這就是我們的範例的樣子UnaryOperator,但在方法中iterate
public static void main(String[] args) {
   Stream.iterate(9, x -> x * x)
           .limit(4)
           .forEach(System.out::println);
}
當我們運行它時,我們會在控制台上得到以下輸出:

9
81
6561
43046721
也就是說,我們的每個元素都與其自身相乘,對於前四個數字依此類推。Java 中的函數式介面 - 4就這樣!如果您在讀完這篇文章後,您距離理解和掌握 Java 中的 Stream API 又更近了一步,那就太好了!
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION