目錄
· 行為參數化
· 概況
· 函數式接口
· 類型推斷
· 使用外層變量
· 方法引用
行為參數化
1. 理解函數式編程要先理解行為參數化。
2. 行為參數化:一個方法接受多個不同的行為作為參數,並在內部使用它們,完成不同行為的能力。
3. 行為參數化優點:可讓代碼更好地適應不斷變化的需求,減輕未來的工作量。
4. 實現方式
a) Java 8以前:通過接口實現類或接口匿名類實現。
b) Java 8及以后:通過Lambda表達式實現。
5. 舉例
a) 通過接口實現類實現行為參數化
i. Apple.java(后續舉例將多次使用到該類)
1 public class Apple { 2 3 private Integer weight; 4 5 private String color; 6 7 public Apple(Integer weight, String color) { 8 this.weight = weight; 9 this.color = color; 10 } 11 12 public Integer getWeight() { 13 return weight; 14 } 15 16 public void setWeight(Integer weight) { 17 this.weight = weight; 18 } 19 20 public String getColor() { 21 return color; 22 } 23 24 public void setColor(String color) { 25 this.color = color; 26 } 27 28 @Override 29 public String toString() { 30 return "weight=" + weight + " color=" + color; 31 } 32 }
ii. ApplePredicate.java
1 public interface ApplePredicate { 2 3 boolean test(Apple apple); 4 5 }
iii. AppleHeavyWeightPredicate.java
1 public class AppleHeavyWeightPredicate implements ApplePredicate { 2 3 public boolean test(Apple apple) { 4 return apple.getWeight() > 150; 5 } 6 7 }
iv. AppleGreenColorPredicate.java
1 public class AppleGreenColorPredicate implements ApplePredicate { 2 3 public boolean test(Apple apple) { 4 return "green".equals(apple.getColor()); 5 } 6 7 }
v. Test.java
1 import java.util.ArrayList; 2 import java.util.Arrays; 3 import java.util.List; 4 5 public class Test { 6 7 public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) { 8 List<Apple> result = new ArrayList<>(); 9 for (Apple apple : inventory) { 10 if (p.test(apple)) { 11 result.add(apple); 12 } 13 } 14 return result; 15 } 16 17 public static void printApples(List<Apple> inventory) { 18 for (Apple apple : inventory) { 19 System.out.println(apple); 20 } 21 } 22 23 public static void main(String[] args) { 24 List<Apple> inventory = Arrays.asList( 25 new Apple(100, "red"), 26 new Apple(110, "red"), 27 new Apple(190, "red"), 28 new Apple(170, "red"), 29 new Apple(100, "green"), 30 new Apple(120, "green"), 31 new Apple(160, "green"), 32 new Apple(180, "green") 33 ); 34 List<Apple> newInventory1 = filterApples(inventory, new AppleHeavyWeightPredicate()); 35 printApples(newInventory1); 36 System.out.println("-----"); 37 List<Apple> newInventory2 = filterApples(inventory, new AppleGreenColorPredicate()); 38 printApples(newInventory2); 39 } 40 41 }
b) 通過接口匿名類實現行為參數化
i. ApplePredicate.java
1 public interface ApplePredicate { 2 3 boolean test(Apple apple); 4 5 }
ii. Test.java
1 import java.util.ArrayList; 2 import java.util.Arrays; 3 import java.util.List; 4 5 public class Test { 6 7 public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) { 8 List<Apple> result = new ArrayList<>(); 9 for (Apple apple : inventory) { 10 if (p.test(apple)) { 11 result.add(apple); 12 } 13 } 14 return result; 15 } 16 17 public static void printApples(List<Apple> inventory) { 18 for (Apple apple : inventory) { 19 System.out.println(apple); 20 } 21 } 22 23 public static void main(String[] args) { 24 List<Apple> inventory = Arrays.asList( 25 new Apple(100, "red"), 26 new Apple(110, "red"), 27 new Apple(190, "red"), 28 new Apple(170, "red"), 29 new Apple(100, "green"), 30 new Apple(120, "green"), 31 new Apple(160, "green"), 32 new Apple(180, "green") 33 ); 34 List<Apple> newInventory1 = filterApples(inventory, new ApplePredicate() { 35 @Override 36 public boolean test(Apple apple) { 37 return apple.getWeight() > 150; 38 } 39 }); 40 printApples(newInventory1); 41 System.out.println("-----"); 42 List<Apple> newInventory2 = filterApples(inventory, new ApplePredicate() { 43 @Override 44 public boolean test(Apple apple) { 45 return "green".equals(apple.getColor()); 46 } 47 }); 48 printApples(newInventory2); 49 } 50 51 }
Lambda表達式
概況
1. Lambda表達式:可把Lambda表達式看作只有一個方法的接口匿名類,即沒有聲明名稱的方法,也可以作為參數傳遞給另一個方法。
2. Lambda表達式特點
a) 匿名:不像普通的方法那樣有一個明確的名稱。
b) 函數:不像方法那樣屬於某個特定的類。但和方法一樣,Lambda有參數列表、函數主體、返回類型,還可能有可以拋出的異常列表。
c) 傳遞:可以作為參數傳遞給方法或存儲在變量中。
d) 簡潔:無需像匿名類那樣寫很多模板代碼。
3. Lambda表達式語法
a) 語法格式1
(parameters) -> expression
b) 語法格式2
(parameters) -> { statements; }
c) 舉例
場景 |
Lambda表達式 |
布爾表達式 |
(List<String> list) -> list.isEmpty() |
創建對象 |
() -> new Apple(10, "red") |
消費一個對象 |
(Apple a) -> { System.out.println(a.getWeight()); } |
從一個對象中選擇/抽取 |
(String s) -> s.length() |
組合兩個值 |
(int a, int b) -> a * b |
比較兩個對象 |
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()) |
4.Lambda表達式使用條件:只能在函數式接口上使用。
5.函數式接口(Functional Interface):只定義一個抽象方法的接口(注意不包括默認方法)。
6.舉例:通過Lambda表達式實現行為參數化
a) ApplePredicate.java
1 public interface ApplePredicate { 2 3 boolean test(Apple apple); 4 5 }
b) Test.java
1 import java.util.ArrayList; 2 import java.util.Arrays; 3 import java.util.List; 4 5 public class Test { 6 7 public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) { 8 List<Apple> result = new ArrayList<>(); 9 for (Apple apple : inventory) { 10 if (p.test(apple)) { 11 result.add(apple); 12 } 13 } 14 return result; 15 } 16 17 public static void printApples(List<Apple> inventory) { 18 for (Apple apple : inventory) { 19 System.out.println(apple); 20 } 21 } 22 23 public static void main(String[] args) { 24 List<Apple> inventory = Arrays.asList( 25 new Apple(100, "red"), 26 new Apple(110, "red"), 27 new Apple(190, "red"), 28 new Apple(170, "red"), 29 new Apple(100, "green"), 30 new Apple(120, "green"), 31 new Apple(160, "green"), 32 new Apple(180, "green") 33 ); 34 List<Apple> newInventory1 = filterApples(inventory, (Apple apple) -> apple.getWeight() > 150); 35 printApples(newInventory1); 36 System.out.println("-----"); 37 List<Apple> newInventory2 = filterApples(inventory, (apple) -> "green".equals(apple.getColor())); 38 printApples(newInventory2); 39 } 40 41 }
函數式接口
1. Java 8自帶的函數式接口都在java.util.function包下。
2. 異常:Java 8自帶函數式接口都不允許拋出Checked Exception。如果需要Lambda表達式來拋出異常,要么定義一個自己的函數式接口,並聲明Checked Exception,要么把Lambda包在一個try/catch塊中。
3. 裝箱操作(Boxing):為了避免裝箱操作帶來的開銷問題,不應使用Predicate<T>或Function<T, R>等通用函數式接口,而應使用IntPredicate、IntToLongFunction等原始類型特化接口。
4. Java 8常用函數式接口
函數式接口 |
函數描述符 |
原始類型特化 |
Predicate<T> |
T->boolean |
IntPredicate LongPredicate DoublePredicate |
Consumer<T> |
T->void |
IntConsumer LongConsumer DoubleConsumer |
Function<T,R> |
T->R |
IntFunction<R> IntToDoubleFunction IntToLongFunction LongFunction<R> LongToDoubleFunction LongToIntFunction DoubleFunction<R> ToIntFunction<T> ToDoubleFunction<T> ToLongFunction<T> |
Supplier<T> |
()->T |
BooleanSupplier IntSupplier LongSupplier DoubleSupplier |
UnaryOperator<T> |
T->T |
IntUnaryOperator LongUnaryOperator DoubleUnaryOperator |
BinaryOperator<T> |
(T,T)->T |
IntBinaryOperator LongBinaryOperator DoubleBinaryOperator |
BiPredicate<L, R> |
(L, R) -> boolean |
|
BiConsumer<T, U> |
(T, U) -> void |
ObjIntConsumer<T> ObjLongConsumer<T> ObjDoubleConsumer<T> |
BiFunction<T, U, R> |
(T, U) -> R |
ToIntBiFunction<T, U> ToLongBiFunction<T, U> ToDoubleBiFunction<T, U> |
5. 舉例
a) Lambda表達式與函數式接口對應
場景 |
Lambda表達式 |
對應的函數式接口 |
布爾表達式 |
(List<String> list) -> list.isEmpty() |
Predicate<List<String>> |
創建對象 |
() -> new Apple(10, "red") |
Supplier<Apple> |
消費一個對象 |
(Apple a) -> { System.out.println(a.getWeight()); } |
Consumer<Apple> |
從一個對象中選擇/抽取 |
(String s) -> s.length() |
Function<String, Integer>或 ToIntFunction<String> |
組合兩個值 |
(int a, int b) -> a * b |
IntBinaryOperator |
比較兩個值 |
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()) |
Comparator<Apple> 或 BiFunction<Apple, Apple, Integer> 或 ToIntBiFunction<Apple, Apple> |
b) Predicate<T>
1 import java.util.ArrayList; 2 import java.util.Arrays; 3 import java.util.List; 4 import java.util.function.Predicate; 5 6 public class Test { 7 8 public static <T> List<T> filter(List<T> list, Predicate<T> p) { 9 List<T> results = new ArrayList<>(); 10 for (T s : list) { 11 if (p.test(s)) { 12 results.add(s); 13 } 14 } 15 return results; 16 } 17 18 public static void main(String[] args) { 19 List<String> listOfStrings = Arrays.asList("", "A", "B", "", "C"); 20 Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty(); 21 List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate); 22 for (String string : nonEmpty) { 23 System.out.println(string); 24 } 25 } 26 27 }
c) Consumer<T>
1 import java.util.Arrays; 2 import java.util.List; 3 import java.util.function.Consumer; 4 5 public class Test { 6 7 public static <T> void forEach(List<T> list, Consumer<T> c){ 8 for(T i: list){ 9 c.accept(i); 10 } 11 } 12 13 public static void main(String[] args) { 14 List<Integer> listOfNumbers = Arrays.asList(1, 2, 3, 4, 5); 15 forEach(listOfNumbers, (Integer i) -> System.out.println(i)); 16 } 17 18 }
d) Function<T, R>
1 import java.util.ArrayList; 2 import java.util.Arrays; 3 import java.util.List; 4 import java.util.function.Function; 5 6 public class Test { 7 8 public static <T, R> List<R> map(List<T> list, Function<T, R> f) { 9 List<R> result = new ArrayList<>(); 10 for(T s: list){ 11 result.add(f.apply(s)); 12 } 13 return result; 14 } 15 16 public static void main(String[] args) { 17 List<String> listOfStrings = Arrays.asList("lambdas", "in", "action"); 18 List<Integer> l = map(listOfStrings, (String s) -> s.length()); 19 for (Integer i : l) { 20 System.out.println(i); 21 } 22 } 23 24 }
類型推斷
1. 類型推斷:同一個Lambda表達式就可賦予不同的函數式接口,只要它們的抽象方法簽名能夠兼容。
1 Callable<Integer> c = () -> 42; 2 PrivilegedAction<Integer> p = () -> 42;
1 Comparator<Apple> c1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); 2 ToIntBiFunction<Apple, Apple> c2 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); 3 BiFunction<Apple, Apple, Integer> c3 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
2. void兼容規則:如果一個Lambda的主體是一個語句表達式, 它就和一個返回void的函數描述符兼容(當然需要參數列表也兼容)。例如,雖然List.add方法返回boolean,但Consumer<T>的T->void仍然兼容。
Consumer<String> b = s -> list.add(s);
3. Object類:Object不是函數式接口。下面的代碼無法編譯。
Object o = () -> {System.out.println("Tricky example"); };
4. Lambda表達式類型省略
a) 參數類型省略:省略和不省略都可能更易讀。
1 // 沒有類型推斷 2 Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); 3 // 有類型推斷 4 Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
b) 參數括號省略:當Lambda僅有一個類型需要推斷的參數時,參數名稱兩邊的括號也可以省略。
List<Apple> greenApples = filter(inventory, a -> "green".equals(a.getColor()));
使用外層變量
1. 外層變量限制:Lambda表達式只能使用顯式聲明為final或實際上是final的外層變量。與匿名類類似,但匿名類更嚴格(只能使用顯式聲明為final的外層變量)。
2. 限制原因
a) Lambda表達式在訪問外層變量時,實際上是在訪問它的副本,而不是訪問原始變量。
b) 函數式編程不鼓勵使用外層變量(更容易並行)。
3. 舉例
a) 可正常運行
1 int number = 100; 2 Runnable r = () -> System.out.println(number); 3 new Thread(r).start();
b) 運行報錯“local variables referenced from a lambda expression must be final or effectively final”
1 int number = 100; 2 Runnable r = () -> System.out.println(number); 3 new Thread(r).start(); 4 number = 200;
方法引用
1. 方法引用:可以重復使用現有的方法定義,並像Lambda一樣傳遞。
2. 方法引用優點:有時比Lambda表達式可讀性更好。
3. 方法引用的種類
a) 指向靜態方法的方法引用,例如Integer.parseInt()方法,寫作Integer::parseInt。
b) 指向任意類型實例方法的方法引用,例如String.length()方法,寫作String::length。
c) 指向現有對象的實例方法的方法引用,例如有一個局部變量apple有getWeight()實例方法,apple::getWeight。
d) 指向構造函數的方法引用,例如Date的構造方法,寫作Date::new。
e) 針對構造函數、數組構造函數和父類調用(super-call)的一些特殊形式的方法引用。
4. 舉例
a) Lambda表達式與方法引用對應
Lambda表達式 |
對應的方法引用 |
(Apple a) -> a.getWeight() |
Apple::getWeight |
() -> Thread.currentThread().dumpStack() |
Thread.currentThread()::dumpStack |
(str, i) -> str.substring(i) |
String::substring |
(String s) -> System.out.println(s) |
System.out::println |
() -> new Date() |
Date::new |
b) 指向現有對象的實例方法的方法引用
1 import java.util.Arrays; 2 import java.util.List; 3 4 import static java.util.Comparator.comparing; 5 6 public class Test { 7 8 public static void printApples(List<Apple> inventory) { 9 for (Apple apple : inventory) { 10 System.out.println(apple); 11 } 12 } 13 14 public static void main(String[] args) { 15 List<Apple> inventory = Arrays.asList( 16 new Apple(100, "red"), 17 new Apple(110, "red"), 18 new Apple(190, "red"), 19 new Apple(170, "red"), 20 new Apple(100, "green"), 21 new Apple(120, "green"), 22 new Apple(160, "green"), 23 new Apple(180, "green") 24 ); 25 // i.e. inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())); 26 // i.e. inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight())); 27 inventory.sort(comparing(Apple::getWeight)); 28 printApples(inventory); 29 } 30 31 }
c) 指向構造函數的方法引用
1 import java.util.Date; 2 import java.util.function.Function; 3 import java.util.function.Supplier; 4 5 interface TriFunction<T, U, V, R>{ 6 R apply(T t, U u, V v); 7 } 8 9 public class Test { 10 11 public static void main(String[] args) { 12 Supplier<Date> s = Date::new; // i.e. () -> new Date() 13 Date d1 = s.get(); 14 System.out.println(d1); 15 16 Function<Long, Date> f = Date::new; // i.e. (Long l) -> new Date(l) 17 Date d2 = f.apply(0L); 18 System.out.println(d2); 19 20 TriFunction<Integer, Integer, Integer, Date> tf = Date::new; 21 Date d3 = tf.apply(2000, 1, 1); 22 System.out.println(d3); 23 } 24 25 }
復合Lambda表達式
1. 復合Lambda表達式:把多個簡單的Lambda表達式復合成復雜的表達式,比如使用and、or復合。
2. 舉例
a) Comparator復合
1 import java.util.Arrays; 2 import java.util.List; 3 4 import static java.util.Comparator.comparing; 5 6 public class Test { 7 8 public static void printApples(List<Apple> inventory) { 9 for (Apple apple : inventory) { 10 System.out.println(apple); 11 } 12 } 13 14 public static void main(String[] args) { 15 List<Apple> inventory = Arrays.asList( 16 new Apple(100, "red"), 17 new Apple(110, "red"), 18 new Apple(190, "red"), 19 new Apple(170, "red"), 20 new Apple(100, "green"), 21 new Apple(120, "green"), 22 new Apple(160, "green"), 23 new Apple(180, "green") 24 ); 25 inventory.sort( 26 comparing(Apple::getWeight) 27 .reversed() 28 .thenComparing(Apple::getColor) 29 ); 30 printApples(inventory); 31 } 32 33 }
b) Predicate復合
1 import java.util.ArrayList; 2 import java.util.Arrays; 3 import java.util.List; 4 import java.util.function.Predicate; 5 6 public class Test { 7 8 public static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p) { 9 List<Apple> result = new ArrayList<>(); 10 for (Apple apple : inventory) { 11 if (p.test(apple)) { 12 result.add(apple); 13 } 14 } 15 return result; 16 } 17 18 public static void printApples(List<Apple> inventory) { 19 for (Apple apple : inventory) { 20 System.out.println(apple); 21 } 22 } 23 24 public static void main(String[] args) { 25 List<Apple> inventory = Arrays.asList( 26 new Apple(100, "red"), 27 new Apple(110, "red"), 28 new Apple(190, "red"), 29 new Apple(170, "red"), 30 new Apple(100, "green"), 31 new Apple(120, "green"), 32 new Apple(160, "green"), 33 new Apple(180, "green") 34 ); 35 Predicate<Apple> p = a -> "red".equals(a.getColor()); 36 p = p.negate() 37 .and(a -> a.getWeight() > 150) 38 .or(a -> a.getWeight() <= 110); 39 List<Apple> newInventory = filterApples(inventory, p); 40 printApples(newInventory); 41 } 42 43 }
c) 函數復合
1 import java.util.function.Function; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 Function<Integer, Integer> f = x -> x + 1; 7 Function<Integer, Integer> g = x -> x * 2; 8 9 Function<Integer, Integer> h1 = f.andThen(g); // i.e. g(f(x)) 10 int result1 = h1.apply(1); 11 System.out.println(result1); // 4 12 13 Function<Integer, Integer> h2 = f.compose(g); // i.e. f(g(x)) 14 int result2 = h2.apply(1); 15 System.out.println(result2); // 3 16 } 17 18 }
作者:netoxi
出處:http://www.cnblogs.com/netoxi
本文版權歸作者和博客園共有,歡迎轉載,未經同意須保留此段聲明,且在文章頁面明顯位置給出原文連接。歡迎指正與交流。