一.Lambda表達式
1.lambda表達式初試
- lambda表達式是返回了實現指定接口的對象實例
- 樣例代碼
public class Test {
public static void main(String [] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("ok");
}
}).start();
// 等同於
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("ok");
}
};
new Thread(runnable).start();
// 切換成Lambda表達式的寫法如下
new Thread(()-> System.out.println("okk")).start();
// 等同於
Runnable runnable1 = ()-> System.out.println("okk");
new Thread(runnable1).start();
}
}
2.lambda表達式的常見寫法
- 如果接口只存在一個抽象方法則可以使用lambda表達式編寫實現內容
- 入參的內容寫在lambda表達式的括號內,返回內容寫在箭頭的右側
- 實例代碼
interface Interface1 {
int doubleNum(int i);
}
public class LambdaDemo1 {
public static void main(String[] args) {
// 寫法1:常見寫法
Interface1 i1 = (i) -> i*2; // 如果是一個參數可以去掉括號, Interface1 i1 = i -> i*2;
// 寫法2:
Interface1 i2 = (int i) -> i*2;
// 寫法3:
Interface1 i3 = (int i) -> {
return i*2;
};
}
}
二.JDK8接口新特性
1.函數接口
@FunctionalInterface
,表示接口是函數接口,註解修飾的函數只存在一個需要實現的抽象方法(需要實現的方法只有一個,不是隻有一個方法,還可以有默認方法)- 實例代碼
@FunctionalInterface
interface Interface1 {
int doubleNum(int i);
}
2. 默認方法
- default關鍵字可以用來定義默認方法,在方法中給出默認實現
- 實例代碼
@FunctionalInterface
interface Interface1 {
int doubleNum(int i);
default int add(int x, int y) {
return x+y;
}
}
public class LambdaDemo1 {
public static void main(String[] args) {
Interface1 interfaceImpl = i -> i*3;
System.out.println(interfaceImpl.doubleNum(3)); // 輸出結果爲9,因爲上方實現了對應的方法
System.out.println(interfaceImpl.add(3, 4)); // 輸出結果爲7,因爲接口中有默認方法
}
}
- 如何接口繼承的其他接口存在同名的默認方法,編譯器會進行提示,要求重新實現對應的默認方法內容,實例如下
@FunctionalInterface
interface Interface1 {
int doubleNum(int i);
default int add(int x, int y) {
return x+y;
}
}
@FunctionalInterface
interface Interface2 {
int doubleNum(int i);
default int add(int x, int y) {
return x+y+1;
}
}
@FunctionalInterface
interface Interface3 extends Interface1, Interface2 { // 編譯器會提示要求重新實現add方法內容
@Override
default int add(int x, int y) {
return Interface1.super.add(x, y); // 相當於調用了Interface1的接口內容,當然我們也可以寫自己的實現
}
}
public class LambdaDemo1 {
public static void main(String[] args) {
Interface3 interfaceImpl = i -> i*3;
System.out.println(interfaceImpl.doubleNum(3)); // 輸出結果爲9
System.out.println(interfaceImpl.add(3, 4)); // 輸出結果爲7
}
}
三.JDK8中重要的函數接口
使用現成的函數接口的好處就是不用定義過多的接口,且函數接口可以支持鏈式操作
1.Function<T, K>
- 採用Function函數接口可以指定輸入及輸出類型的函數接口
- 實例代碼
import java.text.DecimalFormat;
import java.util.function.Function;
interface IMoneyFormat {
String format(int i);
}
class MyMoney {
private final int money;
public MyMoney(int money) {
this.money = money;
}
// public void printMoney(IMoneyFormat iMoneyFormat) {
// System.out.println("我的存款: "+ iMoneyFormat.format(this.money));
// }
// 此時由於我們的接口只需要知道輸入是什麼,輸出是什麼,故採用Function<T,K>函數接口即可
public void printMoney(Function<Integer, String> iMoneyFormat) {
System.out.println("我的存款: "+ iMoneyFormat.apply(this.money));
}
}
public class MoneyDemo {
public static void main(String[] args) {
MyMoney me = new MyMoney(9999);
// IMoneyFormat iMoneyFormat = (money)-> new DecimalFormat("#,###").format(money);
// me.printMoney(iMoneyFormat);
// 採用函數接口的方式實現如下
me.printMoney((money)-> new DecimalFormat("#,###").format(money));
// 輸出: 我的存款: 9,999
// 採用函數接口鏈式操作特性添加前綴實現如下
Function<Integer, String > moneyFormat = (money)-> new DecimalFormat("#,###").format(money);
me.printMoney(moneyFormat.andThen( s -> "人民幣 " + s));
// 輸出: 我的存款: 人民幣 9,999
}
}
2.常用函數接口
- 常用函數接口
接口 | 輸入類型 | 返回類型 | 說明 |
---|---|---|---|
Predicate | T | boolean | 斷言 |
Consumer | T | / | 消費一個數據 |
Function<T, R> | T | R | 輸入T輸出R的函數 |
Supplier | / | T | 提供一個數據 |
UnaryOperator | T | T | 一元函數(輸出輸入類型相同) |
BiFunction<T, U, R> | (T, U) | R | 2個輸入的函數 |
BinaryOperator | (T, T) | T | 二元函數(輸出輸入類型相同) |
- 對於基本數據類型的函數接口jdk也已經提供,比如
Predicate<Integer>
也可以寫成IntPredicate
- 實例代碼
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.IntPredicate;
import java.util.function.Predicate;
public class Test2 {
public static void main(String[] args) {
// 斷言函數: 用於判斷輸入是否大於0
Predicate<Integer> predicate = i -> i > 0;
System.out.println(predicate.test(-2)); // 輸出: false
// 對於基本數據類型,jdk有對應數據類型,上述內容等同如下
IntPredicate predicate1 = i -> i > 0;
// 消費函數
Consumer<String> customer = s -> System.out.println("輸入的內容是: " + s);
customer.accept("巴啦啦小魔仙"); // 輸出: 輸入的內容是: 巴啦啦小魔仙
// 二元函數
BinaryOperator<Integer> testAdd = (a, b) -> a + b;
System.out.println(testAdd.apply(2, 3)); // 輸出:5
}
}
3.方法引用
- 靜態方法的方法引用:
類名::方法名
- 非靜態方法的方法引用
對象實例::方法名
類名::方法名
- 注意第一個參數是對象實例
- 構造函數的方法引用
類名::new
- 實例代碼
import java.util.function.*;
class Dog {
private String name = "哮天犬";
public Dog() {
}
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
public static void bark(Dog dog) {
System.out.println(dog + "叫了");
}
public static void sleep(String name) {
System.out.println(name + "默默的睡覺...");
}
private int food = 10;
/**
* 喫狗糧
*
* @param num
* @return
*/
public int eat(int num) {
System.out.println("吃了"+ num + "斤狗糧");
this.food -= num;
return this.food;
}
}
public class MethodReferenceDemo {
public static void main(String[] args) {
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("使用函數接口"); // 輸出: 使用函數接口
// 如果內部方法的參數跟參數相同,則可以變成 方法引用 的方式進行書寫
Consumer<String> consumer1 = System.out::println;
consumer1.accept("使用方法引用"); // 輸出: 使用方法引用
// 靜態方法的方法引用: 使用類名::方法名
Consumer<Dog> dogConsumer = Dog::bark;
Dog dog = new Dog();
dogConsumer.accept(dog); // 輸出: Dog{name='哮天犬'}叫了
Consumer<String> simpleConsumer = Dog::sleep;
simpleConsumer.accept("旺財"); // 輸出: 旺財默默的睡覺...
// 非靜態方法
// 使用對象實例來引用
IntUnaryOperator function = dog::eat;
System.out.println("還剩下" + function.applyAsInt(2) + "斤"); // 輸出: 吃了2斤狗糧 還剩下8斤
// 使用類名來引用,費靜態方法其實默認第一個參數是當前實例this,故直接給定實例爲入參即可
BiFunction<Dog, Integer ,Integer> biFunction = Dog::eat;
System.out.println("還剩下"+ biFunction.apply(dog, 3)+"斤"); // 輸出: 吃了3斤狗糧 還剩下5斤
// 構造函數的方法引用, 構造函數的輸入是指定參數, 輸出是實例對象
// 方法名是new
// 無參構造函數如下
Supplier<Dog> supplier = Dog::new;
System.out.println("創建了一個新對象: " + supplier.get()); // 輸出: 創建了一個新對象: Dog{name='哮天犬'}
// 有參構造函數如下
Function<String, Dog> dogFunction = Dog::new;
System.out.println("創建了個一個來福對象: " + dogFunction.apply("來福")); // 輸出: 創建了個一個來福對象: Dog{name='來福'}
}
}
4.類型推斷
- 通常我們可以採用類型推斷自動匹配參數內容
- 實例代碼
@FunctionalInterface
interface IMath1 {
int add(int x, int y);
}
@FunctionalInterface
interface IMath2 {
int add(int x, int y);
}
public class Test3 {
public static void main(String[] args) {
// 變量定義
IMath1 lambda = (x, y) -> x+y;
// 數組中
IMath1[] lambdas = {(x, y) -> x+y};
// 強轉
Object lambda2 = (IMath1)(x,y) -> x+y;
// 通過方法返回類型
IMath1 createLambda = createLambda();
// 當有二義性的時候,需要使用強轉指定對應的接口
Test3 test3 = new Test3();
test3.test((IMath1) (x, y) -> x+y); // 如果轉換類型則會報錯
}
// 當有重載方法時,無法直接匹配
public void test(IMath1 iMath1) {
}
public void test(IMath2 iMath2) {
}
static IMath1 createLambda() {
return ((x, y) -> x+y);
}
}
5.級聯表達式及柯里化
- 柯里化:把多個參數的函數轉換爲只有一個參數的函數
- 柯里化的目的:函數標準化
- 高階函數:返回函數的函數
- 實例代碼
import java.util.function.Function;
public class Test4 {
public static void main(String[] args) {
// 實現了x+y的級聯表達式
Function<Integer, Function<Integer, Integer>> fun = x -> y -> x+y;
System.out.println(fun.apply(2).apply(3)); // 輸出5
Function<Integer, Function<Integer, Function<Integer, Integer>>> fun2 = x -> y -> z -> x+y+z;
System.out.println(fun2.apply(1).apply(2).apply(3));
}
}