不論是C語言,Java,仍是數據庫的SQL,函數Function絕對是咱們最常打交道的對象。今天要介紹的「函數式編程」,是指Java8引入的新語法,雖然Java8在2014年就推出了,我想應該仍是有不少同窗不太瞭解。
咱們之前接觸的函數,基本都是接受變量參數,返回結果參數。這些參數能夠是基本變量、對象和數組等,但能不能是一段代碼塊呢?Java裏面代碼塊都能抽象成一個函數,所謂函數式編程,就是在定義一個函數時,它的參數也是一個函數的接口。在調用函數時,再去實現這個函數的接口,這個過程,也就出現了lambda表達式。java
以下文,咱們定義了一個函數 - 通知全部員工。數據庫
/** * 通知員工 * @param notify */ public static void notifyEmployees( Consumer<Employee> notify){ List<Employee> employeeList=employeeMapper.getAllEmployees(); for(Employee employee : employeeList){ notify.accept(employee); } }
可是咱們沒有定義怎麼去通知員工,多是郵件通知,多是短信通知,也有多是微信通知,等等。咱們將具體通知的方式,做爲這個函數的一個傳入參數。這裏藉助了Java自帶的函數式接口 Consumer<T> ,後面會介紹它怎麼用。那麼我在調用這個方法的時候,就能夠根據狀況去實現這個函數式接口。編程
//經過郵件 notifyEmployees((employee)->{ sendEmail(employee.getEmail()); }); //經過短信 notifyEmployees((employee)->{ sendSms(employee.getPhoneNumber()); }); // 等等 ...
Lambda表達式支持將代碼塊做爲方法參數,Lambda 表達式容許使用更簡潔的代碼來建立只有一個抽象方法的接口(這種接口被稱爲函數式接口)的實例。
在上文實現函數式編程時,你或許感受奇怪,爲何經過 -> 箭頭之類的語法,就能實現接口?這就是Lambda表達式的語法,實際上是對咱們之前建立匿名內部類的簡寫。api
匿名內部類也是一種簡化代碼的方式,我最先接觸到的匿名內部類,就是在多線程中建立Thread對象時,經過實現Runnable接口來建立。數組
Thread thread=new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"開始了!"); } }); thread.start();
實際上new Thread(Runnable target) 接收的參數,是實現了Runnable接口的類的對象。但因爲咱們關心的是接口裏面的實現方法,而不是類的名字,因此用匿名內部類的方式來建立對象。
可是在看了Runnable接口的定義後,你會發現這個類只有一個抽象方法,這就又讓人很不爽。我關心的只是這個類裏面的這一個抽象方法,能不能不去實現這個類?Lambda表達式就應運而生了,它讓代碼看起來更簡潔美觀。服務器
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName() + "開始了!")); thread.start();
函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,可是能夠有多個非抽象方法的接口。函數式接口能夠被隱式轉換爲 lambda 表達式。
咱們再看一下Runnable這個函數式接口。微信
@FunctionalInterface public interface Runnable { public abstract void run(); }
函數式接口的特色以下:多線程
Java8定義了大量的預約義函數式接口,用於常見類型的代碼傳遞,這些函數定義在java.util.function下。app
接受參數對象T,返回一個boolean結果dom
Predicate<String> isAdminPre=(String username)-> "admin".equals(username); System.out.println(isAdminPre.test("kerry")); System.out.println(isAdminPre.test("admin"));
接收參數對象T,不返回結果
Consumer<String> msgCon = (String msg) -> { System.out.println("發送消息" + msg); System.out.println("消息發送完成"); }; msgCon.accept("01!01!我是02!");
接收參數對象T,返回結果對象R
Function<Date,String> timeFunc=(Date date)->{ Calendar calendar=Calendar.getInstance(); calendar.setTime(date); int hour=calendar.get(Calendar.HOUR_OF_DAY); if(hour>=6 && hour<12){ return "上午"; }else if(hour>=12&& hour<18){ return "下午"; }else { return "晚上"; } }; System.out.println(timeFunc.apply(new Date()));
不接受參數,提供對象T的建立工程
Supplier<String> uuidSup=()-> UUID.randomUUID().toString().replace("-",""); System.out.println(uuidSup.get());
接收參數對象T,返回結果對象T
UnaryOperator<String> trimUO=(String str)->{ if(str ==null){ return str; } return str.trim(); }; System.out.println(trimUO.apply(" Hello "));
接收兩個T對象,返回一個T對象結果
BinaryOperator<Integer> maxBO=(Integer i1,Integer i2)-> i1>i2?i1:i2; System.out.println(maxBO.apply(13,20));
Java 8 API添加了一個新的抽象稱爲流Stream,可讓你以一種聲明的方式處理數據。Stream API 藉助於Lambda 表達式,極大的提升編程效率和程序可讀性。
Stream 使用一種相似用 SQL 語句從數據庫查詢數據的直觀方式來提供一種對 Java 集合運算和表達的高階抽象,它專一於對集合對象進行各類很是便利、高效的聚合操做,或者大批量數據操做。這種風格將要處理的元素集合看做一種流, 流在管道中傳輸, 而且能夠在管道的節點上進行處理, 好比篩選, 排序,聚合等。
元素流在管道中通過中間操做(intermediate operation)的處理,最後由最終操做(terminal operation)獲得前面處理的結果。
我我的感受,Stream有點像是用Java代碼編寫的Hadoop。Hadoop利用分佈式服務器,提升並行計算的能力,而Stream也一樣能夠經過多線程,提升數據處理的能力。它們也一樣用map和reduce,作數據的映射和整合。
流的操做類型分爲兩種:
常見的流操做方法有以下:
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
關於Stream的知識點比較多,《Java 8 中的 Streams API 詳解》 這篇文章是我看到比較好的Stream系列博客,建議你們直接看這篇文章。
Optional有點像Stream,是Java8推出的處理數據的包裝類,不少方法的實現都和lambda表達式結合在一塊兒,推近了函數式編程風格。Optional主要用來處理常見的空指針異常,學會它,你的代碼會美觀不少。
可經過 Optional.ofNullable(T value)和Optional.of(T value),前者容許被包裝的對象爲空。然後者,若是被包裝的對象爲空仍是會報空指針異常。
從Optional實例中取回實際值對象的方法之一是使用 get()方法,不過這個方法會在值爲null的時候拋出異常。要避免異常,你能夠選擇先用isPresent()驗證是否有值。
User user=new User(); Optional<User> userOptional=Optional.ofNullable(user); //或 Optional<User> userOptional=Optional.of(user);
Optional對象的isPresent()方法返回boolean值,若是爲空返回false,不爲空返回true。
該方法除了執行檢查,還接受一個Consumer(消費者) 參數,若是對象不是空的,就對執行傳入的 Lambda 表達式.
orElse(),若是有值則返回該值,不然返回傳遞給它的參數值。
orElseGet(),這個方法會在有值的時候返回值,若是沒有值,它會執行做爲參數傳入的 Supplier 函數式接口,並將返回其執行結果。
User newUser=Optional.ofNullable(user).orElse(new User("anonymous")); //或 User newUser=Optional.ofNullable(user).orElseGet(()->{ User sUser=new User("anonymous"); sUser.setAge(25); return sUser; });
值得注意的是,就算對象不爲空,orElse() 方法仍然會建立User對象,只是不會給newUser賦值。而orElseGet() 方法,只有當對象爲空的時候纔會建立 User 對象。這個差別,在方法調用量大的時候,對性能產生的影響差別比較大。
orElseThrow() 會在對象爲空的時候拋出異常,而不是返回默認值,這個方法讓咱們有更豐富的語義,能夠決定拋出什麼樣的異常,而不老是拋出 NullPointerException。
map() 對值應用(調用)做爲參數的函數,而後將返回的值包裝在Optional中。這就使對返回值進行鏈試調用的操做成爲可能 。
flatMap() 也須要函數做爲參數,並對值調用這個函數,可是會直接返回結果,並不會包裝在Optional中。
User user=new User(); String name=Optional.ofNullable(user) .map(User::getName) .orElse("anonymous"); System.out.println(name);
map經常使用於鏈式方法判斷中,例如上文代碼實際上等於:
User user=new User(); String name="anonymous"; if(user!=null){ if(user.getName()!=null){ name=user.getName(); } } System.out.println(name);
filter() 接受一個 Predicate 參數,返回測試結果爲 true 的值。若是測試結果爲 false,會返回一個空的 Optional。
User user=new User("Kerry"); user.setEmail("kerry.wu@definesys.com"); Optional<User> filterUser=Optional.ofNullable(user) .filter(u->u.getEmail()!=null&&u.getEmail().contains("@"));