本文目錄
第八章 函數式編程
1. 函數式接口簡介
1.1 概念
有且只有一個抽象方法的接口,叫函數式接口。
因爲Java中函數式編程的體現就是Lambda,所以函數式接口就是可以用於lambda使用的接口。
接口中只能有一個抽象方法,可以有其他類型的方法,比如普通私有方法、靜態私有方法、默認方法等等。
語法糖:
指的是形式上使用更加簡便,但是底層原理不變的代碼語法。比如for-each寫起來簡潔,底層其實還是迭代器。
從應用層面來講,可以說lambda是匿名內部類的語法糖,但是底層原理並不同。
匿名內部類會在編譯時生成一個class文件,而lambda表達式不會,所以會降低開銷。
1.2 格式
函數式接口格式:
package FunctionInterface;
public interface MyInterface {
void method();
}
1.3 @FunctionalInterface
註解
加上這個註解後,接口就強制只能有一個抽象方法。如果多寫了一個抽象方法或者沒抽象方法,編譯會失敗。
package FunctionInterface;
@FunctionalInterface
public interface MyInterface {
void method();
}
1.4 lambda 的小例子
package FunctionInterface;
public class Demo {
//參數使用函數式接口
public static void fun(MyInterface mi) {
mi.method();
}
public static void main(String[] args) {
//調用fun方法,可以傳遞接口的實現類方法或者匿名內部類。
fun(new MyInterface() {
@Override
public void method() {
System.out.println("匿名內部類重寫接口的抽象方法。");
}
});
//方法的參數是一個函數式接口,所以可以傳遞進去lambda表達式
fun(
() -> System.out.println("lambda表達式重寫接口的抽象方法。")
);
}
}
//===============輸出===============//
匿名內部類重寫接口的抽象方法。
lambda表達式重寫接口的抽象方法。
2. 函數式編程
2.1 lambda的延遲執行
有的場景代碼執行完畢後,結果不一定會被使用,從而造成性能浪費。而lambda是延遲執行你的,所以可以提升性能。
2.1.1 性能浪費的日誌案例
日誌記錄代碼如下:
package FunctionInterface.logDemo;
public class Logger {
public static void showLog(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
public static void main(String[] args) {
int level = 2;
String s1 = "a";
String s2 = "b";
String s3 = "c";
showLog(level, s1 + s2 + s3);
}
}
代碼的問題:
代碼中,如果level
不是1,字符串的拼接就是無用的,會浪費性能。
使用lambda來優化:
lambda的特點是延遲加載,所以先定義一個函數式接口。
lambda僅僅是傳參數進去,如果level
不是1
,就不會調用接口去拼接字符串。
package FunctionInterface.logDemo;
public class Logger {
//傳遞函數式接口
public static void showLog(int level, LogBuilderInterface logBuilderInterface) {
//判斷日誌等級
if (level == 1) {
System.out.println(logBuilderInterface.buildMsg());
}
}
public static void main(String[] args) {
int level = 1;
String s1 = "a";
String s2 = "b";
String s3 = "c";
showLog(level,
() -> s1 + s2 + s3
);
}
}
2.2 函數式接口的使用
2.2.1作爲方法的參數。
java.lang.Runnable
是一個函數式接口,假設有一個startThread
方法使用Runnable
接口作爲參數,就可以使用lambda進行傳參,效果等同與new Thread(//lambda表達式).start();
package FunctionInterface.InterAsParams;
public class Demo {
public static void startThread(Runnable runnable) {
new Thread(runnable).start();
}
public static void main(String[] args) {
//使用Thread的構造方法啓動線程。
new Thread(
() -> System.out.println(Thread.currentThread().getName() + "線程啓動!")
).start();
//startThread的參數是一個接口,可以使用這個接口的匿名內部類。
startThread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "線程啓動!");
}
});
//Runnable接口是函數式接口,作爲參數,使用lambda表達式啓動線程。
startThread(
() -> System.out.println(Thread.currentThread().getName() + "線程啓動!")
);
}
}
//===============輸出===============//
Thread-1線程啓動!
Thread-2線程啓動!
Thread-0線程啓動!
2.2.2 作爲方法的返回值
最常見的應用就是比較器java.util.Comparator
。
如果一個方法的返回值類型是一個接口,那麼就可以返回一個lambda表達式。
package FunctionInterface.InterAsReturn;
import java.util.Comparator;
public class Demo {
public static Comparator<Integer> CompareInt(){
return ((o1, o2) -> o1-o2);
}
}
3. 常用的函數式接口
java.util.function
包
3.1 Supplier接口
被稱爲生產型接口,指定的泛型是什麼,接口的get方法就生產什麼類型的數據。
package FunctionInterface;
import java.util.function.Supplier;
public class SupplierDemo {
public static String getMsg(Supplier<String> sup){
return sup.get();
}
public static void main(String[] args) {
System.out.println(getMsg(()->"a"+"b"));
}
}
應用:求數組最大值。
package FunctionInterface;
import java.util.function.Supplier;
public class SupplierDemo {
public static Integer getMax(Supplier<Integer> sup) {
return sup.get();
}
public static void main(String[] args) {
int[] ints = {1, 2, 3, 4, 5};
System.out.println(getMax(
() -> {
int max = ints[0];
for (int i : ints) {
max = Math.max(i, max);
}
return max;
}));
}
}
3.2 Consumer接口
消費一個數據,類型由泛型決定。
void accept(T t)
方法。
具體怎麼使用,需要自定義。
package FunctionInterface;
import java.util.function.Consumer;
public class ConsumerDemo {
public static void fun(String str, Consumer<String> con) {
con.accept(str);
}
public static void main(String[] args) {
String s = "abc";
fun(s,
(str) -> System.out.println("傳遞進來的字符串是:" + str)
);
}
}
消費者接口的另一種寫法:方法引用。
fun(s, System.out::println);
Consumer
接口有一個默認方法:andThen
。
需要兩個Consumer
接口,可以把兩個Consumer
接口組合在一起。
誰寫在前面誰先消費。
例子:定義一個方法,傳進去一個字符串和兩個 Consumer
接口。
-
要麼調用兩次
accept
方法。 -
要麼使用
andThen
方法。
package FunctionInterface.logDemo;
import java.util.function.Consumer;
public class ConsumerAndThenDemo {
public static void fun(String str, Consumer<String> consumer1, Consumer<String> consumer2) {
// consumer1.accept(str);
// consumer2.accept(str);
consumer1.andThen(consumer2).accept(str);
}
public static void main(String[] args) {
String s = "Hello World!";
fun(s,
(str) -> System.out.println(str.toLowerCase()),
(str) -> System.out.println(str.toUpperCase())
);
}
}
練習:格式化打印信息。
package FunctionInterface.logDemo;
import java.util.ArrayList;
import java.util.function.Consumer;
public class ConsumerAndThenDemo {
public static void printInfo(Consumer<String> c1, Consumer<String> c2, String[] array) {
for (String str : array) {
c1.andThen(c2).accept(str);
}
}
public static void main(String[] args) {
String[] strings = {"張三,22", "李四,21"};
printInfo(
(s -> System.out.println("姓名:" + s.split(",")[0])),
(s -> System.out.println("年齡:" + s.split(",")[1])),
strings
);
}
}
//===============輸出===============//
姓名:張三
年齡:22
姓名:李四
年齡:21
3.3 Predicate
接口
3.3.1 接口的使用
對某種類型的數據進行判斷,返回一個boolean
值。boolean test(T t)
package FunctionInterface;
import java.util.function.Predicate;
public class PredicateDemo {
public static boolean fun(String s, Predicate<String> predicate) {
return predicate.test(s);
}
public static void main(String[] args) {
String s = "abc";
boolean b = fun(s,
(o) -> o.contains("A")
);
System.out.println("字符串中含有A嗎?:" + b);
}
}
3.3.2 接口中的三個默認方法
and
方法。
傳入兩個Predicate
接口。
package FunctionInterface.PredicateDemo;
import java.util.function.Predicate;
public class PredicateAndDemo {
public static boolean check(String str, Predicate<String> p1, Predicate<String> p2) {
return p1.and(p2).test(str);
}
public static void main(String[] args) {
String s = "Abc";
boolean b = check(s,
(o -> s.contains("A")),
(o -> s.length() > 5)
);
System.out.println("字符串中既有A,長度又大於5嗎?:" + b);
}
}
or
方法。
return p1.or(p2).test(str);
negate
非操作
public static boolean check(String str, Predicate<String> p) {
return p.negate().test(str);
}
3.4 Function接口
轉換類型的接口。把一個類型的數據轉換爲另一個類型。
Function<T, R>
,把T類型轉換爲R類型。
package FunctionInterface.FunctionDemo;
import java.util.function.Function;
public class Demo {
public static void fun(String s, Function<String, Integer> fun) {
System.out.println(fun.apply(s));
}
public static void main(String[] args) {
String s = "12";
fun(s,
(str) -> Integer.parseInt(str)
);
//方法引用
fun(s,
Integer::parseInt
);
}
}
Function
接口的默認方法:andThen
。用來進行組合操作。
示例:將傳進來的字符串轉成數字,加上10,再轉爲字符串返回。
package FunctionInterface.FunctionDemo;
import java.util.function.Function;
public class andThenDemo {
public static String fun(String str, Function<String, Integer> f1, Function<Integer, String> f2) {
return f1.andThen(f2).apply(str);
}
public static void main(String[] args) {
String s1 = "123";
String s2 = fun(s1,
(s -> Integer.parseInt(s) + 10),
(i -> String.valueOf(i))
);
System.out.println(s2);
}
}
(i -> String.valueOf(i))
這行代碼可以使用方法引用改寫爲:
(String::valueOf)