你好! 在 Java Syntax Pro 任務中,我們研究了 lambda 表達式,並表示它們只不過是函數式介面中函數式方法的實作。換句話說,這是某個匿名(未知)類別的實現,其未實現的方法。如果在課程講座中我們深入研究了 lambda 表達式的操作,那麼現在我們將考慮另一面:即這些介面。Java 的第八版引入了函數式介面的概念。這是什麼?具有一個未實現(抽象)方法的介面被認為是功能性的。許多開箱即用的介面都屬於這個定義,例如前面討論的介面。還有我們自己創建的接口,例如: 謂詞
消費者
供應商
功能
一元運算符
Comparator
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
}
我們有一個接口,其任務是將一種類型的物件轉換為另一種類型的物件(一種適配器)。註解@FunctionalInterface
並不是超級複雜或重要的東西,因為它的目的是告訴編譯器給定的介面是函數式的並且不應包含超過一個方法。如果帶有此註解的介面具有多個未實現(抽象)方法,則編譯器將不會跳過該接口,因為它會將其視為錯誤代碼。沒有這個註解的介面可以被認為是功能性的並且可以工作,但這 @FunctionalInterface
無非是額外的保險。我們回去上課吧Comparator
。如果你查看它的程式碼(或文件),你會發現它有不只一種方法。然後你會問:那麼,如何才能將其視為函數式介面呢?抽象介面可以具有不在單一方法範圍內的方法:
- 靜止的
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
}
收到這個方法後,編譯器沒有抱怨,這意味著我們的介面仍然有效。
- 預設方法
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 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);
}
舉個例子,它將數字從字串格式 ( ) 轉換為數字格式 ( ): Function
String
Integer
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 緊密耦合。你問如何?因此,許多方法Stream
專門與這些功能介面一起工作。讓我們看看如何在Stream
.
帶謂詞的方法
例如,讓我們採用類別方法Stream
-filter
它接受參數Predicate
並Stream
僅傳回滿足條件的元素Predicate
。在 -a 的上下文中Stream
,這意味著它僅傳遞在介面方法true
中使用時傳回的那些元素。這就是我們的範例的樣子,但對於 中的元素過濾器: test
Predicate
Predicate
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());
}
因此,列表evenNumbers
將包含元素 {2, 4, 6, 8}。而且,正如我們所記得的,collect
它將所有元素收集到某個集合中:在我們的例子中,收集List
。
與消費者的方法
Stream
中使用函數式介面的方法之一Consumer
是peek
. 這就是我們的Consumer
in範例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。Stream
peek
Stream
peopleGreetings
foreach
peek
與供應商的方法
Stream
使用函數式介面的方法的範例Supplier
是generate
,它根據傳遞給它的函數式介面產生無限序列。讓我們使用我們的範例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
帶有參數的方法的一個典型範例Function
是map
採用一種類型的元素、對它們執行某些操作並將它們傳遞出去的方法,但這些元素已經可以是不同類型的元素。Function
in的範例可能如下所示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 中的 Stream API 又更近了一步,那就太好了!
GO TO FULL VERSION