導(dǎo)圖
文章最后有源碼
簡介
學(xué)習(xí)lambda表達(dá)式就要先知道函數(shù)式接口是什么鹿鳖?
函數(shù)式接口(Functional Interfaces):如果一個接口定義個唯一一個抽象方法呛讲,那么這個接口就成為函數(shù)式接口。同時(shí),引入了一個新的注解:@FunctionalInterface∩介希可以把他它放在一個接口前,表示這個接口是一個函數(shù)式接口舍悯。這個注解是非必須的航棱,只要接口只包含一個方法的接口睡雇,虛擬機(jī)會自動判斷,不過最好在接口上使用注解 @FunctionalInterface 進(jìn)行聲明饮醇。在接口中添加了 @FunctionalInterface 的接口它抱,只允許有一個抽象方法,否則編譯器也會報(bào)錯朴艰。
示例:
/**
* 函數(shù)式接口
*/
@FunctionalInterface
interface Sum{
int add(int value);
}
Lambda表達(dá)式:可以讓你的代碼更加的簡潔观蓄。ambda無法單獨(dú)出現(xiàn),需要一個函數(shù)式接口來盛放呵晚,可以說lambda表達(dá)式方法體是函數(shù)式接口的實(shí)現(xiàn)蜘腌,lambda實(shí)例化函數(shù)式接口沫屡,可以將函數(shù)作為方法參數(shù)饵隙,或者將代碼作為數(shù)據(jù)對待。
主要優(yōu)點(diǎn):
1.代碼變得更加簡潔緊湊
2.可讀性強(qiáng)沮脖,
3.并行操作大集合變得很方便金矛,可以充分發(fā)揮多核cpu的優(yōu)勢,更加便于多核處理器編寫代碼等勺届,
語法
Lambda語法
(parameters)->expression 或者 (parameters)->{statements;}
Lambda表達(dá)式由三部分組成:
1.parameters:類似方法中的形參列表驶俊,這里的參數(shù)是函數(shù)式接口里的參數(shù)。這里的參數(shù)類型可以明確的聲明也可不聲明而由JVM隱含的推斷免姿,當(dāng)只有一個推斷類型時(shí)可以省略掉圓括號饼酿。
2.-> :可以理解為“被用于”的意思
3.方法體:可以是表達(dá)式也可以是代碼塊,實(shí)現(xiàn)函數(shù)式接口中的方法胚膊。這個方法體可以有返回值也可以沒有返回值
示例:
1.不接受參數(shù)故俐,直接返回1
()->1
2.接受兩個int類型的參數(shù),返回這兩個參數(shù)的和
(int x,int y )-> x+y
3.接受x,y兩個參數(shù)紊婉,JVM根據(jù)上下文推斷參數(shù)的類型药版,返回兩個參數(shù)的和
(x,y)->x+y
4.接受一個字符串,打印該字符串喻犁,沒有返回值
(String name)->System.out.println(name)
5.接受一個參數(shù)槽片,JVM根據(jù)上下文推斷參數(shù)的類型,打印該參數(shù)肢础,沒有返回值,只有一個參數(shù)可以省略圓括號
name->System.out.prinln(name)
6.接受兩個String類型參數(shù)还栓,分別輸出,沒有返回值
(String name,String sex)->{System.out.println(name);System.out.println(sex)}
7.接受呀一個參數(shù)传轰,返回它本身的2倍
x->2*x
傳統(tǒng)寫法與Lambda寫法的比較
首先定義一個函數(shù)式接口
/**
* 函數(shù)式接口
* @param <A>
* @param <B>
*/
@FunctionalInterface
interface Transform<A,B>{
B transform(A a);
}
兩種寫法的對比
//傳統(tǒng)方式使用接口
Transform<String ,Integer> transform1 = new Transform<String, Integer>() {
@Override
public Integer transform(String s) {
return Integer.valueOf(s);
}
} ;
//Lambda方式使用接口,就是這么簡單粗暴剩盒,沒脾氣
Transform<String,Integer> transform2 = (s)-> Integer.valueOf(s);
訪問權(quán)限
在Lambda表達(dá)式使用中,Lambda表達(dá)式外面的局部變量會被JVM隱式的編譯成final類型路召,Lambda表達(dá)式內(nèi)部只能訪問勃刨,不能修改
Lambda表達(dá)式內(nèi)部對靜態(tài)變量和成員變量是可讀可寫的
Lambda不能訪問函數(shù)接口的默認(rèn)方法波材,在函數(shù)接口中可以添加default關(guān)鍵字定義默認(rèn)的方法
局部變量示例:
public static void main(String[] args) {
int num = 6;//局部變量
Sum sum = value -> {
// num = 8; 這里會編譯出錯
return num + value;
};
sum.add(8);
}
/**
* 函數(shù)式接口
*/
@FunctionalInterface
interface Sum{
int add(int value);
}
靜態(tài)變量和成員變量示例:
public int num1 = 6;
public static int num2 = 8;
private int getSum(){
Sum sum = value -> {
num1 = 10;
num2 = 10;
return num1 + num2;
};
return sum.add(1);
}
/**
* 函數(shù)式接口
*/
@FunctionalInterface
interface Sum{
int add(int value);
}
方法引用
在lambda表達(dá)式中,方法引用是一種簡化寫法身隐,引用的方法就是Lambda表達(dá)式的方法體的實(shí)現(xiàn)
語法結(jié)構(gòu):ObjectRef:: methodName
左邊是類名或者實(shí)例名廷区,中間的“::”是方法引用符號,右邊是相應(yīng)的方法名
方法引用一般分為三類:
靜態(tài)方法引用贾铝,實(shí)例方法引用隙轻,構(gòu)造方法引用
靜態(tài)方法引用示例:
public static void main(String[] args){
//傳統(tǒng)方式
Transform<String ,Integer> transform1 = new Transform<String, Integer>() {
@Override
public Integer transform(String s) {
return C_方法引用之靜態(tài)方法引用.strToInt(s);
}
};
int result1 = transform1.transform("100");
//Lambda方式
Transform<String,Integer> transform2 = C_方法引用之靜態(tài)方法引用 ::strToInt;
int result2 = transform2.transform("200");
}
static int strToInt(String str){
return Integer.valueOf(str);
}
/**
* 函數(shù)式接口
* @param <A>
* @param <B>
*/
@FunctionalInterface
interface Transform<A,B>{
B transform(A a);
}
實(shí)例方法引用示例:
public static void main(String[] args){
//傳統(tǒng)方式
Transform<String ,Integer> transform1 = new Transform<String, Integer>() {
@Override
public Integer transform(String s) {
return new Obj().strToInt(s);
}
};
int result1 = transform1.transform("100");
//Lambda方式
Obj obj = new Obj();
Transform<String,Integer> transform2 = obj::strToInt;
int result2 = transform2.transform("200");
}
/**
* 函數(shù)式接口
* @param <A>
* @param <B>
*/
interface Transform<A,B>{
B transform(A a);
}
/**
* 實(shí)例對象類
*/
static class Obj{
public int strToInt(String str){
return Integer.valueOf(str);
}
}
構(gòu)造方法引用示例:
//傳統(tǒng)方式
Factory factory1 = new Factory() {
@Override
public Parent create(String name, int age) {
return new Boy(name,age);
}
};
Boy boy = (Boy) factory1.create("小明",18);
factory1 = new Factory() {
@Override
public Parent create(String name, int age) {
return new Girl(name,age);
}
};
Girl girl = (Girl) factory1.create("小紅",18);
//Lambda方式
Factory<Boy> boyFactory = Boy::new;
Boy boy1 = boyFactory.create("小明",18);
Factory<Girl> girlFactory = Girl::new;
Girl girl1 = girlFactory.create("小紅",18);
其他類和接口:
//工廠類接口
public interface Factory<T extends Parent> {
T create(String name,int age);
}
//父類
public class Parent {
private String name ;
private int age;
public Parent(String name, int age) {
this.name = name;
this.age = age;
}
public void doSome(){
}
}
//男孩類
public class Boy extends Parent {
public Boy(String name, int age) {
super(name, age);
}
@Override
public void doSome() {
System.out.println("我是個男孩");
}
}
//女孩類
public class Girl extends Parent {
public Girl(String name, int age) {
super(name, age);
}
@Override
public void doSome() {
System.out.println("我是個女孩");
}
}
四個常用的接口
Predicate接口
/**
* Predicate接口:輸入一個參數(shù),返回一個boolean值垢揩,內(nèi)置了許多用于邏輯判斷的默認(rèn)方法
*/
public class F_實(shí)踐之Predicate {
public void predicateTest(){
Predicate<String> predicateStr = s -> s.length()>8;
boolean testResult = predicateStr.test("test");//需要api 24
testResult = predicateStr.negate().test("test");//取反玖绿,也就是s.length<=8
Predicate<Object> predicateObj = Objects::nonNull;
Object obj = null;
testResult = predicateObj.test(obj);//判斷是否為空
}
}
Consumer接口
/**
* consumer接口:對輸入的參數(shù)進(jìn)行操作。有輸入沒輸出
*/
private static void consumerTest(){
Consumer<Integer> add5 = (p) -> {
System.out.println("old value:" + p);
p = p + 5;
System.out.println("new value:" + p);
};
add5.accept(10);
}
Function接口
/**
* Function接口:接受一個參數(shù)叁巨,返回單一的結(jié)果斑匪。默認(rèn)的方法(andThen)可將多個函數(shù)串在一起,形成復(fù)合Funtion(有輸入锋勺,有輸出)結(jié)果
*/
public static void functionTest(){
Function<String, Integer> toInteger = Integer::valueOf;
//toInteger的執(zhí)行結(jié)果作為第二個backToString的輸入
Function<String, String> backToString = toInteger.andThen(String::valueOf);
String result = backToString.apply("1234");
System.out.println(result);
Function<Integer, Integer> add = (i) -> {
System.out.println("frist input:" + i);
return i * 2;
};
Function<Integer, Integer> zero = add.andThen((i) -> {
System.out.println("second input:" + i);
return i * 0;
});
Integer res = zero.apply(8);
System.out.println(res);
}
Supplier接口
/**
* Supplier接口:返回一個給定類型的結(jié)果蚀瘸。不需要輸入?yún)?shù),無輸入有輸出
*/
private static void supplierTest(){
Supplier<String> supplier = () -> "我就是輸出";
String s = supplier.get();
System.out.println(s);
}
串行stream操作
Lambda為java8帶來了閉包庶橱,支持對集合對象的stream進(jìn)行函數(shù)式操作贮勃, stream api被集成進(jìn)了collection api ,允許對集合對象進(jìn)行批量操作苏章。
Stream表示數(shù)據(jù)流寂嘉,它沒有數(shù)據(jù)結(jié)構(gòu),本身也不存儲元素枫绅,其操作也不會改變源Stream泉孩,而是生成新Stream.作為一種操作數(shù)據(jù)的接口,它提供了過濾撑瞧、排序棵譬、映射、規(guī)約等多種操作方法预伺,
這些方法按照返回類型被分為兩類:凡是返回Stream類型的方法订咸,稱之為中間方法(中間操作),其余的都是完結(jié)方法(完結(jié)操作)酬诀。完結(jié)方法返回一個某種類型的值脏嚷,而中間方法則返回新的Stream。
中間方法的調(diào)用通常是鏈?zhǔn)降穆饔撨^程會形成一個管道父叙,當(dāng)完結(jié)方法被調(diào)用時(shí)會導(dǎo)致立即從管道中消費(fèi)值,這里我們要記住:Stream的操作盡可能以“延遲”的方式運(yùn)行趾唱,也就是我們常說的“懶操作”涌乳,
這樣有助于減少資源占用,提高性能甜癞。對于所有的中間操作(除sorted外)都是運(yùn)行在延遲模式下夕晓。
Stream不但提供了強(qiáng)大的數(shù)據(jù)操作能力,更重要的是Stream既支持串行也支持并行悠咱,并行使得Stream在多核處理器上有著更好的性能蒸辆。
Stream的使用過程有著固定的模式:
1.創(chuàng)建Stream
2.通過中間操作,對原始Stream進(jìn)行“變化”并生成新的Stream
3.使用完結(jié)操作析既,生成最終結(jié)果
//創(chuàng)建一個集合
List<String> list = new ArrayList<>();
list.add("a1");list.add("a2");list.add("a3");list.add("b1");list.add("b2");list.add("b3");
中間操作方法
過濾(filter)
結(jié)合Predicate接口躬贡,F(xiàn)ilter對流對象中的所有元素進(jìn)行過濾,該操作是一個中間操作,這意味著你可以在操作返回結(jié)果的基礎(chǔ)上進(jìn)行其他操作
public static void sreamFilterTest(List<String> lists){ //要明確這list的泛型類型眼坏,否則jvm不能根據(jù)上下文確定參數(shù)類型
lists.stream().filter((s -> s.startsWith("a"))).forEach(System.out::println);//將開頭是a的過濾出來
//等價(jià)于以上操作
Predicate<String> predicate = (s) -> s.startsWith("a");//將開頭是a的過濾出來
lists.stream().filter(predicate).forEach(System.out::println);
//連續(xù)過濾
Predicate<String> predicate1 = (s -> s.endsWith("1"));//將開頭是a拂玻,并且結(jié)尾是1的過濾出來
lists.stream().filter(predicate).filter(predicate1).forEach(System.out::println);
}
排序(sorted)
結(jié)合Comparator,該操作返回一個排序過后的流的視圖,原始流的順序不會改變空骚。通過Comparator來指定排序規(guī)則纺讲,默認(rèn)是自然排序
private static void streamSortedTest(List<String> list){
//默認(rèn)排序
list.stream().filter(s -> s.startsWith("a")).forEach(System.out::println);
System.out.println("- - - - - - - - -");
//自定義排序
list.stream().sorted(((s, t1) -> t1.compareTo(s))).filter(s -> s.startsWith("a")).forEach(System.out::println);
}
映射(map)
結(jié)合Function接口,該操作能將流對象中的每一個元素映射為另一個元素囤屹,實(shí)現(xiàn)元素類型的轉(zhuǎn)換。
private static void streamMapTest(List<String> list){
list.stream().map(String::toUpperCase).sorted((s, t1) -> t1.compareTo(s)).forEach(System.out::println);
System.out.println("- - - - - - ");
//自定義映射規(guī)則
Function<String,String> function = s -> {return s + ".map3";};
list.stream().map(function).forEach(System.out::println);
}
完結(jié)操作方法
匹配(match)
用來判斷某個predicate是否和流對象相匹配逢渔,最終返回boolean類型的結(jié)果
private static void streamMatchTest(List<String> list){
//流對象中只要有一個元素匹配就返回true
boolean anyStartWithA = list.stream().anyMatch(s -> s.startsWith("a"));
System.out.println("集合中是否有以'a'來頭:"+ anyStartWithA);
//流對象中每一個元素都匹配才返回true
boolean allStartWithA = list.stream().allMatch(s -> s.startsWith("a"));
System.out.println("集合中每一個都是以'a'開頭:"+ allStartWithA);
//流對象中沒有匹配時(shí)返回true
boolean noneStartWithA = list.stream().noneMatch(s -> s.startsWith("c"));
System.out.println("集合中沒有以'c'開頭:"+ noneStartWithA);
}
收集(collect)
在對經(jīng)過變換后肋坚,將變換的stream元素收集,比如將這些元素存在集合中肃廓,可以使用stream提供的collect方法
private static void streamCollectTest(List<String> list){
List<String> listNew = list.stream().filter(s -> s.startsWith("b")).sorted().collect(Collectors.toList());
System.out.println(listNew );
}
規(guī)約(reduce)
允許我們用自己的方式計(jì)算元素或者將一個stream中元素以某種規(guī)律關(guān)聯(lián)
private static void streamReduceTest(List<String> list){
Optional<String> optional = list.stream().sorted().reduce((s, s2) -> {
System.out.println(s+"-"+s2);
return s+"-"+s2;
});
}
計(jì)數(shù)(count)
用來統(tǒng)計(jì)流中元素的總數(shù)
private static void streamCountTest(List<String> list){
long count = list.stream().filter(s -> s.startsWith("b")).count();
System.out.println("以'b'開頭的數(shù)量:"+ count);
}
并行操作stream
并行Stream:基于Fork-join并行分解框架實(shí)現(xiàn)智厌,將大數(shù)據(jù)集合切分為多個小數(shù)據(jù)結(jié)合交給不同的線程去處理,這樣在多核處理情況下盲赊,性能會得到很大的提高铣鹏。
這和MapReduce的設(shè)計(jì)理念一致:大任務(wù)化小,小任務(wù)再分配到不同的機(jī)器執(zhí)行哀蘑。只不過這里的小任務(wù)是交給不同的處理器诚卸。
結(jié)果是性能提高50%,單核下還是串行流性能比較好绘迁,并行流的使用場景是多核+大數(shù)據(jù)
//創(chuàng)建一個大集合
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000000; i++) {
UUID uuid = UUID.randomUUID();
list.add(uuid.toString());
}
//并行stream
private static void parallelStreamSortedTest(List<String> list){
long startTime = System.nanoTime();//返回最準(zhǔn)確的可用系統(tǒng)計(jì)時(shí)器的當(dāng)前值合溺,以毫微秒為單位。
long count = list.parallelStream().sorted().count();
long endTime = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
System.out.printf("并行排序花費(fèi)時(shí)間:%d ms",millis);
}
//串行stream
private static void streamSortedTest(List<String> list){
long startTime = System.nanoTime();//返回最準(zhǔn)確的可用系統(tǒng)計(jì)時(shí)器的當(dāng)前值缀台,以毫微秒為單位棠赛。
long count = list.stream().sorted().count();
long endTime = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
System.out.printf("串行排序花費(fèi)時(shí)間:%d ms",millis);
}
運(yùn)行結(jié)果:
并行排序花費(fèi)時(shí)間:4362 ms
串行排序花費(fèi)時(shí)間:9847 ms