深度掌握 Java Stream 流操作,讓你的代碼高出一個逼格!
一、介紹
我們都知道,從 Java8 開始,jdk 新增加了一個 Stream 類,用來補充集合類,它的強大,相信用過它的朋友,能明顯的感受到,不用使用for循環(huán)就能對集合作出很好的操作。
Stream 使用一種類似用 SQL 語句從數(shù)據(jù)庫查詢數(shù)據(jù)的直觀方式來提供一種對 Java 集合運算和表達(dá)的高階抽象。
這種風(fēng)格將要處理的元素集合看作一種流, 流在管道中傳輸, 并且可以在管道的節(jié)點上進(jìn)行處理, 比如篩選, 排序,聚合等。
元素流在管道中經(jīng)過中間操作(intermediate operation)的處理,最后由最終操作(terminal operation)得到前面處理的結(jié)果。
操作流程如下:
+--------------------+ +------+ +------+ +---+ +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+
采用 Stream API 可以極大提高 Java 程序員的生產(chǎn)力,讓程序員寫出高效率、干凈、簡潔的代碼。
下面,我們就以實際的日常開發(fā)編程風(fēng)格做對比,學(xué)習(xí)完 Stream 的編程風(fēng)格之后,我敢保證,你會愛上它!
二、遍歷操作
2.1、遍歷集合
日常開發(fā)中,我們經(jīng)常需要需要遍歷集合對象中的元素,例如,我們會采用如下方式進(jìn)行遍歷元素,然后過濾出某個字段的集合,jdk7 的操作:
/**
* jdk7 從集合對象中獲取用戶ID集合
* @param userList
* @return
*/
public List<Long> getUserIds(List<User> userList){
List<Long> userIds = new ArrayList<>();
for (User user : userList) {
userIds.add(user.getUserId());
}
return userIds;
}
當(dāng)采用 Stream 編程之后,只需要通過一行代碼,即可實現(xiàn):
/**
* jdk8 從集合對象中獲取用戶ID集合
* @param userList
* @return
*/
public List<Long> getUserIds(List<User> userList){
List<Long> userIds = userList.stream().map(User::getUserId).collect(Collectors.toList());
return userIds;
}
2.2、篩選元素
篩選元素,是日常開發(fā)中經(jīng)常會碰到,例如在 jdk7,我們會這么操作:
/**
* jdk7 從集合對象中過濾出用戶ID不為空的數(shù)據(jù)
* @param userList
* @return
*/
public List<Long> getUserIds7(List<User> userList){
List<Long> userIds = new ArrayList<>();
for (User user : userList) {
if(user.getUserId() != null){
userIds.add(user.getUserId());
}
}
return userIds;
}
采用 Stream api,我們只需要通過filter方法來篩選出需要的數(shù)據(jù),即可過濾出用戶ID不為空的數(shù)據(jù)。
/**
* jdk8 從集合對象中篩選出用戶ID不為空的數(shù)據(jù)
* @param userList
* @return
*/
public List<Long> getUserIds8(List<User> userList){
List<Long> userIds = userList.stream().filter(item -> item.getUserId() != null).map(User::getUserId).collect(Collectors.toList());
return userIds;
}
2.3、刪除重復(fù)的內(nèi)容
如果你想對返回的集合內(nèi)容排除重復(fù)的數(shù)據(jù),操作也很簡單,在合并的時候使用Collectors.toSet()即可!
/**
* jdk8 從集合對象中篩選出用戶ID不為空的數(shù)據(jù),并進(jìn)行去重
* @param userList
* @return
*/
public Set<Long> getUserIds(List<User> userList){
Set<Long> userIds = userList.stream().filter(item -> item.getUserId() != null).map(User::getUserId).collect(Collectors.toSet());
return userIds;
}
2.4、數(shù)據(jù)類型轉(zhuǎn)換
在實際的開發(fā)過程中,經(jīng)常會出現(xiàn)數(shù)據(jù)類型定義不一致的問題,例如有的系統(tǒng),使用String接受,有的是用Long,對于這種場景,我們需要將其轉(zhuǎn)換,操作也很簡單
/**
* jdk8 將Long類型數(shù)據(jù)轉(zhuǎn)換成String類型
* @param userIds
* @return
*/
public List<String> getUserIds10(List<Long> userIds){
List<String> userIdStrs = userIds.stream().map(x -> x.toString()).collect(Collectors.toList());
return userIdStrs;
}
2.5、數(shù)組轉(zhuǎn)集合
我們還會碰到,前端傳給我們的是一個數(shù)組,但是我們需要轉(zhuǎn)成集合,采用 stream api 操作也很簡單!
public static void main(String[] args) {
//創(chuàng)建一個字符串?dāng)?shù)組
String[] strArray = new String[]{"a","b","c"};
//轉(zhuǎn)換后的List 屬于 java.util.ArrayList 能進(jìn)行正常的增刪查操作
List<String> strList = Stream.of(strArray).collect(Collectors.toList());
}
三、集合轉(zhuǎn)Map操作
在實際的開發(fā)過程中,還有一個使用最頻繁的操作就是,將集合元素中某個主鍵字段作為key,元素作為value,來實現(xiàn)集合轉(zhuǎn)map的需求,這種需求在數(shù)據(jù)組裝方面使用的非常多,尤其是在禁止連表 sql 查詢操作的公司,視圖數(shù)據(jù)的拼裝只能在代碼層面來實現(xiàn)。
例如下面這段代碼,角色表里面關(guān)聯(lián)角色組ID信息,當(dāng)查詢角色信息的時候,需要把角色組名稱也展示處理,采用map方式來匹配,效率會非常高。
實際代碼案例分享
//角色組ID集合
Set<Long> roleGroupIds = new HashSet<>();
//查詢所有的角色信息
List<RoleInfo> dbList = roleInfoMapper.findByPage(request);
for (RoleInfo source : dbList) {
roleGroupIds.add(source.getRoleGroupId());
RoleInfoDto result = new RoleInfoDto();
BeanUtils.copyProperties(source, result);
resultList.add(result);
}
//查詢角色組信息
if (CollectionUtils.isNotEmpty(roleGroupIds)) {
List<RoleGroupInfo> roleGroupInfoList = roleGroupInfoMapper.selectByIds(new ArrayList<>(roleGroupIds));
if (CollectionUtils.isNotEmpty(roleGroupInfoList)) {
//將List轉(zhuǎn)換成Map,其中id主鍵作為key,對象作為value
Map<Long, RoleGroupInfo> sourceMap = new HashMap<>();
for (RoleGroupInfo roleGroupInfo : roleGroupInfoList) {
sourceMap.put(roleGroupInfo.getId(), roleGroupInfo);
}
//封裝角色組名稱
for (RoleInfoDto result : resultList) {
if (sourceMap.containsKey(result.getRoleGroupId())) {
result.setRoleGroupName(sourceMap.get(result.getRoleGroupId()).getName());
}
}
}
}
3.1、集合轉(zhuǎn) map(不分組)
在 jdk7 中,將集合中的元素轉(zhuǎn) map,我們通常會采用如下方式。
/**
* jdk7 將集合轉(zhuǎn)換成Map,其中用戶ID作為主鍵key
* @param userList
* @return
*/
public Map<Long, User> getMap(List<User> userList){
Map<Long, User> userMap = new HashMap<>();
for (User user : userList) {
userMap.put(user.getUserId(), user);
}
return userMap;
}
在 jdk8 中,采用 stream api的方式,我們只需要一行代碼即可實現(xiàn)
/**
* jdk8 將集合轉(zhuǎn)換成Map,其中用戶ID作為主鍵key,如果集合對象有重復(fù)的key,以第一個匹配到的為主
* @param userList
* @return
*/
public Map<Long, User> getMap(List<User> userList){
Map<Long, User> userMap = userList.stream().collect(Collectors.toMap(User::getUserId, v -> v, (k1,k2) -> k1));
return userMap;
}
打開Collectors.toMap方法源碼,一起來看看到底是啥。
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
從參數(shù)表可以看出:
- 第一個參數(shù):表示 key
- 第二個參數(shù):表示 value
- 第三個參數(shù):表示某種規(guī)則
上文中的Collectors.toMap(User::getUserId, v -> v, (k1,k2) -> k1),表達(dá)的意思就是將userId的內(nèi)容作為key,v -> v是表示將元素user作為value,其中(k1,k2) -> k1表示如果存在相同的key,將第一個匹配的元素作為內(nèi)容,第二個舍棄!
3.2、集合轉(zhuǎn)map(分組)
在實際的操作中,有一些場景需要我們將相同的key,加入到一個集合,而不是覆蓋,哪改如何做呢?
如果是采用 jdk7,我們大概會這么做。
/**
* jdk7 將集合轉(zhuǎn)換成Map,將相同的key,加入到一個集合中,實現(xiàn)分組
* @param userList
* @return
*/
public Map<Long, List<User>> getMapGroup(List<User> userList){
Map<Long, List<User>> userListMap = new HashMap<>();
for (User user : userList) {
if(userListMap.containsKey(user.getUserId())){
userListMap.get(user.getUserId()).add(user);
} else {
List<User> users = new ArrayList<>();
users.add(user);
userListMap.put(user.getUserId(), users);
}
}
return userListMap;
}
而在 jdk8 中,采用 stream api的方式,我們只需要一行代碼即可實現(xiàn)
/**
* jdk8 將集合轉(zhuǎn)換成Map,將相同的key,加入到一個集合中,實現(xiàn)分組
* @param userList
* @return
*/
public Map<Long, List<User>> getMapGroup(List<User> userList){
Map<Long, List<User>> userMap = userList.stream().collect(Collectors.groupingBy(User::getUserId));
return userMap;
}
四、分頁操作
stream api 的強大之處還不僅僅是對集合進(jìn)行各種組合操作,還支持分頁操作。
例如,將如下的數(shù)組從小到大進(jìn)行排序,排序完成之后,從第1行開始,查詢10條數(shù)據(jù)出來,操作如下:
//需要查詢的數(shù)據(jù)
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5,10, 6, 20, 30, 40, 50, 60, 100);
List<Integer> dataList= numbers.stream().sorted((x, y) -> x.compareTo(y)).skip(0).limit(10).collect(Collectors.toList());
System.out.println(dataList.toString());
其中skip參數(shù)表示第幾行,limit表示查詢的數(shù)量,類似頁容量!
五、查找與匹配操作
stream api 還支持對集合進(jìn)行查找,同時還支持正則匹配模式。
- allMatch(檢查是否匹配所有元素)
List<Integer> list = Arrays.asList(10, 5, 7, 3);
boolean allMatch = list.stream()//
.allMatch(x -> x > 2);//是否全部元素都大于2
System.out.println(allMatch);
- findFirst(返回第一個元素)
List<Integer> list = Arrays.asList(10, 5, 7, 3);
Optional<Integer> first = list.stream()//
.findFirst();
Integer val = first.get();
System.out.println(val);//輸出10
- reduce(可以將流中元素反復(fù)結(jié)合起來,得到一個值)
List<Integer> list = Arrays.asList(10, 5, 7, 3);
Integer result = list.stream()//
.reduce(2, Integer::sum);
System.out.println(result);//輸出27,其實相當(dāng)于2+10+5+7+3,就是一個累加
stream api 支持的操作方法非常多,這里只列舉了幾種類型,具體在使用的時候,可以參考官網(wǎng)接口文檔說明!
六、并行操作
所謂并行,指的是多個任務(wù)在同一時間點發(fā)生,并由不同的cpu進(jìn)行處理,不互相搶占資源;而并發(fā),指的是多個任務(wù)在同一時間點內(nèi)同時發(fā)生了,但由同一個cpu進(jìn)行處理,互相搶占資源。
這點上大家一定要區(qū)分清楚,別弄混了!
stream api 的并行操作和串行操作,只有一個方法區(qū)別,其他都一樣,例如下面我們使用parallelStream來輸出空字符串的數(shù)量:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 采用并行計算方法,獲取空字符串的數(shù)量
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
在實際使用的時候,并行操作不一定比串行操作快!對于簡單操作,數(shù)量非常大,同時服務(wù)器是多核的話,建議使用Stream并行!反之,采用串行操作更可靠!