Java 8 引入的Stream API提供了一種新的數(shù)據(jù)處理方式,它以聲明式、函數(shù)式的編程模型,極大地簡化了對(duì)集合、數(shù)組或其他支持?jǐn)?shù)據(jù)源的操作。Stream可以被看作是一系列元素的流水線。允許你高效地對(duì)大量數(shù)據(jù)執(zhí)行復(fù)雜的過濾、映射、排序、聚合等操作,而無需顯式地使用循環(huán)或者臨時(shí)變量。Stream API的設(shè)計(jì)理念主要包括兩個(gè)方面:鏈?zhǔn)秸{(diào)用和惰性求值。鏈?zhǔn)秸{(diào)用允許我們將多個(gè)操作連接在一起,形成一個(gè)流水線,而惰性求值意味著只有在真正需要結(jié)果的時(shí)候才執(zhí)行計(jì)算,從而避免了不必要的計(jì)算開銷。
接下來我們就來盤點(diǎn)一下日常開發(fā)中常用的一些Stream API。
創(chuàng)建Stream
- 集合創(chuàng)建
List<String> list = new ArrayList<>();
// 串行流
Stream<String> stream = list.stream();
// 并行流
Stream<String> parallelStream = list.parallelStream();
- 數(shù)組創(chuàng)建
String[] strs = new String[3];
Stream<String> stream = Arrays.stream(strs);
- 使用
Stream.of(T...values)
創(chuàng)建
Stream<String> stream = Stream.of("Apple", "Orange", "Banana");
- 使用Stream.generate()創(chuàng)建流
// 生成一個(gè)無限流,通過limit()限制元素個(gè)數(shù)
Stream<Double> randomStream = Stream.generate(Math::random).limit(5);
- 使用Stream.iterate()創(chuàng)建流
// 生成一個(gè)等差數(shù)列,通過limit()限制元素個(gè)數(shù)
Stream<Integer> integerStream = Stream.iterate(0, n -> n + 2).limit(5);
- 使用IntStream、LongStream、DoubleStream創(chuàng)建原始類型流
// 使用IntStream創(chuàng)建
IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]
// 使用LongStream創(chuàng)建
LongStream longStream = LongStream.rangeClosed(1, 5); // [1, 2, 3, 4, 5]
IntStream我們使用的地方還是比較多的,比如我們按照下標(biāo)遍歷一個(gè)集合時(shí),同常的做法是:for(int i = 0; i < list.size(); i++){},我們可以使用IntStream去改造一下,IntStream.rangeClosed(0, list.size()).forEach();
中間操作
中間操作是構(gòu)建流水線的一部分,用于對(duì)流進(jìn)行轉(zhuǎn)換和處理,但它們并不會(huì)觸發(fā)實(shí)際的計(jì)算。
- 過濾操作(filter)
過濾操作用于篩選流中的元素,保留滿足指定條件的元素。Stream<T> filter(Predicate<? super T> predicate)
,filter
接受一個(gè)謂詞Predicate,我們可以通過這個(gè)謂詞定義篩選條件,Predicate
是一個(gè)函數(shù)式接口,其包含一個(gè)test(T t)
方法,該方法返回boolean。
private static void filterTest(){
List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape");
// 過濾長度大于5的水果
List<String> filteredFruits = fruits.stream().filter(fruit -> fruit.length() > 5).collect(Collectors.toList());
System.out.println("長度大于5的水果: "+ filteredFruits);
}
private static void filterTest(List<Student> students){
List<Student> filterStudents = students.stream()
.filter(student -> Objects.equals("武漢大學(xué)", student.getSchool()))
.collect(Collectors.toList());
filterStudents.forEach(System.out::println);
}
打印結(jié)果:
- 映射操作(map/flatMap)
映射操作用于對(duì)流中的每個(gè)元素進(jìn)行轉(zhuǎn)換。他有map以及flatMap兩種操作。map就是基本的映射操作,對(duì)每個(gè)元素進(jìn)行提取轉(zhuǎn)換。
// 將實(shí)體層映射成學(xué)生姓名字符串
List<String> names = students.stream()
.map(Student::getName)
.collect(Collectors.toList());
// 將字符串轉(zhuǎn)大寫。
List<String> upperList = Lists.newArrayList("hello", "world", "stream", "api").stream().map(String::toUpperCase).collect(Collectors.toList());
日常開發(fā)中map操作我們用的非常多,比如數(shù)據(jù)庫中查詢出來的DO實(shí)體,我們需要轉(zhuǎn)換為VO返回給前端頁面展示,這時(shí)候我們可以使用map進(jìn)行轉(zhuǎn)換操作:
List<StudentDO> studentDOList = studentMapper.listStudents();
List<StudentVO> studentVOList = studentDOList.stream().map(studentDO -> {
StudentVO studentVO = StudentVO.builder().studentNo(studentDO.getId())
.studentName(studentDO.getName()).build();
return studentVO;
}).collect(Collectors.toList());
而flatMap的作用略微特殊,它用于將一個(gè)元素映射為一個(gè)流,然后將所有流連接成一個(gè)流。這在處理嵌套結(jié)構(gòu)或集合中的元素是另一個(gè)集合的情況下非常有用。
List<List<String>> nestedWords = Arrays.asList(
Arrays.asList("Java", "Kotlin"),
Arrays.asList("Python", "Ruby"),
Arrays.asList("JavaScript", "TypeScript")
);
// 使用 flatMap 將嵌套的 List<String> 轉(zhuǎn)換為一個(gè)扁平的 List<String>, 結(jié)果將是包含所有單詞的扁平流
List<String> wordList = nestedWords.stream()
.flatMap(List::stream).collect(Collectors.toList());
System.out.println(wordList);
// 打印結(jié)果: [Java, Kotlin, Python, Ruby, JavaScript, TypeScript]
flatMap
在使用時(shí),通常會(huì)涉及到處理復(fù)雜的數(shù)據(jù)結(jié)構(gòu),比如處理嵌套的對(duì)象集合或者進(jìn)行數(shù)據(jù)的扁平化。
@Data
@Builder
class Student {
private String name;
private List<Integer> grades;
}
@Data
@Builder
class ClassRoom {
private List<Student> studentList;
}
@Data
@Builder
class School {
private List<ClassRoom> classRoomList;
}
School school = School.builder()
.classRoomList(Lists.newArrayList(
ClassRoom.builder().studentList(Lists.newArrayList(
Student.builder().name("Alice").gradeList(Lists.newArrayList(90, 85, 88)).build(),
Student.builder().name("Bob").gradeList(Lists.newArrayList(78, 92, 80)).build()
)).build(),
ClassRoom.builder().studentList(Lists.newArrayList(
Student.builder().name("Charlie").gradeList(Lists.newArrayList(95, 89, 91)).build(),
Student.builder().name("David").gradeList(Lists.newArrayList(82, 87, 79)).build()
)).build()
))
.build();
// 使用 flatMap 扁平化處理獲取所有學(xué)生的所有課程成績
List<Integer> allGrades = school.getClassRoomList().stream()
.flatMap(classroom -> classroom.getStudentList().stream())
.flatMap(student -> student.getGradeList().stream())
.collect(Collectors.toList());
System.out.println(allGrades);
// 打印結(jié)果:[90, 85, 88, 78, 92, 80, 95, 89, 91, 82, 87, 79]
- mapToInt操作
mapToInt
是 Stream API 中的一種映射操作,專門用于將元素映射為IntStream
。通過mapToInt
,你可以將流中的元素映射為int
類型,從而進(jìn)行更專門化的操作,例如數(shù)值計(jì)算。
int totalAge2 = students.stream().mapToInt(Student::getAge).sum();
類似的還有mapToLong
和mapToDouble
操作,這兩個(gè)操作類似于 mapToInt
,分別用于將流中的元素映射為 LongStream
和 DoubleStream
。
- 排序操作(sorted)
排序操作用于對(duì)流中的元素進(jìn)行排序。
List<String> cities = Lists.newArrayList("New York", "Tokyo", "London", "Paris");
// 對(duì)城市按字母順序排序
List<String> sortedStream = cities.stream().sorted().collect(Collectors.toList());
對(duì)于集合中對(duì)象的排序,sorted要求待比較的元素必須實(shí)現(xiàn)Comparable接口。
@Data
@Builder
static class Student implements Comparable<Student>{
private String name;
private Integer age;
@Override
public int compareTo(Student other) {
return other.getAge()-this.getAge();
}
}
List<String> sortedList = students.stream()
.sorted()
.map(Student::getName())
.collect(Collectors.toList());
如果沒有實(shí)現(xiàn),就需要將比較器作為參數(shù)傳遞給sorted(Comparator<? super T> comparator)
。
@Data
@Builder
static class Student {
private String name;
private Integer age;
}
List<String> sortedList = students.stream()
.sorted((student1,student2) -> student2.getAge() - student1.getAge())
.map(Student::getName())
.collect(Collectors.toList());
- 去重操作(distinct)
去重操作用于去除流中的重復(fù)元素。distinct基于Object.equals(Object)實(shí)現(xiàn)。
List<Integer> numbers = Lists.newArrayList(1, 2, 3, 2, 4, 5, 3, 6);
// 去除重復(fù)的數(shù)字
List<Integer> distinctList = numbers.stream().distinct().collect(Collectors.toList());
// 或者去除學(xué)生中姓名相同的
List<String> studentNameList = students.stream()
.map(Student::getName())
.distinct()
.collect(Collectors.toList());
- 截?cái)嗖僮鳎╨imit)
截?cái)嗖僮饔糜谙拗屏髦性氐臄?shù)量。limit返回包含前n個(gè)元素的流,當(dāng)集合大小小于n時(shí),則返回實(shí)際長度。
List<Integer> numbers = Lists.newArrayList(1, 2, 3, 2, 4, 5, 3, 6);
// 只取前三個(gè)數(shù)字
List<Integer> limitedList = numbers.stream().limit(3).collect(Collectors.toList());
// 取土工工程專業(yè)的年齡最小的前兩名學(xué)生
List<Student> limitStu = students.stream()
.filter(student -> Objects.equals("土木工程", student.getMajor()))
.sorted((student1,student2) -> student2.getAge() - student1.getAge())
.limit(2)
.collect(Collectors.toList());
- 跳過操作(skip)
跳過操作用于跳過流中的前幾個(gè)元素,返回由后面所有元素構(gòu)造的流,如果n大于滿足條件的集合的長度,則會(huì)返回一個(gè)空的集合。作用上跟limit相反。
List<Integer> numbers = Lists.newArrayList(1, 2, 3, 2, 4, 5, 3, 6);
// 跳過前三個(gè)數(shù)字,返回后面的數(shù)字
List<Integer> limitedList = numbers.stream().skip(3).collect(Collectors.toList());
// 跳過土工工程專業(yè)的年齡最小的前兩名學(xué)生,取后面的學(xué)生
List<Student> limitStu = students.stream()
.filter(student -> Objects.equals("土木工程", student.getMajor()))
.sorted((student1,student2) -> student2.getAge() - student1.getAge())
.skip(2)
.collect(Collectors.toList());
- peek操作
peek
方法對(duì)每個(gè)元素執(zhí)行操作并返回一個(gè)新的 Stream。peek
的主要目的是用于調(diào)試和觀察流中的元素,通常用于打印調(diào)試信息、記錄日志或其他類似的目的,而不會(huì)改變流中元素的結(jié)構(gòu)。
List<String> words = Arrays.asList("apple", "banana", "orange", "grape");
List<String> modifiedWords = words.stream()
.filter(word -> word.length() > 5)
.peek(word -> System.out.println("Filtered Word: " + word))
.map(String::toUpperCase)
.peek(word -> System.out.println("Uppercase Word: " + word))
.collect(Collectors.toList());
Stream的終端操作
終端操作是對(duì)流進(jìn)行最終計(jì)算的操作,執(zhí)行終端操作后,流將被消耗,不能再被使用。
- 迭代forEach操作
forEach
迭代操作,用于對(duì)流中的每個(gè)元素執(zhí)行指定的操作。
List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape");
// 使用 forEach 輸出每個(gè)水果
fruits.stream().forEach(fruit -> System.out.println(fruit));
// 執(zhí)行forEach時(shí)可省略 stream(),即
fruits.forEach(fruit -> System.out.println(fruit));
// 或
fruits.stream().forEach(System.out::println);
- 收集操作(collect)
通過collect()
方法結(jié)合java.util.stream.Collectors
工具類將Stream轉(zhuǎn)換為另一種形式,例如列表、集合(toList, toSet, toMap)、映射或歸約結(jié)果。如上述示例中的:
- 收集到List
使用Collectors.toList()
。
// 跳過土工工程專業(yè)的年齡最小的前兩名學(xué)生,取后面的學(xué)生
List<Student> limitStu = students.stream()
.filter(student -> Objects.equals("土木工程", student.getMajor()))
.sorted((student1,student2) -> student2.getAge() - student1.getAge())
.skip(2)
.collect(Collectors.toList());
- 收集到Set
使用Collectors.toSet()
。
// 將學(xué)生姓名收集到Set
Set<String> studentNameSet = students.stream().map(Student::getName)
.collect(Collectors.toSet());
- List轉(zhuǎn)Map
使用Collectors.toMap
。日常開發(fā)中使用很多。
// 轉(zhuǎn)換為年齡對(duì)應(yīng)的學(xué)生信息
Map<Integer, Student> studentMap = students.stream().collect(Collectors.toMap(
Student::getAge,
Function.identity(),
(e1,e2) -> e1));
這段代碼代表,我們使用年齡作為Map的key,對(duì)應(yīng)學(xué)生信息作為value。Function.identity()
:這是一個(gè)提取元素自身的映射函數(shù)。(e1, e2) -> e1
:這是一個(gè)合并沖突的操作。如果在流中存在相同的年齡(相同的鍵),這個(gè)函數(shù)定義了當(dāng)出現(xiàn)重復(fù)鍵時(shí)應(yīng)該如何處理。在這里,我們選擇保留第一個(gè)出現(xiàn)的元素,即保留先出現(xiàn)的 Student
對(duì)象。當(dāng)然我們還可以這樣(e1, e2) -> {...}
自定義合并沖突策略,例如:
// 轉(zhuǎn)換為年齡對(duì)應(yīng)的學(xué)生信息,如果年齡相同,則取名字較長的
Map<Integer, Student> studentMap = students.stream().collect(Collectors.toMap(Student::getAge, Function.identity(), (e1,e2) -> {
return e1.getName().length() > e2.getName().length() ? e1 : e2;
}));
如果value的值是一些number,我們也可以做一些加減乘除之類的合并。
日常開發(fā)中,這個(gè)用法很頻繁。
- 字符串拼接:
使用Collectors.joining(拼接符)
。
List<Student> students = Lists.newArrayList(
Student.builder().name("Alice").gradeList(Lists.newArrayList(90, 85, 88)).build(),
Student.builder().name("Bob").gradeList(Lists.newArrayList(78, 92, 80)).build()
);
String studentName = students.stream().map(Student::getName).collect(Collectors.joining(","));
// 打印出來:Alice,Bob
- 分組
即按照集合中的元素的某個(gè)屬性進(jìn)行分組,轉(zhuǎn)換為Map<Object, List<Object>>
:
List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape");
Map<Integer, List<String>> lengthToNamesMap = fruits.stream()
.collect(Collectors.groupingBy(String::length));
// 按照年齡分組
Map<Integer, List<Student>> studentMap = students.stream().collect(Collectors.groupingBy(Student::getAge));
// 連續(xù)進(jìn)行分組
Map<String,Map<String,List<Student>>> groupsStudent = students.stream()
// 先按照學(xué)校分組
.collect(Collectors.groupingBy(Student::getSchool
// 再按照專業(yè)分組
,Collectors.groupingBy(Student::getMajor)));
- counting()
counting()
收集器用于計(jì)算流中元素的數(shù)量。等同于Stream的count()
操作。
long studentCount = students.stream().collect(Collectors.counting());
// 效果同等于
long studentCount = students.stream().count();
- maxBy()
maxBy()
基于指定的比較器,用于找到流中的最大的元素。等同于Stream的max
操作
// 年齡最大的學(xué)生
Student olderStudent = students.stream()
.collect(Collectors.maxBy((s1,s2) -> s1.getAge()- s2.getAge())).orElse(null);
Student olderStudent2 = students.stream()
.collect(Collectors.maxBy(Comparator.comparing(Student::getAge))).orElse(null);
// 等價(jià)于stram的max
Student olderStudent = students.stream()
.max(Comparator.comparing(Student::getAge)).orElse(null);
- minBy()
minBy()
基于指定的比較器,用于找到流中的最小的元素。等同于Stream的min
操作。
// 年齡最小的學(xué)生
Student youngStudent = students.stream()
.collect(Collectors.minBy(Comparator.comparing(Student::getAge))).orElse(null);
Student youngStudent = students.stream()
.min(Comparator.comparing(Student::getAge)).orElse(null);
- averagingInt
averagingInt()
收集器用于計(jì)算流中元素的平均值。
// 求學(xué)生平均年齡
double avgAge = students.stream()
.collect(Collectors.averagingInt(Student::getAge));
- summarizingInt()
summarizingInt()
收集器用于計(jì)算流中元素的匯總統(tǒng)計(jì)信息,包括總數(shù)、平均值、最大值和最小值。
// 一次性得到元素個(gè)數(shù)、總和、均值、最大值、最小值
IntSummaryStatistics summaryStatistics = students.stream().collect(Collectors.summarizingInt(Student::getAge));
System.out.println("總數(shù):" + summaryStatistics.getCount());
System.out.println("平均值:" + summaryStatistics.getAverage());
System.out.println("最大值:" + summaryStatistics.getMax());
System.out.println("最小值:" + summaryStatistics.getMin());
- partitioningBy()
將流中的元素按照指定的條件分成兩個(gè)部分。在分區(qū)中key只有兩種情況:true或false,目的是將待分區(qū)集合按照條件一分為二,分區(qū)相對(duì)分組的優(yōu)勢在于,我們可以同時(shí)得到兩類結(jié)果,在一些應(yīng)用場景下可以一步得到我們需要的所有結(jié)果,比如將數(shù)組分為奇數(shù)和偶數(shù)。
// 分為武漢大學(xué)學(xué)生,非武漢大學(xué)學(xué)生
Map<Boolean,List<Student>> partStudent = students.stream()
.collect(Collectors.partitioningBy(student -> Objects.equals("武漢大學(xué)",student.getSchool())));
- count操作
count
用于計(jì)算流中的元素個(gè)數(shù)。效果等同于Collectors.counting()
。
long studentCount = students.stream().count();
// 效果同等于
long studentCount = students.stream().collect(Collectors.counting());
- max操作
基于指定比較器,max用于找到流中最大的元素。效果等同于Collectors.maxBy()
。
Student olderStudent = students.stream()
.max(Comparator.comparing(Student::getAge)).orElse(null);
Student olderStudent2 = students.stream()
.collect(Collectors.maxBy(Comparator.comparing(Student::getAge))).orElse(null);
- min操作
基于指定比較器,min用于找到流中最小的元素。效果等同于Collectors.minBy()
。
Student youngStudent = students.stream()
.min(Comparator.comparing(Student::getAge)).orElse(null);
// 年齡最小的學(xué)生
Student youngStudent = students.stream()
.collect(Collectors.minBy(Comparator.comparing(Student::getAge))).orElse(null);
- reduce操作
reduce
用于對(duì)流中的元素進(jìn)行歸約操作,得到一個(gè)最終的結(jié)果。
// 計(jì)算學(xué)生的總年齡
int totalAge1 = students.stream()
.map(Student::getAge)
.reduce(0, (a,b) -> a+b);
// 也可以使用Integer.sum
int totalAge2 = students.stream()
.map(Student::getAge)
.reduce(0, Integer::sum);
// 也可以不設(shè)置初始值0,直接Integer.sum,但是返回的是Optional
int totalAge3 = students.stream()
.map(Student::getAge)
.reduce(Integer::sum).orElse(0);
- findFirst操作
findFirst
用于查找流中的第一個(gè)元素。也即list.get(0)
。
Student firstStu = students.stream()
.filter(student -> Objects.equals("土木工程", student.getMajor()))
.findFirst().orElse(null);
曾經(jīng)有個(gè)小兄弟問我,他有一段代碼類似 Student firstStu = students.get(0)。他們組長讓他優(yōu)化優(yōu)化,然后就用了這種方式優(yōu)化的。??
- findAny操作
findAny
用于查找流中的任意一個(gè)元素。在并行流中,findAny
可以更快地獲取結(jié)果,而在串行流中與findFirst
的效果基本一致。
Student anyStu = students.stream()
.filter(student ->Objects.equals("土木工程", student.getMajor()))
.findAny().orElse(null);
- anyMatch操作
anyMatch則是檢測是否存在一個(gè)或多個(gè)滿足指定的參數(shù)行為,如果滿足則返回true。
boolean hasQh = students.stream()
.anyMatch(student -> Objects.equals("清華大學(xué)", student.getSchool()));
- noneMatch
noneMatch用于檢測是否不存在滿足指定行為的元素,如果不存在則返回true.
boolean hasBd = students.stream()
.noneMatch(student -> Objects.equals("北京大學(xué)", student.getSchool()));
- allMatch
allMatch用于檢測是否全部都滿足指定的參數(shù)行為,如果全部滿足則返回true。
boolean isAdult = students.stream()
.allMatch(student -> student.getAge() > 18);
并行流
在Java 8及以上版本,你可以使用并行流(Parallel Stream)來充分利用多核處理器的能力。并行流在處理大量數(shù)據(jù)時(shí)可以提高性能,但并不是在所有情況下都比順序流更快。當(dāng)在并行流上進(jìn)行操作時(shí),需要注意并發(fā)問題。確保你的操作是無狀態(tài)的、無副作用的,或者使用合適的并發(fā)工具。一定一定要注意線程安全。并行流本質(zhì)上基于java7的Fork-Join框架實(shí)現(xiàn),其默認(rèn)的線程數(shù)為宿主機(jī)的內(nèi)核數(shù)。
創(chuàng)建并行流,只需要將stream()替換成parallelStream()即可。
List<Student> list = studentMapper.listStudents();
Stream<Student> parallelStream = students.parallelStream();
與順序流相似,你可以在并行流上執(zhí)行各種中間和終端操作。
日常中,對(duì)于大批量的數(shù)據(jù)處理轉(zhuǎn)換,我們可以使用并行流去處理。我們可以先把數(shù)據(jù)切分成100或者其他數(shù)值一組的List<List<Student>>
然后使用并行流去處理這些數(shù)據(jù)。
List<StudentVO> studentVOList = Collections.synchronizedList(Lists.newArrayList());
Lists.partition(students, 100).parallelStream().forEach(pList -> {
// 處理轉(zhuǎn)換數(shù)據(jù)
List<StudentVO> voList = convertList(pList);
studentVOList.addAll(voList);
});
再比如一些大批量的數(shù)據(jù)分批次查詢,都可以使用并行流去做,但是一定要注意線程安全。
注意事項(xiàng)
使用Stream API可使Java集合處理更簡潔、清晰,充分發(fā)揮現(xiàn)代、函數(shù)式編程的優(yōu)勢。然而,需注意Stream的惰性求值,只在終端操作觸發(fā)時(shí)執(zhí)行中間操作,確保操作的必要性,避免不必要計(jì)算。Stream鼓勵(lì)無狀態(tài)、無副作用的操作,避免在中間操作修改共享狀態(tài),以確保流的預(yù)測性和可維護(hù)性。Stream不可重用,一旦被消費(fèi),無法再次使用,需謹(jǐn)慎設(shè)計(jì)流程。并行流雖提高性能,但需謹(jǐn)慎使用,不適用于所有情況,可能導(dǎo)致額外性能開銷。
總結(jié)
Java 8中引入的Stream API為開發(fā)者帶來了全新的編程范式。其鏈?zhǔn)秸{(diào)用和惰性求值的設(shè)計(jì)理念,使得數(shù)據(jù)處理變得更為簡單和高效。通過深入理解Stream API,我們能夠更好地利用這一強(qiáng)大工具,在實(shí)際開發(fā)中寫出更為優(yōu)雅和易讀的代碼。文章來源:http://www.zghlxwxcb.cn/news/detail-825303.html
本文已收錄于我的個(gè)人博客:碼農(nóng)Academy的博客,專注分享Java技術(shù)干貨,包括Java基礎(chǔ)、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中間件、架構(gòu)設(shè)計(jì)、面試題、程序員攻略等文章來源地址http://www.zghlxwxcb.cn/news/detail-825303.html
到了這里,關(guān)于提高Java開發(fā)生產(chǎn)力,我選Stream API,真香啊的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!