一、lambda表達式實例
提到lambda表達式,我們一般用的最多的就是foreach遍歷List、Set、Queue、Map等。
/**
* 容器forEache遍歷
*/
public static void forEach() {
//list
List<String> list = new ArrayList<>();
list.forEach(s -> System.out.println(s));
//set
Set<String> set = new HashSet<>();
set.forEach(s -> System.out.println(s));
//queue
Queue<String> queue = new PriorityQueue();
queue.forEach(s -> System.out.println(s));
//deque
Deque<String> deque = new LinkedList();
deque.forEach(s -> System.out.println(s));
//Map
Map<String, Object> map = new HashMap();
map.forEach((s, o) -> System.out.println(s + ":" + o));
}
當然,我們可以自己實現寫一個函數式接口,然後通過lambda表達式來編寫我們自己的代碼。
public static void main(String[] args) {
fooFun("測試打印效果",target -> System.out.println(target));
}
/**
* 隨意一個方法
* @param target 需要被答應的內容
* @param printer 打印機
*/
public static void fooFun(String target,FunctionInterface printer){
printer.print(target);
}
/**
* 函數式接口,@FunctionalInterface可加可不加,FunctionalInterface屬於target=Runtime
* ,編譯時已經被jvm處理過
*/
@FunctionalInterface
public interface FunctionInterface<T>{
/**
* 接口函數
* @param target 需要被打印的目標
*/
void print(T target);
}
分析:
1.定義一個函數式接口(對應的,public interface FunctionInterface<T>);
2.在自己定義的方法中,傳入這個已經定義好的函數式接口(fooFun(String target,FunctionInterface printer););
3在使用方法時,就能用lambda表達表達式了。(fooFun("測試打印效果",target -> System.out.println(target));)
二、什麼是lambda表達式
這裏演示了lambda表達式的基本使用,接下來我們來看看什麼是lambda表達式。
lambda表達式我們可以理解爲傳遞匿名函數的一種方式,這種表達式沒有方法名,有參數列表,有方法體,有返回值,可能有異常拋出(通常不推薦拋出異常,java默認實現也沒有拋出異常,只有自己實現時,纔會根據需要拋出),能夠當作參數帶入方法中。
總結起來分兩種:
(parameters) -> expression
(parameters) -> {statements;}
三、函數式接口、接口函數、靜態方法、默認方法、方法引用
上文提到了函數式接口,接口方法,接下來我們瞭解一下什麼是函數式接口,什麼是接口方法。
函數式接口就是隻定義了一個抽象方法的接口。
/**
* @author baopz
*/
@FunctionalInterface
public interface FunctionInterface {
@Override
int hashCode();
@Override
boolean equals(Object obj);
@Override
String toString();
/**
* 唯一的抽象方法
*/
void test();
/**
* 默認方法
*/
default void foo() {}
/**
* 靜態方法
*/
static void bar() {}
}
分析:
1.代碼中,接口Override了來自Object的三個方法hashCode()、equals()、toString()
2.唯一的抽象方法test(),用來在lambda表達式中唯一實現的方法,也就是接口函數
3.有個帶有方法體的,由default修飾的默認方法foo(),可以理解爲實現了該接口的子類,會順帶繼承的方法,默認方法
4.有個帶有方法體的,由static修飾的靜態方法bar(),可以理解爲工具方法,靜態方法。
方法引用,僅僅涉及到單一方法的使用時,提供的一種更加簡潔的寫法的語法糖。
四、方法引用
指向靜態方法的方法引用,Integer::parseInt
指向任意類型實例方法的引用,String::length
指向現有對象的實例方法的方法引用,methodReferences::length
構造函數、數組構造函數和父類調用(super-call)的一些特殊形式的方法引用
實例:
/**
* 方法引用事例
* @author baopz
*/
public class MethodReferences {
public static void main(String[] args) {
//靜態方法的方法引用
staticFun("123", Integer::parseInt);
//任意類型實例方法的引用
instanceFun("foo", String::length);
//構造函數
MethodReferences methodReferences = createInstance(MethodReferences::new);
//數組構造函數
MethodReferences[] methodRes = createArrayInstance(3, MethodReferences[]::new);
//現有對象的實例方法的方法引用
curInstanceFun("foo", methodReferences::length);
}
public static void staticFun(String str, Consumer<String> consumer) {
consumer.accept(str);
}
public static void instanceFun(String str, Consumer<String> consumer) {
consumer.accept(str);
}
public static MethodReferences createInstance(Supplier<MethodReferences> supplier) {
return supplier.get();
}
public static MethodReferences[] createArrayInstance(int length, Function<Integer, MethodReferences[]> function) {
return function.apply(length);
}
public static int curInstanceFun(String str, Function<String, Integer> function) {
return function.apply(str);
}
public int length(String str) {
return str.length();
}
}
五、java默認提供的幾類函數式接口
上文中使用了Function,Supplier,Consumer等接口,這些接口都來自,java1.8 新增的java.util.function包中,是java自帶的一些默認函數式接口。
```
Predicate<T> T->boolean
Function<T,R> T->R
Consumer<T> T-void
Supplier<T> void -> T
...
數值類型函數式接口
```
* Predicate<T>,謂詞,語義是傳入任意一個類型T,經過處理(Predicate的接口方法test()),返回一個boolean類型的值。
public static boolean isDog(Dog dog, Predicate<Dog> predicate){
return predicate.test(dog);
}
public static void main(String[] args) {
Dog.isDog(new Dog(),dog -> dog instanceof Dog);
}
* Function<T,R> ,方法,語義是傳入一個任意類型T,經過處理(Function 的接口方法apply()),返回一個R類型的值
public static void main(String[] args) {
//基礎lambda表達式
Bar.length("foo",s -> s.length());
//方法引用的寫法
Bar.length("foo",String::length);
}
public static int length(String foo, Function<String,Integer> function){
return function.apply(foo);
}
* Consumer<T>,消費者,語義是傳入一個任意類型的T,用方法處理(Consumer的接口方法accept()),返回一個void(就是沒有返回值)。
public static void main(String[] args) {
Dog.run(new Dog(),dog -> System.out.println("dog running."));
}
public static void run(Dog dog, Consumer<Dog> consumer){
consumer.accept(dog);
}
* Supplier<T>,提供者,語義是不用傳遞任何參數,直接通過方法(Supplier的接口方法get())產生一個T類型,並返回。
public static void main(String[] args) {
//lambda基本表達式
Dog dog = Dog.get(() -> new Dog());
//方法引用寫法
dog = Dog.get(Dog::new);
}
public static Dog get(Supplier<Dog> supplier){
return supplier.get();
}
數值類型函數式接口,我們都知道在java中對象類型分兩類,基礎類型和引用類型,得益於java1.5提供的類型自動裝箱,自動拆箱,用Integer、Double、Long等計算是拆箱爲int、double、long等來進行數值計算,但自動裝拆箱有效率損耗,所以lambda爲了消除這種效率損耗,提供了數值類型函數式接口,如IntFunction、DoublePredicate、LongConsumer等(還有很多)
public static void main(String[] args) {
add(10, 20, (left, right) -> left + right);
}
public static int add(int a, int b, IntBinaryOperator operator) {
return operator.applyAsInt(a, b);
}
這裏類型比較多,就不一一介紹,建議各位小夥伴如果要深入學習,建議把所有的類型都練習一遍。
由於類型比較多,類名不容易記住,java對這些類的命名有個小訣竅,
就是針對專門的輸入參數類型的函數式接口,
1.名稱都要加上對應的原始類型前綴(DoubleConsumer),
2.如果輸入參數有兩個的,都有Binary關鍵字(LongBinaryOperator),
3.如果沒有具體類型的兩個參數類型傳入,則是以Bi爲前綴(BiConsumer)
4.如果只知道返回類型的,都是以To爲前綴(ToIntFunction)
六、思考
java1.8的lambda內容講完了這裏給大家留個小問題,如果傳入的參數是三個或多個,該如何處理呢?