1 函數式接口java.util.function
https://www.cnblogs.com/CobwebSong/p/9593313.html
2 JAVA8的java.util.function包
https://www.cnblogs.com/linzhanfly/p/9686941.html
一 概述
name | type | description |
---|---|---|
Consumer | Consumer< T > | 接收T對象,不返回值 |
Predicate | Predicate< T > | 接收T對象並返回boolean |
Function | Function< T, R > | 接收T對象,返回R對象 |
Supplier | Supplier< T > | 提供T對象(例如工廠),不接收值 |
UnaryOperator | UnaryOperator< T > | 接收T對象,返回T對象 |
BiConsumer | BiConsumer<T, U> | 接收T對象和U對象,不返回值 |
BiPredicate | BiPredicate<T, U> | 接收T對象和U對象,返回boolean |
BiFunction | BiFunction<T, U, R> | 接收T對象和U對象,返回R對象 |
BinaryOperator | BinaryOperator< T > | 接收兩個T對象,返回T對象 |
參考:https://blog.csdn.net/huo065000/article/details/78964382
二 Consumer
1 作用
- 消費某個對象
2 使用場景
- Iterable接口的forEach方法需要傳入Consumer,大部分集合類都實現了該接口,用於返回Iterator對象進行迭代。
3 設計思想
- 開發者調用ArrayList.forEach時,一般希望自定義遍歷的消費邏輯,比如:輸出日志或者運算處理等。
- 處理邏輯留給使用者,使用靈活多變。
- 多變的邏輯能夠封裝成一個類(實現Consumer接口),將邏輯提取出來。
PASS:Javascript能夠將函數傳遞給另一個函數,這應該算是函數式編程的一個體現,java的function包中的類也是類似的。
public interface Iterable<T> { default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } }
4 DEMO
public class ConsumerTest { public static void main(String[] args) { ArrayList<Employee> employees = new ArrayList<>(); String[] prefix = {"A", "B"}; for (int i = 1; i <= 10; i++) employees.add(new Employee(prefix[i % 2] + i, i * 1000)); employees.forEach(new SalaryConsumer()); employees.forEach(new NameConsumer()); } static class Employee { private String name; private int salary; public Employee() { this.salary = 4000; } public Employee(String name, int salary) { this.name = name; this.salary = salary; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } @Override public String toString() { return new StringBuilder() .append("name:").append(name) .append(",salary:").append(salary) .toString(); } } // 輸出需要交稅的員工 static class SalaryConsumer implements Consumer<Employee> { @Override public void accept(Employee employee) { if (employee.getSalary() > 2000) { System.out.println(employee.getName() + "要交稅了."); } } } // 輸出需要名字前綴是‘A’的員工信息 static class NameConsumer implements Consumer<Employee> { @Override public void accept(Employee employee) { if (employee.getName().startsWith("A")) { System.out.println(employee.getName() + " salary is " + employee.getSalary()); } } } }
三 Predicate
1 作用
- 判斷對象是否符合某個條件
2 使用場景
ArrayList的removeIf(Predicate):刪除符合條件的元素
如果條件硬編碼在ArrayList中,它將提供無數的實現,但是如果讓調用者傳入條件,這樣ArrayList就可以從復雜和無法猜測的業務中解放出來。
3 設計思想
- 提取條件,讓條件從處理邏輯脫離出來,解耦合
4 DEMO
// employee.getSalary() > 2000 提取成一個條件類 class SalaryConsumer implements Consumer<Employee> { @Override public void accept(Employee employee) { // 自行傳入本地的最低交稅工資 if (new SalaryPredicate(2000).test(employee)) { System.out.println(employee.getName() + "要交稅了."); } } } class SalaryPredicate implements Predicate<Employee>{ private int tax; public SalaryPredicate(int tax) { this.tax = tax; } @Override public boolean test(Employee employee) { return employee.getSalary() > tax; } }
三 Function
1 作用
- 實現一個”一元函數“,即傳入一個值經過函數的計算返回另一個值。
2 使用場景
- V HashMap.computeIfAbsent(K , Function<K, V>) // 簡化代碼,如果指定的鍵尚未與值關聯或與null關聯,使用函數返回值替換。
- <R> Stream<R> map(Function<? super T, ? extends R> mapper); // 轉換流
3 設計思想
- 一元函數的思想,將轉換邏輯提取出來,解耦合
4 DEMO
public static void main(String[] args) { ArrayList<Employee> employees = new ArrayList<>(); String[] prefix = {"B", "A"}; for (int i = 1; i <= 10; i++) employees.add(new Employee(prefix[i % 2] + i, i * 1000)); int[] expenses = ListToArray(employees, new EmployeeToExpenses());// 公司對單個員工的支出數組 int[] incomes = ListToArray(employees, new EmployeeToIncome()); // 單個員工的收入數組 System.out.println("社保+公積金+稅=" + (sum(expenses) - sum(incomes)) + "元"); } private static int[] ListToArray(List<Employee> list, Function<Employee, Integer> function) { int[] ints = new int[list.size()]; for (int i = 0; i < ints.length; i++) ints[i] = function.apply(list.get(i)); return ints; } private static int sum(int[] salarys) { int sum = 0; for (int i = 0; i < salarys.length; i++) sum += salarys[i]; return sum; } // 公司支出 static class EmployeeToExpenses implements Function<Employee, Integer> { @Override public Integer apply(Employee employee) { // 假設公司公積金和社保為工資的20% return Double.valueOf(employee.getSalary() * (1 + 0.2)).intValue(); } } // 員工實際到手工資 static class EmployeeToIncome implements Function<Employee, Integer> { @Override public Integer apply(Employee employee) { // 假設員工薪水 * 80% 為到手工資 return Double.valueOf(employee.getSalary() * (1 - 0.2)).intValue(); } }
四 Supplier
1 作用
- 創建一個對象(工廠類)
2 使用場景
- Optional.orElseGet(Supplier<? extends T>):當this對象為null,就通過傳入supplier創建一個T返回。
3 設計思想
- 封裝工廠創建對象的邏輯
4 DEMO
public static void main(String[] args) { // 生成固定工資的員工 Supplier<Employee> supplier = () -> new Employee(); Employee employee1 = supplier.get(); employee1.setName("test1"); Employee employee2 = supplier.get(); employee2.setName("test2"); System.out.println("employee1:" + employee1); System.out.println("employee2:" + employee2); }
五 UnaryOperator
1 作用
- UnaryOperator繼承了Function,與Function作用相同
- 不過UnaryOperator,限定了傳入類型和返回類型必需相同
2 使用場景
- List.replaceAll(UnaryOperator) // 該列表的所有元素替換為運算結算元素
- Stream.iterate(T,UnaryOperator) // 重復對seed調用UnaryOperator來生成元素
3 設計思想
- 一元函數的思想,將同類轉換邏輯提取出來,解耦合
4 DEMO
public static void main(String[] args) { ArrayList<Employee> employees = new ArrayList<>(); String[] prefix = {"B", "A"}; for (int i = 1; i <= 10; i++) employees.add(new Employee(prefix[i % 2] + i, i * 1000)); System.o ut.println("公司進行薪資調整..."); salaryAdjustment(employees,new SalaryAdjustment(4000)); employees.forEach(System.out::println); } static void salaryAdjustment(List<Employee> list, UnaryOperator<Employee> operator) { for (int i = 0; i < list.size(); i++) { list.set(i, operator.apply(list.get(i))); } } static class SalaryAdjustment implements UnaryOperator<Employee> { private int salary; public SalaryAdjustment(int salary) { this.salary = salary; } @Override public Employee apply(Employee employee) { employee.setSalary(salary); return employee; } }
______________________________________________________________________________________
Java函數式編程
函數式編程
從JDK1.8開始為了簡化使用者進行代碼的開發,專門提供有lambda表達式的支持,利用此操作形式可以實現函數式的編程,對於函數編程比較著名的語言是:haskell、Scala,利用函數式的編程可以避免掉面向對象編程過程中的一些繁瑣的問題。
面向對象在其長期發展的過程中一直有一部分的反對者,這些反對者認為面向對象的設計過於復雜繁瑣,以一個最簡單的程序為例:
范例:觀察傳統開發中的問題
1 interface IMessage{ 2 public void send(String str); 3 } 4 public class Main { 5 6 public static void main(String[] args) { 7 IMessage msg=new IMessage() { 8 @Override 9 public void send(String str) { 10 System.out.println("消息發送:"+str); 11 } 12 }; 13 msg.send("hello"); 14 } 15 }
1,Lamda表達式在這個程序中,實際上核心的功能只有一行語句【System.out.println("消息發送:"+str);】,但是為了這樣一行核心語句我們需要按照完整的面向對象給出的設計結構進行開發。於是這些問題隨着技術的不斷發展也是越來越突出。
范例:使用Lambda表達式實現上面相同的功能
1 interface IMessage{ 2 public void send(String str); 3 } 4 public class Main { 5 6 public static void main(String[] args) { 7 IMessage msg=(str)->{ 8 System.out.println("消息發送:"+str);}; 9 msg.send("hello"); 10 } 11 }
Lambda表達式如果想要使用,那么必須有一個重要的實現要求:SAM(Single Abstract Method),只有一個抽象方法,在這個接口里面只是提供一個send()方法,除此之外沒有任何其他方法定義,所以這樣的接口就被定義為函數式接口,而只有函數式接口才能被Lambda表達式所使用。現在整個程序代碼里面會發現真的只是編寫了一行語句,於是利用這種形式就避免了復雜的面向對象結構化的要求。
范例:函數式接口里面的方法只能有一個
1 @FunctionalInterface//函數式接口 2 interface IMessage{//JDK1.8之后接口里面可以定義普通方法和Static方法 3 public void send(String str); 4 public default void print() {//這是一個 5 System.out.println("公共方法"); 6 } 7 } 8 public class Main { 9 10 public static void main(String[] args) { 11 IMessage msg=(str)->{ 12 System.out.println("消息發送:"+str);}; 13 msg.send("hello"); 14 msg.print(); 15 } 16 }
①方法沒有參數:()->{}對於Lambda表達式而言,需要提供有如下幾種格式:
②方法有參數:(參數,參數)->{}
③如果現在只有一行語句返回:(參數,參數)->語句;
范例:定義沒有參數的方法
1 @FunctionalInterface//函數式接口 2 interface IMessage{//JDK1.8之后接口里面可以定義普通方法和Static方法 3 public void send(); 4 } 5 public class Main { 6 public static void main(String[] args) { 7 IMessage msg=()->{ 8 System.out.println("消息發送:這是個無參lambda形式");}; 9 msg.send(); 10 } 11 }
范例:定義有參數的
1 @FunctionalInterface//函數式接口 2 interface IMath{//JDK1.8之后接口里面可以定義普通方法和Static方法 3 public int add(int x,int y); 4 } 5 public class Main { 6 public static void main(String[] args) { 7 IMath math=(t1,t2)->{ 8 return t1+t2; 9 }; 10 System.out.println(math.add(10,20)); 11 } 12 }
范例:簡化Lambda操作以上表達式只有一行語句,這個時候也可以進一步簡化。
1 @FunctionalInterface//函數式接口 2 interface IMath{//JDK1.8之后接口里面可以定義普通方法和Static方法 3 public int add(int x,int y); 4 } 5 public class Main { 6 public static void main(String[] args) { 7 IMath math=(t1,t2)->t1+t2;{ 8 }; 9 System.out.println(math.add(10,20)); 10 } 11 }
利用lambda可以擺脫傳統面向對象的關於結構的限制,使得代碼更加的簡便。只用於替換SAM的函數式接口
2,方法引用
引用數據類型最大的特點是可以進行內存的指向處理,但是在我們傳統的開發之中一直所使用的只是對象的引用操作,從JDK1.8之后也有方法的引用,即不同的方法名稱可以描述同一個方法。如果進行方法的引用在java里面提供有如下的四種形式。
①引用靜態方法: 類名稱::static 方法名稱;
②引用某個實例對象的方法: 實例化對象::普通方法;
③引用特定類型的方法: 特定類::普通方法;
④引用構造方法: 類名稱::new。
在String類里面提供有String.valueOf()方法,這個方法屬於靜態方法。
方法定義:public static String valueOf(int i),該方法有參數,並且有返回值;
·范例:
1 @FunctionalInterface//函數式接口 2 interface IFunction<P,R>{//P描述參數,R描述返回值 3 public R change(P p); 4 } 5 public class Main { 6 public static void main(String[] args) { 7 IFunction<Integer,String>fun=String::valueOf; 8 String str=fun.change(100); 9 System.out.println(str.length()); 10 } 11 }
·范例:引用實例化對象中的方法利用方法引用這一概念可以為一個方法定義多個名字,但是要求必須是函數式接口。
在String類里面有一個轉大寫的方法:public String toUpperCase();
·這個方法是必須有實例化對象提供的情況下才可以調用;
1 @FunctionalInterface//函數式接口 2 interface IFunction<R>{//P描述參數,R描述返回值 3 public R upper(); 4 } 5 public class Main { 6 public static void main(String[] args) { 7 IFunction<String>fun="hello"::toUpperCase; 8 String str=fun.upper(); 9 System.out.println(str); 10 } 11 }
這是一個普通方法,如果要引用普通方法,則往往都需要實例化對象,但是如果說現在你不想給出實例化對象,只是想引用這個方法。則就可以使用特定類進行引用處理。在進行方法引用的時候也可以進行特定類的一些操作方法,在String類中提供一個字符串大寫關系的比較: public int compareTo(String anotherString);
·范例:引用特定類中的方法
1 @FunctionalInterface//函數式接口 2 interface IFunction<P>{//P描述參數,R描述返回值 3 public int compare(P p1,P p2); 4 } 5 public class Main { 6 public static void main(String[] args) { 7 IFunction<String>fun=String::compareTo; 8 System.out.println(fun.compare("A","a")); 9 } 10 }
范例:引用構造方法在方法里面最具殺傷力的是構造方法的引用。
1 class Person{ 2 private String name; 3 private int age; 4 public Person(String name,int age){ 5 this.name=name; 6 this.age=age; 7 } 8 @Override 9 public String toString() { 10 return "姓名:"+this.name+",年齡:"+this.age; 11 } 12 } 13 @FunctionalInterface//函數式接口 14 interface IFunction<R>{//P描述參數,R描述返回值 15 public R create(String s,int a); 16 } 17 public class Main { 18 public static void main(String[] args) { 19 IFunction<Person>fun=Person::new; 20 System.out.println(fun.create("Wanyu",25)); 21 } 22 }
提供方法引用的概念更多的情況下也只是彌補了對於引用的支持功能。
3,內建函數式接口
在JDK1.8之中提供Lambda表達式也提供方法引用,但是你會發現現在由開發者自己定義函數式接口往往需要使用【@FunctionInterface】注解來進行大量的聲明,於是很多的情況下如果為了方便則可以引用系統中提供的函數式接口。
在系統值周專門提供有有一個java.util.function的開發包,里面可以直接使用函數式接口,在這個包下面可以直接使用如下的幾個核心接口:
①功能性函數式接口:
·在String類中有一個方法判斷是否以指定的字符串開頭:public boolean startsWith(String str)
接口定義 |
接口使用 |
@FunctionalInterface public interface Function<T,R>{ public R apply(T t); } |
import java.util.function.*; |
②消費型函數式接口:只能進行數據處理操作
·在進行系統數據輸出的時候使用的是:System.out.println()
接口定義 |
接口使用 |
@FunctionalInterface public interface Consumer<T>{ public void accept(T t); } |
import java.util.function.*; |
③供給型函數式接口:
·在String類中提供有轉小寫方法,這個方法沒有接收參數,但是有返回值;
|-方法:public String toLowerCase();
接口定義 |
接口使用 |
@FunctionalInterface public interface Supplier<T>{ public T get(); } |
import java.util.function.*; |
④斷言型函數式接口:進行判斷處理
·在String類有一個compareToIgnoreCase()
接口定義 |
接口使用 |
@FunctionalInterface public interface Predicate<T>{ public boolean test(T t); } |
import java.util.function.*; |
以后對於實際開發之中,如果JDK本身提供的函數式接口可以被我們所使用,那么就沒有必要重新定義了。