Java8函數式編程及Lambda
1 函數式編程##
###1.1 函數式編程思想###
函數式編程是對行爲的抽象。核心思想是:使用不可變的值和函數,函數對一個值進行處理映射成另一個值。
###1.2 函數式編程警惕###
####(1) 無狀態####
一般所說的狀態可視爲【reference, store, value】這樣的三元組(引用,存儲, 值)。❶ reference也可以叫index或pointer 或 location,store則可看做是一個接受一個reference返回value的函數(具體實現可以是內存單元,或外部文件等);❷ value就是存儲的值了;❸ 狀態變化則是指兩方面了①.reference的改變,②.reference所指的value改變,一般情況下指後者(將名稱綁定機制和賦值機制分開,如Java等語言);❹那函數式編程的無狀態就是指,以上兩者都是不可變的。
####(2) 無副作用####
❶函數式編程強調沒有"副作用",意味着函數要保持獨立,所有功能就是返回一個新的值,沒有其他行爲,尤其是不得修改外部變量的值。❷函數的副作用指的是函數在調用過程中,除了給出了返回值外,還修改了函數外部的狀態,比如,函數在調用過程中,修改了某一個全局狀態。函數式編程認爲,函數的副用作應該被儘量避免。
❸Java函數式編程中,函數式接口對象的實現會引用方法,引用的方法必須返回具體的對象值。不允許引用的方法拋異常!這點坑要注意。
2 函數式接口
2.1 函數式接口###
函數式接口是隻包含一個抽象方法的接口。如Runnable,Comparator接口。Java中Lambda無法單獨出現,需要一個函數式接口來盛放。Lambda表達式或方法具體實現就是函數式接口實現。(函數式接口標記註解 @FunctionalInterface)
2.2 Java中最常用的函數式接口###
Function/<T,R>
接受一個輸入參數,返回一個結果。
Consumer<T>
代表了接受一個輸入參數並且無返回的操作。
redicate<T>
接受一個輸入參數,返回一個布爾值結果。
Supplier<T>
無參數,返回一個結果。
UnaryOperator<T>
接受一個參數爲類型T,返回值類型也爲T。
BinaryOperator<T>
代表了一個作用於於兩個同類型操作符的操作,並且返回了操作符同類型的結果。
Java8中所有函數式接口 http://www.runoob.com/java/java8-functional-interfaces.html
##3 Lambda ##
面對大型的數據集合,Java欠缺高效的並行操作,Lambda的就是解決這一問題。Lambda表達式的入參必須是final類型。Lambda表達式本身類型是:函數式接口類型。
3.1 流(Stream)操作集合
Stream是用函數式編程方式在集合類上進行復雜操作的工具。
惰性求值:返回類型是Stream;
及早求值:返回類型是另一個值或空;
- collect() 由stream值生成列表; map() 值轉換;
- filter() 條件過濾;
- flatMap() 多個Stream
- reduce() 從一組值中生成一個值;
- limit: 對一個Stream進行截斷操作,獲取其前N個元素,如果原 Stream中包含的元素個數小於N,那就獲取其所有的元素;
- distinct: 對於Stream中包含的元素進行去重操作(去重邏輯依賴元素的equals方法),新生成的Stream中沒有重複的元素;
過濾器:
- allMatch:是不是Stream中的所有元素都滿足給定的匹配條件
- anyMatch:Stream中是否存在任何一個元素滿足匹配條件
- findFirst: 返回Stream中的第一個元素,如果Stream爲空,返回空Optional
- noneMatch:是不是Stream中的所有元素都不滿足給定的匹配條件
3.2 收集器
收集器作用:從流生成複雜結構的結果。
數據集合轉換–toList()等。
轉換成值–averagingInt()等。
數據分塊–partitioningBy()。
數據分組–groupingBy()。
3.3 數據並行化
並行:多個任務在同一時間段發生,每個任務分別在不同的cpu上並行處理。
併發:兩個或多個任務共享一個時間段在一個cup上處理。
集合的並行流操作,把stream替換成parallelStream就能獲得一個擁有並行能力的流。要避免持有鎖,必要時流框架會自己同步操作。並行流底層沿用fork/join框架實現。
3.4 影響並行流操作的性能因素
1 數據規模大小,數據夠大並行纔有意義
2 數據源結構 對於集合,ArrayList(優);HashSet,TreeSet(一般);LinkedList(差)
3 裝箱 基本類型處理要更快
4 核的數量 多核cpu纔有意義
5 無狀態操作性能更優 如map,filter無狀態操作,sorted 有狀態操作。
4 Lambda單元測試
1 lambda 表達式中建議使用方法引用。
2 使用peek方法,加入斷點調試。
5 Optional方法##
.of
爲非null的值創建一個Optional。爲null,則拋出NullPointerException。
.ofNullable
爲指定的值創建一個Optional,如果指定的值爲null,則返回一個空的Optional。
.isPresent
如果值存在返回true,否則返回false。
.get
如果Optional有值則將其返回,否則拋出NoSuchElementException。
.ifPresent
如果Optional實例有值則爲其調用consumer,否則不做處理。
.orElse
如果有值則將其返回,否則返回指定的其它值。
.orElseGet
orElseGet與orElse方法類似,區別在於得到的默認值。
orElse方法將傳入的字符串作爲默認值,orElseGet方法可以接受Supplier接口的實現用來生成默認值。
.orElseThrow
如果有值則將其返回,否則拋出supplier接口創建的異常。
在orElseGet方法中,我們傳入一個Supplier接口。
然而,在orElseThrow中我們可以傳入一個lambda表達式或方法,如果值不存在來拋出異常。
6 代碼示例
Lamba使用示例
/**
* @author bilahepan
* @version $Id: LambdaTest.java, v 0.1 2017年6月5日 下午3:32:55 bilahepan Exp $
*/
public class LambdaTest {
@Test
public void testUseThread() {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名內部類開啓線程。");
}
}).start();
new Thread(() -> System.out.println("使用lambda開啓線程。")).start();
//直接使用Runnable
Runnable thread1 = new Runnable() {
@Override
public void run() {
System.out.println("使用匿名內部類開啓線程。");
}
};
Runnable thread2 = () -> System.out.println("使用lambda開啓線程。");
thread1.run();
thread2.run();
}
@Test
public void testCommonMethod() {
List<List<String>> list = DataFactory.initData();
//lambda基本用法
list.parallelStream()
.flatMap(item -> item.stream())
.filter(item -> isStart(item))//redicate<T>
.peek(item->System.out.println(item.toString()))
.map(item -> caseConvert(item))//Function<T,R>
.collect(Collectors.toList())
.forEach(item -> System.out.println(item));
}
@Test
public void testReduce()
{
//規約操作-從一組值中生成一個值。count,min,max
//BinaryOperator<T> 代表了一個作用於於兩個同類型操作符的操作,並且返回了操作符同類型的結果。
System.out.println(DataFactory.initList().stream().count());
//這幾個操作返回的都是一個Optional對象
System.out.println(DataFactory.initList().stream().min(Comparator.comparing(item->item.getPrice())).get());
System.out.println(DataFactory.initList().stream().max(Comparator.comparing(item->item.getPrice())).get());
System.out.println(DataFactory.initList().stream().map(item->item.getPrice()).reduce(add()).get());
}
@SuppressWarnings("unused")
@Test
public void testCollector() {
//收集器
//使用內置的收集器--數據集合轉換
DataFactory.initList() .stream()
.map(item -> item.getName())
.collect(Collectors.toList())
.forEach(item -> System.out.println(item));
//使用內置的收集器--轉換成值
double average = DataFactory.initList()
.stream()
.collect(Collectors.averagingInt(item -> item.getPrice()));
//數據分塊--partitioningBy
Map<Boolean, List<Book>> partMap = DataFactory.initList()
.stream()
.collect(Collectors.partitioningBy(item -> item.getPrice() > 40));
List<Book> bookList1 = partMap.get(true);
List<Book> bookList2 = partMap.get(false);
//數據分組--groupingBy
Map<String, List<Book>> groupMap = DataFactory.initList().stream().collect(Collectors.groupingBy(item->item.getName()));
groupMap.entrySet().iterator();
}
//--------------------------------------------------------私有成員方法--------------------------------------------------------
/**
*
* @return BinaryOperator<Integer>
*/
private BinaryOperator<Integer> add() {
return (a, b) -> a + b;
}
/**
*
* @param str
* @return
*/
private String caseConvert(String str) {
if (str.startsWith("a", 0)) {
return str.toUpperCase();
} else {
return str.toLowerCase();
}
}
/**
*
* @param str
* @return
*/
private boolean isStart(String str) {
return (str.startsWith("a", 0) || str.startsWith("b", 0));
}
}
DataFactory初始化數據使用類
/**
* @author bilahepan
* @version $Id: DataFactory.java, v 0.1 2017年11月3日 下午12:35:08 bilahepan Exp $
*/
public class DataFactory {
/**
* @return
*/
public static LinkedList<Book> initList() {
LinkedList<Book> list = new LinkedList<>();
list.add(new Book("a", 10));
list.add(new Book("a", 20));
list.add(new Book("b", 20));
list.add(new Book("b", 30));
list.add(new Book("c", 30));
list.add(new Book("c", 40));
list.add(new Book("d", 40));
list.add(new Book("d", 50));
return list;
}
/**
* @return
*/
public static List<List<String>> initData() {
List<List<String>> list = new LinkedList<>();
LinkedList<String> item1 = new LinkedList<>();
item1.add("anbnb");
item1.add("ahhhk");
item1.add("a1212");
LinkedList<String> item2 = new LinkedList<>();
item2.add("bnbnb");
item2.add("bhhhk");
item2.add("b1212");
LinkedList<String> item3 = new LinkedList<>();
item3.add("cnbnb");
item3.add("chhhk");
item3.add("c1212");
LinkedList<String> item4 = new LinkedList<>();
item4.add("dnbnb");
item4.add("dhhhk");
item4.add("d1212");
list.add(item1);
list.add(item2);
list.add(item3);
list.add(item4);
return list;
}
}
Book 類
/**
* @author bilahepan
* @version $Id: Book.java, v 0.1 2017年11月3日 下午12:24:40 bilahepan Exp $
*/
public class Book {
/** name */
private String name;
/** price */
private int price;
/**
*
*/
public Book() {
}
/**
* @param name
* @param price
*/
public Book(String name, int price) {
this.name = name;
this.price = price;
}
/**
* Getter method for property <tt>name</tt>.
*
* @return property value of name
*/
public String getName() {
return name;
}
/**
* Setter method for property <tt>name</tt>.
*
* @param name value to be assigned to property name
*/
public void setName(String name) {
this.name = name;
}
/**
* Getter method for property <tt>price</tt>.
*
* @return property value of price
*/
public int getPrice() {
return price;
}
/**
* Setter method for property <tt>price</tt>.
*
* @param price value to be assigned to property price
*/
public void setPrice(int price) {
this.price = price;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Book [name=" + name + ", price=" + price + "]";
}
}