一、函數(shù)式接口
函數(shù)式接口的定義:
函數(shù)式接口(Functional Interface)就是一個(gè)有且僅有一個(gè)抽象方法,但是可以有多個(gè)非抽象方法的接口。
函數(shù)式接口可以被隱式轉(zhuǎn)換為 lambda 表達(dá)式。
//如下就是一個(gè)函數(shù)式的接口
interface MathOperation {
int operation(int a, int b);
}
函數(shù)式接口本身并沒有過多的價(jià)值,主要是配合Lambda表達(dá)式進(jìn)行使用。如果非函數(shù)式接口那么就不能使用Lambda表達(dá)式,還是得回歸之前的匿名函數(shù)。
@FunctionalInterface的妙用,如果不確定自己的接口是不是函數(shù)式接口,可以在接口上加上,如果不是編輯會(huì)報(bào)錯(cuò)的。

二、Lambda表達(dá)式
Lambda允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞進(jìn)方法中)。
這種寫法有一定的局限性,就是必須是函數(shù)式接口,當(dāng)然好處就是語法非常的簡(jiǎn)潔明了。
//如下就是一個(gè)函數(shù)式的接口
interface MathOperation {
int operation(int a, int b);
}
public static int operate(int a, int b, MathOperation mathOperation) {
return mathOperation.operation(a, b);
}
public static void main(String[] args) {
/**
* java 8之前的使用姿勢(shì),結(jié)果為15
*/
System.out.println(operate(5, 10, new MathOperation() {
@Override
public int operation(int a, int b) {
return a + b;
}
}));
/**
* java 8的使用姿勢(shì),結(jié)果都為15
* 1:Lambda表達(dá)式會(huì)自動(dòng)推斷類型,所以類型聲明可以省略
* 2:當(dāng)Lambda表達(dá)式右側(cè)只有一個(gè)簡(jiǎn)單語句,例如a+b,那么可以自動(dòng)推斷出返回值,不用加return
* 3:如果處理邏輯比較復(fù)雜,那么需要加上{}和return
*/
System.out.println(operate(5, 10, (int a, int b) -> a + b));
System.out.println(operate(5, 10, (int a, int b) -> { return a + b; }));
System.out.println(operate(5, 10, (a, b) -> a + b));
}
lambda 表達(dá)式內(nèi)部的變量作用域其實(shí)跟匿名函數(shù)也是一樣的,如果lambda表達(dá)式訪問局部變量或者成員變量都是需要使用final來修飾,當(dāng)然lambda的語法并沒有那么嚴(yán)格,可以不使用final進(jìn)行修飾,但是要保證lambda表達(dá)式不能對(duì)變量進(jìn)行修改。
int variable = 22;
final int variable1 = 22;
//這樣都是合法的lambda表達(dá)式
System.out.println(operate(5,10,(a,b) -> a+b+variable));
System.out.println(operate(5,10,(a,b) -> a+b+variable1));
//這樣子就會(huì)報(bào)錯(cuò)
System.out.println(operate(5,10,(a,b) -> {
variable = 222;
}));
至于為什么lambda表達(dá)式或者說匿名函數(shù)會(huì)這樣要求,其實(shí)原因也很簡(jiǎn)單,究其根本就是生命周期和值拷貝。
成員變量(非static)一旦類被實(shí)例化就會(huì)存在堆內(nèi)存當(dāng)中,生命周期跟隨實(shí)例創(chuàng)建而創(chuàng)建,跟隨實(shí)例銷毀而銷毀。
局部變量,存在棧當(dāng)中,方法調(diào)用而產(chǎn)生,調(diào)用完成就釋放。
lambda表達(dá)式/匿名函數(shù)其實(shí)是跟外部類同級(jí)的,如果使用了局部變量,局部變量在方法調(diào)用完成就釋放了,但是lambda表達(dá)式還存在,這就比較尷尬了,所以java的做法是將局部變量的值copy一份使用,如果是引用型變量,copy的就是地址,既然是值copy,為了能夠局部變量使用效果一樣,自然是不允許修改值的,不然調(diào)用就會(huì)產(chǎn)生不同的效果。
三、方法引用
方法引用也是java 8推出的一個(gè)特性,可以使用::來指向?qū)?yīng)方法,在介紹方法引用之前先介紹一下java 8推出的新的函數(shù)式接口。
//Consumer<T>其實(shí)可以理解為就是一個(gè)消費(fèi)者,需要傳遞一個(gè)Class<T>給Consumer,然后可以自己實(shí)現(xiàn)accept的操作
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
//Supplier<T>其實(shí)可以理解成一個(gè)容器,用于存放Class<T>,然后可以自定義get的操作
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
//在java 8之前可以使用匿名函數(shù)這么玩
Consumer<Integer> consumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
};
consumer.accept(10);
Supplier<Integer> supplier = new Supplier<Integer>() {
@Override
public Integer get() {
return Integer.MAX_VALUE;
}
};
System.out.println(supplier.get());
理解了上述兩個(gè)函數(shù)式接口的主要用處,接下來可以結(jié)合著方法引用來使用。先定義一個(gè)測(cè)試類,里面有靜態(tài)方法和非靜態(tài)方法。
package com.cainiao.wmp.service;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @author huayi.zh
* @date 2020/09/14
*/
public class Java8Test {
static class Car {
//Supplier是jdk1.8的接口,這里和lambda一起使用了
public static Car create(final Supplier<Car> supplier) {
return supplier.get();
}
public static void collide(final Car car) {
System.out.println("Collided " + car.toString());
}
public void follow(final Car another) {
System.out.println("Following the " + another.toString());
}
public void repair() {
System.out.println("Repaired " + this.toString());
}
}
public static void main(String[] args) {
/**
* 使用方法1:引用創(chuàng)建新實(shí)例,可以發(fā)現(xiàn)Supplier<Car>起到了裝配Car實(shí)例的作用
*/
Supplier<Car> supplier = Car::new;
Car car = Car.create(supplier);
/**
* 使用方法2:借助forEach方法,用::調(diào)用靜態(tài)函數(shù),其實(shí)Car::collide產(chǎn)生一個(gè)Consumer<Car>,需要接收一個(gè)Car實(shí)例,
* 自然就可以使用forEach方法了,可以將Car::collide看做是對(duì)Consumer<Car>接口的一個(gè)實(shí)現(xiàn),具體內(nèi)容就是接收一個(gè)
* Car實(shí)例,然后執(zhí)行collide方法。
*/
List<Car> cars = Arrays.asList(car);
// Consumer<Car> collide = Car::collide;
// cars.forEach(collide);
cars.forEach(Car::collide);
/**
* 使用方法3:借助forEach方法,用::調(diào)用普通函數(shù),分為兩種情況,一種是Car類,一種是Car的實(shí)例
* cars.forEach(Car::repair);可以執(zhí)行,但是cars.forEach(Car::follow);卻不行
*/
Consumer<Car> repair = Car::repair;
BiConsumer<Car, Car> follow = Car::follow;
/**
* 原因很明顯了,follow方法參數(shù)需要提供一個(gè)Car實(shí)例,本身又是非靜態(tài)函數(shù),需要用實(shí)例才能調(diào)用,需要兩個(gè)Car實(shí)例,
* 但是repair是空參函數(shù),所以只需要一個(gè),forEach每次循環(huán)只能提供一個(gè)Car實(shí)例
*/
/**
* 換一種寫法,使用Car的實(shí)例,然后加上::也是調(diào)用的
*/
cars.forEach(car::follow);
}
}
四、默認(rèn)方法
Java 8 新增了接口的默認(rèn)方法。
簡(jiǎn)單說,默認(rèn)方法就是接口可以有實(shí)現(xiàn)方法,而且不需要實(shí)現(xiàn)類去實(shí)現(xiàn)其方法。
我們只需在方法名前面加個(gè) default 關(guān)鍵字即可實(shí)現(xiàn)默認(rèn)方法。
為什么要有這個(gè)特性?
首先,之前的接口是個(gè)雙刃劍,好處是面向抽象而不是面向具體編程,缺陷是,當(dāng)需要修改接口時(shí)候,需要修改全部實(shí)現(xiàn)該接口的類,目前的 java 8 之前的集合框架沒有 foreach 方法,通常能想到的解決辦法是在JDK里給相關(guān)的接口添加新的方法及實(shí)現(xiàn)。然而,對(duì)于已經(jīng)發(fā)布的版本,是沒法在給接口添加新方法的同時(shí)不影響已有的實(shí)現(xiàn)。所以引進(jìn)的默認(rèn)方法。他們的目的是為了解決接口的修改與現(xiàn)有的實(shí)現(xiàn)不兼容的問題。
//默認(rèn)方法語法格式如下:
public interface Vehicle {
default void print(){
System.out.println("我是一輛車!");
}
}
java都是單繼承,多實(shí)現(xiàn)的,目的就是為了避免多個(gè)繼承,存在沖突實(shí)現(xiàn)的方法,那么現(xiàn)在在接口上增加了默認(rèn)方法,也會(huì)存在同樣的問題,java 8是這樣處理的。
//第一個(gè)解決方案是創(chuàng)建自己的默認(rèn)方法,來覆蓋重寫接口的默認(rèn)方法:
public class Car implements Vehicle, FourWheeler {
default void print(){
System.out.println("我是一輛四輪汽車!");
}
}
//第二種解決方案可以使用 super 來調(diào)用指定接口的默認(rèn)方法:
public class Car implements Vehicle, FourWheeler {
public void print(){
Vehicle.super.print();
}
}
五、Stream
Java 8 API添加了一個(gè)新的抽象稱為流Stream,可以讓你以一種聲明的方式處理數(shù)據(jù)。
Stream API可以極大提高Java程序員的生產(chǎn)力,讓程序員寫出高效率、干凈、簡(jiǎn)潔的代碼。
在 Java 8 中, 集合接口有兩個(gè)方法來生成流:
- stream() ? 為集合創(chuàng)建串行流。
- parallelStream() ? 為集合創(chuàng)建并行流。
常見算子有:
forEach:可以迭代流中的每個(gè)數(shù)據(jù)
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
//forEach可以迭代流中每個(gè)元素,具體做什么操作,可以使用lambda表達(dá)式來實(shí)現(xiàn)。
strings.stream().forEach(s-> System.out.println(s));
//等同于以下實(shí)現(xiàn),只不過使用lambda表達(dá)式會(huì)使代碼更加的簡(jiǎn)潔緊湊。
strings.stream().forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//forEach的源碼,可以發(fā)現(xiàn)參數(shù)一個(gè)Consumer<? super T> action,跟之前的方法引用可以對(duì)上,那是不是也可以用方法引用來實(shí)現(xiàn)?
//答案當(dāng)然是可以的,說明lambda表達(dá)式其實(shí)就是一種特殊的方法引用,
void forEach(Consumer<? super T> action);
//如下來實(shí)現(xiàn)
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
strings.stream().forEach(System.out::println);
map:****用于映射每個(gè)元素到對(duì)應(yīng)的結(jié)果
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
strings.stream().map(s -> "\"" + s + "\"").forEach(s -> System.out.println(s));
filter:根據(jù)規(guī)則過濾流中每個(gè)元素
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
strings.stream().filter(s->s != "").forEach(s-> System.out.println(s));
limit:控制獲取指定數(shù)量的流
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
strings.stream().limit(1).forEach(s-> System.out.println(s));
sorted:對(duì)流中每個(gè)元素進(jìn)行排序,自定義類需要自己實(shí)現(xiàn)compare方法
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
strings.stream().sorted((a,b)->a.compareTo(b)).forEach(s-> System.out.println(s));
Collectors:Collectors 類實(shí)現(xiàn)了很多歸約操作,例如將流轉(zhuǎn)換成集合和聚合元素。
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
String collect = strings.stream().collect(Collectors.joining(","));
List<String> collect1 = strings.stream().collect(Collectors.toList());
六、Optional類
Optional 類是一個(gè)可以為null的容器對(duì)象。如果值存在則isPresent()方法會(huì)返回true,調(diào)用get()方法會(huì)返回該對(duì)象。
Optional 是個(gè)容器:它可以保存類型T的值,或者僅僅保存null。Optional提供很多有用的方法,這樣我們就不用顯式進(jìn)行空值檢測(cè)。
Optional 類的引入很好的解決空指針異常。
常用方法:
1:static <T> Optional<T> empty()
返回空的 Optional 實(shí)例。
Optional<Integer> v11 = Optional.empty();
2:****static <T> Optional<T> ofNullable(T value)
如果為非空,返回 Optional 描述的指定值,否則返回空的 Optional。
Integer v1 = new Integer(10);
Integer v2 = null;
//ofNullable可以傳入null,返回一個(gè)空的Optional<Integer>,使用get()會(huì)拋出異常
Optional<Integer> v11 = Optional.ofNullable(v1);
System.out.println(v11.get());
3:****static <T> Optional<T> of(T value)
返回一個(gè)指定非****null****值的****Optional****。
Integer v1 = new Integer(10);
Integer v2 = null;
//of不允許傳入null,會(huì)拋出異常
Optional<Integer> v11 = Optional.of(v2);
4:****boolean equals(Object obj)
判斷其他對(duì)象是否等于 Optional****。
Integer v1 = new Integer(10);
Integer v2 = null;
Optional<Integer> v11 = Optional.ofNullable(v1);
Optional<Integer> v21 = Optional.ofNullable(v1);
boolean equals = v11.equals(v21);
//true
System.out.println(equals);
5:****T get()
如果在這個(gè)Optional中包含這個(gè)值,返回值,否則拋出異常:NoSuchElementException
Integer v1 = new Integer(10);
Integer v2 = null;
Optional<Integer> v11 = Optional.ofNullable(v1);
System.out.println(v11.get());//10
6:****boolean isPresent()
如果在這個(gè)Optional中包含這個(gè)值,返回值,否則拋出異常:NoSuchElementException
Integer v1 = new Integer(10);
Integer v2 = null;
Optional<Integer> v11 = Optional.ofNullable(v1);
System.out.println(v11.isPresent());//true
Optional<Integer> v21 = Optional.ofNullable(v2);
System.out.println(v21.isPresent());//false
7:T orElse(T other)
如果存在該值,返回值, 否則返回 other****。
Integer v1 = new Integer(10);
Integer v2 = null;
Optional<Integer> v11 = Optional.ofNullable(v1);
System.out.println(v11.orElse(20));//10
Optional<Integer> v21 = Optional.ofNullable(v2);
System.out.println(v21.orElse(20));//20