java8 lambda python_【学习笔记】java8 Lambda表达式语法及应用
本文是慕課網大牧莫邪老師的視頻教程一課掌握Lambda表達式語法及應用的學習筆記。如果覺得內容對你有用,可以購買老師的課程支持一下,課程價格1元,十分良心了。
1. 課程介紹
2. 為什么引入Lambda表達式
2.1 什么是Lambda表達式
Lambda表達式也稱箭頭函數、匿名函數、閉包
Lambda表達式體現的是輕量級函數式編程思想
-> 符號是Lambda表達式核心操作符號,符號左側是操作參數,符號右側是操作表達式
Lambda表達式是 jdk1.8 提供的新特性
2.2 Model Code as Data(MCAD模式)
Model Code as Data,編碼即數據,盡可能輕量級的將代碼封裝為數據
解決方案:接口&實現類(匿名內部類)
存在問題:語法冗余、thIs關鍵字、變量捕獲、數據控制等
傳統模式下,新線程的創建:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threading ..." + Thread.currentThread().getName());
}
}).start();
使用jdk8新特性,lambda表達式優化線程模型
new Thread(() -> {
System.out.println("threading ..." + Thread.currentThread().getName());
}).start();
2.3 項目問題:功能接口的設計及優化
需求環境:線程類的創建
解決方案:匿名內部類的實現
解決方法:lambda表達式實現
2.4 為什么要使用lambda表達式
它不是解決未知問題的新技術
對現有解決方案的語義化優化
需要根據實際需求考慮性能問題
3. Lambda表達式的基礎知識
3.1 函數式接口概述和定義
函數式接口,就是java類型系統中的接口
函數式接口,是只包含一個接口方法的特殊接口
語義化檢測注解:@FunctionalInterface
可以像定義普通接口一樣定義函數式接口,并且接口內只有一個抽象方法:
@FunctionalInterface
public interface IUserCredential {
/**
* 通過用戶賬號,驗證用戶身份信息的接口
* @param username 要驗證的用戶賬號
* @return 返回身份信息[系統管理員、用戶管理員、普通用戶]
*/
String verifyUser(String username);
// 添加這個方法后,會報錯
// boolean test();
由于接口添加了@FunctionalInterface注解,表明是一個函數式接口,內部只能有一個抽象方法,如果再添加一個抽象方法boolean test(),就會報錯:Multiple non-overriding abstract methods found in interface com.imooc.IUserCredential
注意:@FunctionalInterface 注解也可以不加,函數式接口只需要滿足以下兩個條件即可:
定義一個接口
接口中只有一個抽象方法
3.2 默認方法和靜態方法
1. 接口的默認方法:default關鍵字修飾
@FunctionalInterface
public interface IUserCredential {
/**
* 通過用戶賬號,驗證用戶身份信息的接口
* @param username 要驗證的用戶賬號
* @return 返回身份信息[系統管理員、用戶管理員、普通用戶]
*/
String verifyUser(String username);
/**
* 接口的默認方法
*/
default String getCredential(String username) {
// 模擬方法
if ("admin".equals(username)) {
return "admin + 系統管理員用戶";
} else if("manager".equals(username)){
return "manager + 用戶管理員用戶";
} else {
return "commons + 普通會員用戶";
}
}
}
接口的默認方法使用default關鍵字修飾,調用時,需要用接口的實例調用:
IUserCredential instance = new xxx(); // 實例化接口中的實現類
instance.getCredential("admin");
2. 接口的靜態方法:static關鍵字修飾
@FunctionalInterface
public interface IUserCredential {
/**
* 通過用戶賬號,驗證用戶身份信息的接口
* @param username 要驗證的用戶賬號
* @return 返回身份信息[系統管理員、用戶管理員、普通用戶]
*/
String verifyUser(String username);
/**
* 接口的靜態方法
*/
static String getCredential(String username) {
// 模擬方法
if ("admin".equals(username)) {
return "admin + 系統管理員用戶";
} else if("manager".equals(username)){
return "manager + 用戶管理員用戶";
} else {
return "commons + 普通會員用戶";
}
}
}
同普通類的靜態方法一樣,接口的靜態方法在調用時,直接調用即可,不需要實例化接口實例:
// 直接使用 接口名.方法名 調用
IUserCredential.getCredential("admin");
3. 來自Object繼承的方法
由于接口或類都是Object的子類,如果我們在接口中增加一個由Object類繼承過來的抽象方法,接口依然不會報錯:
@FunctionalInterface
public interface IUserCredential {
/**
* 通過用戶賬號,驗證用戶身份信息的接口
* @param username 要驗證的用戶賬號
* @return 返回身份信息[系統管理員、用戶管理員、普通用戶]
*/
String verifyUser(String username);
/**
* 這里的toString()方法是Object類繼承的,添加后并不會報錯
* @return
*/
@Override
String toString();
}
以上接口雖然有兩個抽象類,但由于toString()方法是從Object類繼承的,因此并不會報錯,該接口依然是一個函數式接口。
3.3 Lambda表達式和函數式接口的關系
在jdk1.8之前,我們使用匿名內部類,實現接口的抽象方法:
IUserCredential ic2 = new IUserCredential() {
@Override
public String verifyUser(String username) {
return "admin".equals(username)?"管理員":"會員";
}
};
在jdk1.8,使用lambda表達式,針對函數式接口的簡單實現
IUserCredential ic3 = (String username) -> {
return "admin".equals(username) ? "lbd管理員" : "lbd會員";
};
lambda表達式 是 函數式接口的一種實現.
3.4 jdk中常見的函數式接口
在java.util.function提供了大量的函數式接口:
Predicate 接收參數T對象,返回一個boolean類型結果
@FunctionalInterface
public interface Predicate {
boolean test(T t);
// 省略靜態方法和默認方法
}
Consumer 接收參數T對象,沒有返回值
@FunctionalInterface
public interface Consumer {
void accept(T t);
// 省略靜態方法和默認方法
}
Function 接收參數T對象,返回R對象
@FunctionalInterface
public interface Function {
R apply(T t);
// 省略靜態方法和默認方法
}
Supplier 不接受任何參數,直接通過get()獲取指定類型的對象
@FunctionalInterface
public interface Supplier {
T get();
}
UnaryOperator 接口參數T對象,執行業務處理后,返回更新后的T對象
@FunctionalInterface
public interface UnaryOperator extends Function {
static UnaryOperator identity() {
return t -> t;
}
}
BinaryOperator 接口接收兩個T對象,執行業務處理后,返回一個T對象
public interface BiFunction {
R apply(T t, U u);
// 省略靜態方法和默認方法
}
@FunctionalInterface
public interface BinaryOperator extends BiFunction {
// 省略靜態方法和默認方法
}
示例:
// 1. Predicate 接收參數T對象,返回一個boolean類型結果
Predicate pre = (String username) -> {
return "admin".equals(username);
};
System.out.println(pre.test("manager"));
// 2. Consumer 接收參數T對象,沒有返回值
Consumer con = (String message) -> {
System.out.println("要發送的消息:" + message);
System.out.println("消息發送完成");
};
con.accept("hello 慕課網的學員們..");
// 3. Function 接收參數T對象,返回R對象
Function fun = (String gender) -> {
return "male".equals(gender) ? 1 : 0;
};
System.out.println(fun.apply("male"));
// 4. Supplier 不接受任何參數,直接通過get()獲取指定類型的對象
Supplier sup = () -> {
return UUID.randomUUID().toString();
};
System.out.println(sup.get());
// 5. UnaryOperator 接口參數T對象,執行業務處理后,返回更新后的T對象
UnaryOperator uo = (String img)-> {
img += "[100x200]";
return img;
};
System.out.println(uo.apply("原圖--"));
// 6. BinaryOperator 接口接收兩個T對象,執行業務處理后,返回一個T對象
BinaryOperator bo = (Integer i1, Integer i2) -> {
return i1 > i2? i1: i2;
};
System.out.println(bo.apply(12, 13));
3.5 Lambda表達式基本語法
聲明:就是和lambda表達式綁定的接口類型
參數:包含在一對圓括號中,和綁定的接口中的抽象方法中的參數個數及順序一致。
操作符:->
執行代碼塊:包含在一對大括號中,出現在操作符號的右側
示例如下:
首先定義3個接口:
// 1. 沒有參數,沒有返回值的lambda表達式綁定的接口
interface ILambda1{
void test();
}
// 2. 帶有參數,沒有返回值的lambda表達式
interface ILambda2{
void test(String name, int age);
}
// 3. 帶有參數,帶有返回值的lambda表達式
interface ILambda3 {
int test(int x, int y);
}
編寫Lambda表達式示例:
// 1. [接口聲明] = (參數) -> {執行代碼塊};
ILambda1 i1 = () -> {
System.out.println("hello imooc!");
System.out.println("welcome to imooc!");
};
i1.test();
// 2. lambda表達式的返回值,如果代碼塊只有一行,并且沒有大括號,不用寫return關鍵字,單行代碼的執行結果,會自動返回
ILambda1 i2 = () -> System.out.println("hello imooc");
i2.test();
// 3. 帶有多個參數的Lambda表達式
ILambda2 i21 = (String n, int a) -> {
System.out.println(n + "say: my year's old is " + a);
};
i21.test("jerry", 18);
// 4. 不寫參數類型,由jvm自動推導
ILambda2 i22 = (n, a) -> {
System.out.println(n + " 說:我今年" + a + "歲了.");
};
i22.test("tom", 22);
// 5. 帶有返回值的Lambda表達式
ILambda3 i3 = (x, y) -> {
int z = x + y;
return z;
};
System.out.println(i3.test(11, 22));
// 6. 只有一行時,可以省略大括號和return字段
ILambda3 i31 = (x, y) -> x + y;
System.out.println(i31.test(100, 200));
小結:
lambda表達式,必須和接口進行綁定。
lambda表達式的參數,可以附帶0個到n個參數,括號中的參數類型可以不用指定,jvm在運行時,會自動根據綁定的抽象方法中參數進行推導。
lambda表達式的返回值,如果代碼塊只有一行,并且沒有大括號,不用寫return關鍵字,單行代碼的執行結果,會自動返回。如果添加了大括號,或者有多行代碼,必須通過return關鍵字返回執行結果。
3.6 變量捕獲——變量的訪問操作
1. 匿名內部類型中對于變量的訪問
public void testInnerClass() {
String s2 = "局部變量";
new Thread(new Runnable() {
String s3 = "內部變量";
@Override
public void run() {
// 訪問全局變量
// System.out.println(this.s1); // 無法訪問s1,這里的this關鍵字表示是當前內部類型的對象
System.out.println(s1);
System.out.println(s2); // 局部變量的訪問
// s2 = "hello"; // 不能對局部變量進行數據的修改[final]
System.out.println(s3);
System.out.println(this.s3);
}
}).start();
}
在匿名內部類中,
this關鍵字表示的是當前內部類型的對象
局部變量的訪問時,不能對局部變量進行數據的修改(默認為final類型)
2. lambda表達式變量捕獲
public void testLambda() {
String s2 = "局部變量lambda";
new Thread(() -> {
String s3 = "內部變量lambda";
// 訪問全局變量
System.out.println(this.s1);// this關鍵字,表示的就是所屬方法所在類型的對象
// 訪問局部變量
System.out.println(s2);
// s2 = "hello";// 不能進行數據修改,默認推導變量的修飾符:final
System.out.println(s3);
s3 = "labmda 內部變量直接修改";
System.out.println(s3);
}).start();
}
在Lambda表達式中,
this關鍵字,表示的就是所屬方法所在類型的對象
修改局部時,同樣會報錯:默認推導變量的修飾符為final
3.7 Lambda表達式類型檢查
首先定義個函數式接口:
@FunctionalInterface
interface MyInterface {
R strategy (T t, R r);
}
定義方法:
public static void test(MyInterface inter) {
List list = inter.strategy("hello", new ArrayList());
System.out.println(list);
}
匿名內部類調用:
test(new MyInterface() {
@Override
public List strategy(String s, List list) {
list.add(s);
return list;
}
});
Lambda表達式調用:
test((x, y) -> {
y.add(x);
return y;
});
Lambda表達式方法推導:
(x,y)->{..} --> test(param) --> param==MyInterface --> lambda表達式-> MyInterface類型
這個就是對于lambda表達式的類型檢查,MyInterface接口就是lambda表達式的目標類型(target typing)
Lambda表達式方法參數推導:
(x,y)->{..} --> MyInterface.strategy(T r, R r)--> MyInterface inter
--> T==String R==List --> lambda--> (x, y) == strategy(T t , R r)--> x==T==String y==R==List
lambda表達式參數的類型檢查
3.8 方法重載和Lambda表達式
先定義兩個函數式接口:
interface Param1 {
void outInfo(String info);
}
interface Param2 {
void outInfo(String info);
}
再定義兩個重載方法:
// 定義重載的方法
public void lambdaMethod(Param1 param) {
param.outInfo("hello param1 imooc!");
}
public void lambdaMethod(Param2 param) {
param.outInfo("hello param2 imooc");
}
使用匿名內部類調用:
app.lambdaMethod(new Param1() {
@Override
public void outInfo(String info) {
System.out.println(info);
}
});
app.lambdaMethod(new Param2() {
@Override
public void outInfo(String info) {
System.out.println("------");
System.out.println(info);
}
});
這里能正常運行,但是在使用Lambda表達式時,會有問題:
app.lambdaMethod( (String info) -> {
System.out.println(info);
});
異常信息:
Ambiguous method call. Both
lambdaMethod(Param1) in Test?and
lambdaMethod(Param2) in Test?match
jvm對Lambda表達式調用的方法推導如下:
lambda表達式存在類型檢查-> 自動推導lambda表達式的目標類型
lambdaMethod() -> 方法 -> 重載方法
-> Param1 函數式接口
-> Param2 函數式接口
調用方法-> 傳遞Lambda表達式-> 自動推導->
-> Param1 | Param2
因此,在調用時,需要人為地告訴jvm我們要調用的方法參數是啥:
app.lambdaMethod((Param1) (String info) -> {
System.out.println(info);
});
3.9 深入理解lambda表達式
Lambda表達式在jvm謹慎解析在私有靜態方法和匿名內部類型,通過實現接口的匿名內部類型中接口方法調用靜態實現方法,完成Lambda表達式的執行。
先準備一個App.java類:
// 函數式接口
interface IMarkUp {
void markUp(String msg);
}
public class App {
public static void main(String [] args) {
IMarkUp mu = (message) -> System.out.println(message);
mu.markUp("lambda!");
}
}
使用javac 編譯下,再使用javap查看:
$ javac App.java
$ javap -p App.class
Compiled from "App.java"
public class App {
public App();
public static void main(java.lang.String[]);
private static void lambda$main$0(java.lang.String);
}
可以看到,自動生成了私有靜態方法private static void lambda$main$0(java.lang.String),Lambda在實際運行時,也是生成了一個私有靜態方法:
private static void lambda$main$0(String message) {
System.out.println(message);
}
為了查看編譯過程,我們使用參數-Djdk.internal.lambda.dumpProxyClasses重新處理:
$ java -Djdk.internal.lambda.dumpProxyClasses App
lambda!
運行后,發現多生成了一個類:App$$Lambda$1.class,使用javap -p App$$Lambda$1查看類的信息:
$ javap -p App$$Lambda$1
final class App$$Lambda$1 implements IMarkUp {
private App$$Lambda$1();
public void markUp(java.lang.String);
}
這里的markUp(java.lang.String)方法實際調用的是 lambda$main$0:
public void markUp(String message) {
App.lambda$main$0(message);
}
總結下lambda表達式的底層執行過程:
在編譯時,會自動生成私有靜態方法 private static void lambda$main$0(java.lang.String)
在編譯時,會自動生成實現類:final class App$$Lambda$1 implements IMarkUp
調用mu.markUp("lambda!"),實際上進行的操作是new App$$Lambda$1().markUp("lambda!")
代碼如下:
interface IMarkUp {
void markUp(String msg);
}
public class App {
public static void main(String [] args) {
IMarkUp mu = (message) -> System.out.println(message);
mu.markUp("lambda!");
// 3. 實際調用: new App$$Lambda$1().markUp("lambda!");
}
// 1. 自動生成的私有靜態方法
/*
private static void lambda$main$0(String message) {
System.out.println(message);
}
*/
// 2. 自動生成的內部類
/*
final class App$$Lambda$1 implements IMarkUp {
private App$$Lambda$1() {
}
public void markUp(String message) {
App.lambda$main$0(message);
}
}
*/
}
4. Lambda表達式在集合中的運用
4.1 方法引用
方法引用是結合Lambda表達式的一種語法特性,主要分為靜態方法引用、實例方法引用和構造方法引用。
先準備一個POJO:
@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
private String name;
private String gender;
private int age;
// 靜態方法引用
public static int compareByAge(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
再準備一些數據:
List list = new ArrayList();
list.add(new Person("shuke", "男", 29));
list.add(new Person("tom", "男", 16));
list.add(new Person("jerry", "男", 20));
list.add(new Person("beita", "女", 30));
1. 靜態方法引用
匿名內部類實現排序:
Collections.sort(list, new Comparator() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
lambda表達式實現排序
Collections.sort(list, (p1, p2) -> p1.getAge() - p2.getAge());
方法引用實現排序
// 使用::操作符引用Person類的靜態方法compareByAge()
Collections.sort(list, Person::compareByAge);
2. 實例方法引用
添加一個類,準備實例方法:
class PersonUtil {
// 實例方法引用
public int comprareByName(Person p1, Person p2) {
return p1.getName().hashCode() - p2.getName().hashCode();
}
}
實例方法引用:
PersonUtil pu = new PersonUtil();
Collections.sort(list, pu::comprareByName);
3. 構造方法引用
準備一個函數式接口:
interface IPerson {
Person getPerson(String name, String gender, int age);
}
使用方式如下:
/*
// 匿名內部類方式
IPerson p1 = new IPerson() {
@Override
public Person getPerson(String name, String gender, int age) {
return new Person(name, gender, age);
}
}
// lambda表達式方式
IPerson p1 = (name, gender, age) -> new Person(name, gender, age);
*/
// 綁定構造方法,實際調用的構造方法是 Person(String, String, int)
IPerson p1 = Person::new;
// 調用接口方法
Person person = p1.getPerson("tom", "男", 18);
4.2 Stream概述
首先準備數據:
List list = new ArrayList();
list.add("tom");
list.add("jerry");
list.add("shuke");
list.add("beita");
list.add("damu");
現在有這樣的處理要求:找出升序大于5的有效賬號
第一種方式:增強for遍歷
List lista = new ArrayList();
for (String s : list) {
if (s.length() > 3) {
lista.add(s);
}
}
System.out.println(lista);
第二種方式:Iterator遍歷
List listb = new ArrayList<>();
Iterator it = list.iterator();
while(it.hasNext()) {
String s = it.next();
if(s.length() > 3) {
listb.add(s);
}
}
System.out.println(listb);
第三種方式:使用 stream 實現
List listc = list.stream().filter(s->s.length()>3).collect(Collectors.toList());
System.out.println(listc);
4.3 Stream常見操作API介紹
4.3.1 聚合操作
4.3.2 stream的處理流程
數據源
數據轉換
獲取結果
4.3.3 獲取Stream對象
從集合或者數組中獲取[**]
Collection.stream(),如accounts.stream()
Collection.parallelStream()
Arrays.stream(T t)
BufferReader
BufferReader.lines()-> stream()
靜態工廠
java.util.stream.IntStream.range()..
java.nio.file.Files.walk()..
自定構建
java.util.Spliterator
更多的方式..
Random.ints()
Pattern.splitAsStream()..
4.3.4 中間操作API{intermediate}
操作結果是一個Stream,中間操作可以有一個或者多個連續的中間操作,需要注意的是,中間操作只記錄操作方式,不做具體執行,直到結束操作發生時,才做數據的最終執行。
中間操作:就是業務邏輯處理。
中間操作過程:
無狀態:數據處理時,不受前置中間操作的影響,如:map/filter/peek/parallel/sequential/unordered
有狀態:數據處理時,受到前置中間操作的影響,如:distinct/sorted/limit/skip
4.3.5 終結操作|結束操作{Terminal}
需要注意的是,一個Stream對象,只能有一個Terminal操作,這個操作一旦發生,就會真實處理數據,生成對應的處理結果。
終結操作又可區分為非短路操作和短路操作,
非短路操作:當前的Stream對象必須處理完集合中所有 數據,才能得到處理結果,如:forEach/forEachOrdered/toArray/reduce/collect/min/max/count/iterator
短路操作:當前的Stream對象在處理過程中,一旦滿足某個條件,就可以得到結果,如:anyMatch/allMatch/noneMatch/findFirst/findAny等,Short-circuiting,無限大的Stream-> 有限大的Stream。
4.4 Stream操作集合數中的數據-上
4.4.1 獲取stream
多個數據
Stream stream = Stream.of("admin", "tom", "damu");
數組
String [] strArrays = new String[] {"xueqi", "biyao"};
Stream stream2 = Arrays.stream(strArrays);
列表
List list = new ArrayList<>();
list.add("少林");
list.add("武當");
list.add("青城");
list.add("崆峒");
list.add("峨眉");
Stream stream3 = list.stream();
集合
Set set = new HashSet<>();
set.add("少林羅漢拳");
set.add("武當長拳");
set.add("青城劍法");
Stream stream4 = set.stream();
Map
Map map = new HashMap<>();
map.put("tom", 1000);
map.put("jerry", 1200);
map.put("shuke", 1000);
Stream stream5 = map.entrySet().stream();
4.4.2 Stream對象對于基本數據類型的功能封裝
// int / long / double
IntStream.of(new int[] {10, 20, 30}).forEach(System.out::println);
// range方法:[1, 5),左閉右開
IntStream.range(1, 5).forEach(System.out::println);
// rangeClosed:[1, 5],左半右閉
IntStream.rangeClosed(1, 5).forEach(System.out::println);
4.4.3 Stream對象 --> 轉換得到指定的數據類型
// 數組
Object [] objx = stream.toArray(String[]::new);
// 字符串
String str = stream.collect(Collectors.joining()).toString();
// 列表
List listx = (List) stream.collect(Collectors.toList());
// 集合
Set setx = (Set) stream.collect(Collectors.toSet());
// Map
Map mapx = (Map) stream.collect(Collectors.toMap(x->x, y->"value:"+y));
4.5 Stream操作集合數中的數據-下
1. Stream中間操作
List accountList = new ArrayList<>();
accountList.add("xongjiang");
accountList.add("lujunyi");
accountList.add("wuyong");
accountList.add("linchong");
accountList.add("luzhishen");
accountList.add("likui");
accountList.add("wusong");
// map() 中間操作,map()方法接收一個Functional接口
accountList = accountList.stream().map(x->"梁山好漢:" + x).collect(Collectors.toList());
// filter() 添加過濾條件,過濾符合條件的用戶
accountList = accountList.stream().filter(x-> x.length() > 5).collect(Collectors.toList());
// forEach 增強型循環
accountList.forEach(x-> System.out.println("forEach->" + x));
// peek() 中間操作,迭代數據完成數據的依次處理過程
accountList.stream()
.peek(x -> System.out.println("peek 1: " + x))
.peek(x -> System.out.println("peek 2:" + x))
.forEach(System.out::println);
2. Stream中對于數字運算的支持
List intList = new ArrayList<>();
intList.add(20);
intList.add(19);
intList.add(7);
intList.add(8);
intList.add(86);
intList.add(11);
intList.add(3);
intList.add(20);
// skip() 中間操作,有狀態,跳過部分數據
intList.stream().skip(3).forEach(System.out::println);
// limit() 中間操作,有狀態,限制輸出數據量
intList.stream().skip(3).limit(2).forEach(System.out::println);
// distinct() 中間操作,有狀態,剔除重復的數據
intList.stream().distinct().forEach(System.out::println);
// sorted() 中間操作,有狀態,排序
// max() 獲取最大值
Optional optional = intList.stream().max((x, y)-> x-y);
System.out.println(optional.get());
// min() 獲取最小值
// reduce() 合并處理數據
Optional optional2 = intList.stream().reduce((sum, x)-> sum + x);
System.out.println(optional2.get());
5. Lambda表達式在實際生產中的應用
5.1 Lambda表達式重構項目
可以使用Lambda表達式簡化項目中的代碼。
5.2 Lambda表達式和Stream性能問題
我們主要從兩個方面進行性能比較:基本數據類型與復雜數據類型。
5.2.1 基本數據類型的性能比較
package java8;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Random;
/**
* {這里添加描述}
*
* @author funcy
* @date 2020-01-18 8:57 下午
*/
public class Test02 {
public static void main(String[] args) {
Random random = new Random();
// 1. 基本數據類型:整數
List integerList = new ArrayList();
for (int i = 0; i < 1000000; i++) {
integerList.add(random.nextInt(Integer.MAX_VALUE));
}
// 1) stream
testStream(integerList);
// 2) parallelStream
testParallelStream(integerList);
// 3) 普通for
testForLoop(integerList);
// 4) 增強型for
testStrongForLoop(integerList);
// 5) 迭代器
testIterator(integerList);
}
public static void testStream(List list) {
long start = System.currentTimeMillis();
Optional optional = list.stream().max(Integer::compare);
System.out.println(optional.get());
long end = System.currentTimeMillis();
System.out.println("testStream:" + (end - start) + "ms");
}
public static void testParallelStream(List list) {
long start = System.currentTimeMillis();
Optional optional = list.parallelStream().max(Integer::compare);
System.out.println(optional.get());
long end = System.currentTimeMillis();
System.out.println("testParallelStream:" + (end - start) + "ms");
}
public static void testForLoop(List list) {
long start = System.currentTimeMillis();
int max = Integer.MIN_VALUE;
for (int i = 0; i < list.size(); i++) {
int current = list.get(i);
if (current > max) {
max = current;
}
}
System.out.println(max);
long end = System.currentTimeMillis();
System.out.println("testForLoop:" + (end - start) + "ms");
}
public static void testStrongForLoop(List list) {
long start = System.currentTimeMillis();
int max = Integer.MIN_VALUE;
for (Integer integer : list) {
if (integer > max) {
max = integer;
}
}
System.out.println(max);
long end = System.currentTimeMillis();
System.out.println("testStrongForLoop:" + (end - start) + "ms");
}
public static void testIterator(List list) {
long start = System.currentTimeMillis();
Iterator it = list.iterator();
int max = it.next();
while (it.hasNext()) {
int current = it.next();
if (current > max) {
max = current;
}
}
System.out.println(max);
long end = System.currentTimeMillis();
System.out.println("testIterator:" + (end - start) + "ms");
}
}
運行結果如下:
2147480897
testStream:88ms
2147480897
testParallelStream:28ms
2147480897
testForLoop:9ms
2147480897
testStrongForLoop:11ms
2147480897
testIterator:15ms
5.2.2 復雜數據類型的性能
package java8;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Random;
/**
* {這里添加描述}
*
* @author funcy
* @date 2020-01-18 9:11 下午
*/
public class Test03 {
public static void main(String[] args) {
Random random = new Random();
List productList = new ArrayList<>();
for(int i = 0; i < 1000000; i++) {
productList.add(new Product("pro" + i, i, random.nextInt(Integer.MAX_VALUE)));
}
// 調用執行
testProductStream(productList);
testProductParallelStream(productList);
testProductForloop(productList);
testProductStrongForloop(productList);
testProductIterator(productList);
}
public static void testProductStream(List list) {
long start = System.currentTimeMillis();
Optional optional = list.stream().max((p1, p2)-> p1.hot - p2.hot);
System.out.println(optional.get());
long end = System.currentTimeMillis();
System.out.println("testProductStream:" + (end - start) + "ms");
}
public static void testProductParallelStream(List list) {
long start = System.currentTimeMillis();
Optional optional = list.stream().max((p1, p2)-> p1.hot - p2.hot);
System.out.println(optional.get());
long end = System.currentTimeMillis();
System.out.println("testProductParallelStream:" + (end - start) + "ms");
}
public static void testProductForloop(List list) {
long start = System.currentTimeMillis();
Product maxHot = list.get(0);
for(int i = 0; i < list.size(); i++) {
Product current = list.get(i);
if (current.hot > maxHot.hot) {
maxHot = current;
}
}
System.out.println(maxHot);
long end = System.currentTimeMillis();
System.out.println("testProductForloop:" + (end - start) + "ms");
}
public static void testProductStrongForloop(List list) {
long start = System.currentTimeMillis();
Product maxHot = list.get(0);
for (Product product : list) {
if(product.hot > maxHot.hot) {
maxHot = product;
}
}
System.out.println(maxHot);
long end = System.currentTimeMillis();
System.out.println("testProductStrongForloop:" + (end - start) + "ms");
}
public static void testProductIterator(List list) {
long start = System.currentTimeMillis();
Iterator it = list.iterator();
Product maxHot = it.next();
while(it.hasNext()) {
Product current = it.next();
if (current.hot > maxHot.hot) {
maxHot = current;
}
}
System.out.println(maxHot);
long end = System.currentTimeMillis();
System.out.println("testProductIterator:" + (end - start) + "ms");
}
}
class Product {
String name; // 名稱
Integer stock; // 庫存
Integer hot; // 熱度
public Product(String name, Integer stock, Integer hot) {
this.name = name;
this.stock = stock;
this.hot = hot;
}
}
運行結果:
java8.Product@5f184fc6
testProductStream:63ms
java8.Product@5f184fc6
testProductParallelStream:15ms
java8.Product@5f184fc6
testProductForloop:16ms
java8.Product@5f184fc6
testProductStrongForloop:16ms
java8.Product@5f184fc6
testProductIterator:17ms
5.2.3 結論
jvm相關人員也對stream進行了一系列測,結果如下:
可以看到,隨著核心數增加,并行Stream帶來的性能提升是非常明顯的。
最終,我們可以得到這樣一個結論:對于簡單數據的迭代處理,可以直接通過外部迭代進行操作,如果在性能上有一定的要求,可以使用并行stream進行操作;對于復雜對象的處理操作,stream的串行操作在性能上已經和普通的迭代相差無幾,甚至超過了普通的迭代方式,完全可以用簡潔的stream的語法來替換普通的迭代操作,如果在性能上有要求,可以直接選擇并行stream操作以提升性能,并行stream在多核條件下,更能發揮其性能優勢。
5.3 線程安全問題
這一節我們來看看并行stream(parallelStream)的線程安全:
package java8;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* {這里添加描述}
*
* @author funcy
* @date 2020-01-18 9:30 下午
*/
public class Test04 {
public static void main(String[] args) {
// 整數列表
List lists = new ArrayList();
// 增加數據
for (int i = 0; i < 1000; i++){
lists.add(i);
}
// 串行Stream
List list2 = new ArrayList<>();
lists.stream().forEach(x->list2.add(x));
System.out.println(lists.size());
System.out.println(list2.size());
// 并行Stream
List list3 = new ArrayList<>();
lists.parallelStream().forEach(x-> list3.add(x));
System.out.println(list3.size());
// stream的collect操作
List list4 = lists.parallelStream().collect(Collectors.toList());
System.out.println(list4.size());
}
}
運行結果如下:
1000
1000
994
1000
可以看到,lists.parallelStream().forEach(x-> list3.add(x)) 會引發線程安全問題,而lists.parallelStream().collect(Collectors.toList())不會引起線程安全問題。
關于stream的collect操作,官方文檔有云:當并行 執行時,可以實例化,填充和合并多個中間結果,以便保持可變結構的隔離。因此,即使與非線程安全的數據結構(例如ArrayList)并行執行,并行還原也不需要額外的同步。
結論:并行stream的線程安全問題,在業務處理的過程中,主要通過自定義編碼添加線程鎖的方式,或者使用stream api中提供的線程安全的終端操作來完成執行過程。不過,在更多的場景中,如果我們遇到多線程問題,會直接使用線程安全的集合來規范數據源。
總結
以上是生活随笔為你收集整理的java8 lambda python_【学习笔记】java8 Lambda表达式语法及应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通风帽采取防止凝结水滴落的措施有哪些
- 下一篇: python中readline的用法_p