JSR(Java Specification Requests) 335 = lambda表達式+接口改進(默認方法)+批量數據操做html
1、函數式編程
以處理數據的方式處理代碼,這意味着函數能夠被賦值給變量,傳遞給函數等,函數是第一等級的值
入門例子:java
[java] view plain copyshell
Scala/Groovy/Clojure是JVM平臺上的編程語言,都支持函數式編程,如下以Scala語言來函數式改寫上面的代碼:express
[java] view plain copy編程
將過濾,轉化和聚合三個行爲分開,表面上看,上面的代碼貌似先遍歷列表挑出全部符合條件的元素傳給map,map接着遍歷並處理從filter傳來的結果,最後將處理後的結果都傳給reduce,reduce接着遍歷處理。數了數貌似要經歷過好屢次遍歷呢,可是其具體的實現並不是如此,由於其有「惰性」,它的具體實現行爲和上面JAVA for-loop實現基本一致,只是函數式更直觀,更簡潔而已。api
靜態語言 VS 動態語言:http://www.zhihu.com/question/19918532數組
JAVA的函數式編程(Lambda):
當咱們用匿名內部類爲某個接口定義不一樣的行爲時,語法過於冗餘,lambda提供了更簡潔的語法。
- 函數式接口 Functional interfaces
每個函數對象都對應一個接口類型。函數式接口只能擁有一個抽象方法,編譯器會根據接口的結構自行判斷(判斷過程並非簡單的對接口方法計數,由於該接口可能還定義了靜態或默認方法)。API做者也能夠經過@FunctionalInterface註解來指定一個接口爲函數式接口,以後編譯器就會驗證該接口是否知足函數式接口的要求。
JAVA SE 7中已經存在的函數式接口,例如:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.util.Comparator
- java.io.FileFilter
JAVA SE 8中新增長了一個包 java.util.function,包含了經常使用的函數式接口,例如:
- Predicate<T> -- boolean test(T t);
- Consumer<T> -- void accept(T t);
- Function<T, R> -- R apply(T t);
- Supplier<T> -- T get();數據結構
- BiFunction<T, U, R> -- R apply(T t, U u);oracle
lambda表達式例子:
x -> x+1
() -> {System.out.println("hello world");}app
(int x, int y) -> { x++; y--; return x+y;}
Function<Integer, Integer> func = (x, Integer y) -> x + y //wrong
Function<Integer, Integer> func = Integer x -> x + 1 //wrong
當且僅當下面全部條件均被知足時,lambda表達式才能夠被賦給目標類型T:
- T是一個函數式接口
- lambda表達式的參數和T的方法參數在數量和類型上一一對應
- lambda表達式的返回值和T的方法返回值相兼容
- lambda表達式內所拋出的異常和T的方法throws類型想兼容
lambda表達式的參數類型能夠從目標類型中得出的話就能夠省略。
目標類型上下文:
- 變量聲明
- 賦值
- 返回語句
public Runnable PrintSth() {
return () -> {
System.out.println("sth");
};
}
Comparator<String> c;
c = (String s1, String s2) -> s1.compareToIgnoreCase(s2);
- 數組初始化器
new FileFilter[] {
f -> f.exists(), f -> f.canRead(), f -> f.getName().startsWith("s")
}
- 方法和構造方法的參數
List<Person> ps = ...;
Stream<String> names = ps.stream().map(p -> p.getName());
map的參數是Function<T, R>,p能夠從List<Person>推導出T爲Person類型,而經過Stream<String>能夠知道R的類型爲String,當編譯器沒法解析出正確lambda類型的時候,能夠:
1. 爲lambda參數提供顯示類型
2. 將lambda表達式轉型爲Function<Person, String>
3. 爲泛型參數R提供一個實際類型,好比 .<String>map(p->p.getName())
- lambda表達式函數體
- 條件表達式(?:)
- 轉型表達式
Supplier<Runnable> c = () -> () -> {System.out.println("hi");};
Callable<Integer> c = flag ? (() -> 23) : (() -> 42);
Object o = () -> {System.out.println("hi");}; //非法
Object o = (Runnable) () -> {System.out.println("hi");};
詞法做用域:
lambda不會引入一個新的做用域。
public class Hello {
Runnable r1 = () -> { System.out.println(this); };
Runnable r2 = () -> { System.out.println(toString()); };
public String toString() { return "Hello, world"; }
public static void main(String... args) {
new Hello().r1.run();
new Hello().r2.run();
}
}
Output:
Hello, world
Hello, world
lambda不能夠掩蓋其所在上下文中的局部變量,相似:
int i = 0;
int sum = 0;
for (int i = 1; i < 10; i += 1) { //這裏會出現編譯錯誤,由於i已經在for循環外部聲明過了
sum += i;
}
變量捕獲:
[java] view plain copy
Output: 11, 2
Function<Integer, Integer> func = t.m(); //++this.sum;
System.out.println(func.apply(9)); //++this.sum; this.sum+i;
lambda只能捕獲effectively final的局部變量,就是捕獲時或捕獲後不能修改相關變量。若是sum是m方法的局部變量,func不能修改該局部變量,由於m執行完就退出棧幀。同時這樣也能夠避免引發race condition,in a nutshell,lambda表達式對值封閉,對變量開放。
int sum = 0;
list.forEach(e -> { sum += e.size(); }); // Illegal, close over values
List<Integer> aList = new List<>();
list.forEach(e -> { aList.add(e); }); // Legal, open over variables
A race condition occurs when two or more threads can access shared data and they try to change it at the same time. Because the thread scheduling algorithm can swap between threads at any time, you don't know the order in which the threads will attempt to access the shared data.
list.parallelStream().forEach(e -> {sum += e;}); //此時多個線程同時處理,若是sum不是不可變的話,就會引發race condition,那樣的話咱們就須要提供synchronization或者利用volatile來避免讀取stale data。
http://www.lambdafaq.org/what-are-the-reasons-for-the-restriction-to-effective-immutability/
在普通內部類中,實例會一直保留一個對其外部類實例的強引用,每每可能會形成內存泄漏。而lambda表達式只有在有用到類實例變量時才保持一個this引用,如上面func會保存一個引用指向Test3的實例。
lambda簡寫形式 - 方法引用:
若是咱們想要調用的方法擁有一個名字,咱們就能夠直接調用它。
- 靜態方法引用 className::methodName
- 實例上的實例方法引用 instanceReference::methodName
- 超類上的實例方法引用 super::methodName
- 類型上的實例方法引用 className::methodName,好比String::toUpperCase,語法和靜態方法引用相同,編譯器會根據實際狀況做出決定
- 構造方法引用 className::new
- 數組構造方法引用 TypeName[]::new
[java] view plain copy
2、默認方法和靜態接口方法
如何給一個單抽象函數式接口添加其餘的方法呢?接口方法能夠是抽象和默認的,默認方法擁有默認的實現,實現接口的類型能夠經過繼承獲得該默認實現。除此以外,接口還容許定義靜態方法,使得咱們能夠從接口直接調用和它相關的輔助方法。默認方法能夠被繼承,當父類和接口的超類擁有多個具備相同簽名的方法時,好比菱形繼承,咱們就須要遵守一些規則:
1. 類的方法優先於默認方法,不管該方法是具體仍是抽象的
2. 被其餘類型所覆蓋的方法會被忽略,當超類共享一個公共祖先的時候
class LinkedList<E> implements List<E>, Queue<E> { ... }
3. 當兩個獨立的默認方法或者默認方法和抽象方法相沖突時會發生編譯錯誤,此時咱們須要顯式覆蓋超類方法
interface Robot implements Artist, Gun {
default void draw() { Artist.super.draw(); }
}
3、批量處理
- 傳遞行爲而不是值
enhanced for loop VS forEach
[java] view plain copy
我的看法,這個例子中,forEach並無比for loop更高效,通常其在parallel stream下被調用的時候才發揮做用。
- 能夠並行處理,批量處理
流是java8類庫中新增的關鍵抽象,被定義於java.util.stream中。流的操做能夠被組合成流水線pipeline。
Stream特性:
1. stream不會存儲值,流的元素來自數據源(數據結構,生成函數或I/O通道)
2. 對stream的操做並不會修改數據源
3. 惰性求值,每一步只要一找到知足條件的數字,立刻傳遞給下一步去處理而且暫停當前步驟
4. 可選上界,由於流能夠是無限的,用戶能夠不斷的讀取直到設置的上界
咱們能夠把集合做爲流的數據源(Collection擁有stream和parallelStream方法),也能夠經過流產生一個集合(collect方法)。流的數據源多是一個可變集合,若是在遍歷時修改數據源,會產生interference,因此只要該集合只屬於當前線程而且lambda表達式不修改數據源就能夠。
1. 不要干擾數據源
2. 不要干擾其餘lambda表達式,當一個lambda在修改某個可變狀態而另外一個lambda在讀取狀態時就會產生這種干擾
惰性:
急性求值,即在方法返回前完成對全部元素的過濾,而惰性求值則在須要的時候才進行過濾,一次性遍歷。
Collectors:
利用collect()方法把流中的元素聚合到集合中,如List或者Set。collect()接收一個類型Collectors的參數,這個參數決定了如何把流中的元素聚合到其餘數據結構中。Collectors類包含了大量經常使用收集器的工廠方法,toList()和toSet()就是其中最多見的兩個。
並行:
爲了實現高效的並行計算,引入了fork/join模型:
- 把問題分解爲子問題
- 串行解決子問題從而獲得部分結果
- 合併部分結果爲最終結果
並行流:將流抽象爲Spliterator,容許把輸入元素的一部分轉移到另外一個新的Spliterator中,而剩下的數據會保存在原來的Spliterator裏面。以後還能夠進一步分割這兩個Spliterator。分割迭代器還能夠提供源的元數據(好比元素的數量)和其餘一系列布爾值特徵(好比元素是否被排序)。若是想要本身實現一個集合或者流,就可能須要手動實現Spliterator接口。stream在JAVA SE8中很是重要,咱們爲collection提供了stream和parallelStream把集合轉化成流,此外數組也能夠經過Arrays.stream()被轉化爲流。stream中還有一些靜態工廠方法來建立流,例如Stream.of(), Stream.generate()等。
- 建立stream
1.Stream接口的靜態方法
Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");
2.Collection接口的默認方法
- 轉換stream,每次轉換會返回一個新的stream對象(把一個stream經過某些行爲轉換成一個新的stream)
1. distinct,stream中包含的元素不重複
2. filter,使用給定的過濾函數進行過濾操做
3. map,對於stream中包含的元素使用給定的轉換函數進行轉換操做,新生成的Stream只包含轉換生成的元素。這個方法有三個對於原始類型的變種方法,分別是mapToInt, mapToLong和mapToDouble。
4. peek,生成包含原stream的各個元素的新stream,同時提供一個消費函數,新stream每一個元素被消費的時候都會執行給定的消費函數
5. limit,對一個stream進行截斷
6. skip,返回一個丟棄原stream的前N個元素後剩下元素組成新的stream
- 對stream進行聚合,獲取想要的結果
接受一個元素序列爲輸入,反覆使用某個合併操做,把序列中的元素合併成一個彙總的結果
http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html
4、其餘
代碼加載順序:
非靜態是在new申請內存空間並初始化其值。java必須先加載字節碼,而後經過new根據字節碼信息申請類的內存空間。靜態是在類的字節碼加載到jvm中,但僅且只執行一次。靜態變量和靜態代碼塊跟聲明順序有關,若是今天太代碼塊中調用靜態變量,那麼靜態變量必須在靜態代碼塊前面聲明;若是靜態代碼塊沒有調用靜態變量,那麼就先聲明誰被加載。
父類靜態屬性-》父類靜態代碼塊-》子類靜態變量-》子類靜態代碼塊-》父類非靜態變量-》父類非靜態變量-》父類非靜態代碼塊-》父類構造函數-》子類非靜態變量-》子類非靜態代碼塊-》子類構造函數
1. 初始化構造時,先父後子
2. 靜態,非靜態
3. java中的類只有被用到的時候纔會被加載
4. java類只有在類字節碼被加載後才能夠被構形成對象實例
遞歸+組合之後再慢慢研究:
http://www.cnblogs.com/ldp615/archive/2013/04/09/recursive-lambda-expressions-1.html
http://www.kuqin.com/shuoit/20140913/342127.html
目前(2016-8)像JAD,JD-GUI都還不支持Lambda特性,因此發現了一個新反編譯工具CFR,只要去官網下載CFR的jar包,而後CMD命令進入到放置該包的目錄,運行:
java -jar cfr**.jar *.class (*號的地方本身寫入符合的名字)
Reference:
1. Java 下一代:函數式編碼風格(Groovy, Scala和Clojure共享的函數結構及其優點)
2. http://www.cnblogs.com/figure9/archive/2014/10/24/4048421.html
3. http://zh.lucida.me/blog/java-8-lambdas-insideout-library-features/
4. http://ifeve.com/stream/
5. http://www.oschina.net/question/2273217_217864