스트림의 중간 작업은 필터 필터링, 맵 매핑 변환, 플랫맵 병합, 개별 중복 제거, 정렬 정렬 및 기타 작업을 포함하여 스트림 체인의 데이터 처리 작업을 의미합니다. 이러한 작업은 여러 중간 작업을 연결하여 복잡한 데이터 처리를 수행할 수 있는 새로운 Stream 개체를 반환합니다. 중간 작업에는 종료 작업이 트리거되어야 한다는 점에 유의해야 합니다.
다음은 Stream의 일반적인 중간 작업을 카테고리별로 설명합니다.
filter() 메서드는 데이터 필터링을 구현하는 데 자주 사용됩니다. 즉, 컬렉션 및 배열과 같은 데이터 소스에서 지정된 조건을 충족하는 요소를 필터링할 수 있습니다. 그리고 새로운 스트림을 반환합니다.
블랙리스트에 있는 휴대폰 번호 목록이 있고 "133"으로 시작하는 모든 요소를 필터링해야 한다고 가정합니다. 그런 다음 filter()를 사용하여 이를 달성할 수 있습니다. -
//将数组转换为一个字符串列表 List<String> numbers = Arrays.asList("13378520000","13278520000","13178520000","13358520000"); //通过stream()方法创建一个流,接着使用filter()方法过滤出前缀为“133”的元素,最终通过collect() 方法将结果收集到一个新列表中 List<String> filterdNumbers = numbers.stream().filter(s -> s.startsWith("133")).collect(Collectors.toList()); System.out.println(filterdNumbers); //打印结果:[13378520000, 13358520000]
map() 메소드는 스트림의 각 요소를 매핑하고, 이를 다른 요소로 변환하거나, 여기에서 정보를 추출하고, 새 스트림을 반환하는 데 사용됩니다.
다음 두 가지 경우에 따라 요소를 다른 요소로 변환하고 요소의 정보를 추출하는 map()을 학습합니다. -
1.2.1 요소 변환
휴대폰 번호 문자 목록이 있다고 가정합니다. , 처음 7자리를 기반으로 휴대폰 번호의 위치를 확인하려면 모든 휴대폰 번호의 처음 7개 하위 문자열을 가져와야 합니다. 이를 달성하려면 map() 메서드를 사용할 수 있습니다.
List<String> numbers = Arrays.asList("13378520000","13278520000","13178520000","13558520000"); //通过stream()方法创建一个流,使用map()方法将每个字符串转换为截取前7位的字符,最后使用collect()方法将结果收集到一个新列表中 List<String> filterdNumbers = numbers.stream().map(s -> s.substring(0,7)).collect(Collectors.toList()); System.out.println(filterdNumbers); //打印结果:[1337852, 1327852, 1317852, 1355852]
1.2 .2. 요소 정보 추출
사용자 개체 목록이 있다고 가정하면 map() 메서드를 사용하여 각 개체의 휴대전화 번호를 추출해야 합니다:
List<People> peopleList = Arrays.asList( new People("王二","13378520000"), new People("李二","13278520000"), new People("张四","13178520000") ); //通过stream()方法创建一个流,使用map()方法提取每个用户的手机号,最后使用collect()方法将结果收集到一个新列表中 List<String> tel = peopleList.stream().map(People::getTel).collect(Collectors.toList()); System.out.println(tel); //打印结果:[13378520000, 13278520000, 13178520000]
flatMap() 메서드는 다대다 매핑을 달성하거나 여러 목록을 하나의 목록 작업으로 병합할 수 있습니다.
1.3.1 다대다 매핑 구현
잔액 목록 A와 B가 있다고 가정합니다. 그룹 A의 각 요소를 그룹 B의 모든 요소에 순차적으로 추가해야 합니다. 이를 달성하려면 flatMap을 사용하세요. -
List<Integer> listA = Arrays.asList(1, 2, 3); List<Integer> listB = Arrays.asList(4, 5, 6); List<Integer> list = listA.stream().flatMap(a -> listB.stream().map(b -> a +b)).collect(Collectors.toList()); System.out.println(list); //打印结果: [5, 6, 7, 6, 7, 8, 7, 8, 9]
1.3.2 여러 목록을 하나의 목록으로 병합
여러 개의 휴대폰 번호 문자열 목록이 포함된 목록이 있다고 가정합니다. into A 목록은 flatMap() 메서드를 사용하여 구현할 수 있습니다:
List<List<String>> listOfLists = Arrays.asList( Arrays.asList("13378520000", "13278520000"), Arrays.asList("13178520000", "13558520000"), Arrays.asList("15138510000", "15228310000") ); List<String> flatMapList = listOfLists.stream().flatMap(Collection::stream).collect(Collectors.toList()); System.out.println(flatMapList); //打印结果:[13378520000, 13278520000, 13178520000, 13558520000, 15138510000, 15228310000]
distinct() 메서드는 스트림에서 중복 요소를 제거하고 중복 없는 목록을 생성하는 데 사용할 수 있습니다.
반복되는 휴대전화 번호 문자열이 포함된 목록이 있다고 가정하면, Unique()를 사용하여 작업을 중복 제거할 수 있습니다. -
List<String> numbers = Arrays.asList("13378520000", "15138510000","13178520000", "15138510000"); List<String> disNumbers = numbers.stream().distinct().collect(Collectors.toList()); System.out.println(disNumbers); //打印结果:[13378520000, 15138510000, 13178520000]
한 가지 주의할 점은, 스트림 중복을 제거하기 위해 구별을 사용할 때 요소를 결정해야 한다는 것입니다. 스트림 equals() 및 hashCode() 메소드가 구현된 이유는 이 두 메소드가 두 객체가 동일한지 여부를 판단하는 기준이기 때문입니다.
sorted() 메서드는 스트림의 요소를 정렬하는 데 사용됩니다.
People 개체 그룹을 연령별로 정렬해야 한다고 가정합니다. 다음은 각각 오름차순과 내림차순으로 정렬됩니다. -
1.5.1, 오름차순
기본적으로 오름차순 - 오름차순으로 정렬됩니다. 주문 -
List<People> peopleList = Arrays.asList( new People("王二",20), new People("李二",30), new People("张四",31) ); List<People> newpeopleList=peopleList.stream().sorted(Comparator.comparing(People::getAge)).collect(Collectors.toList()); //打印结果 newpeopleList.stream().forEach(System.out::println);
인쇄 결과:
People{name='王二', age=20}
People{name='leetwo', age=30}
People{name='Zhang Si', age= 31}
1.5.2. 내림차순 정렬
reversed() 메소드를 사용하여 역순으로 정렬합니다. 즉, 오름차순으로 정렬합니다— ', age=30}
People{name='王二', age=20}1.6, peek: 각 요소의 정보를 볼 수 있지만 흐름을 수정하지는 않습니다. 스트림의 요소 상태13378520000
peek() 메서드는 스트림의 요소 상태를 수정하지 않고 스트림의 요소를 보는 데 사용됩니다. 이 메서드는 스트림의 모든 단계에서 사용할 수 있으며 스트림 작동에 영향을 주거나 스트림 작동을 종료하지 않습니다.인쇄 결과:List<People> peopleList = Arrays.asList( new People("王二",20), new People("李二",30), new People("张四",31) ); List<People> newpeopleList = peopleList.stream().sorted(Comparator.comparing(People::getAge).reversed()).collect(Collectors.toList()); //打印结果 newpeopleList.stream().forEach(System.out::println);로그인 후 복사
133
13278520000132
peek() 메서드는 forEach와 매우 유사하며 스트림의 요소를 순회하는 데 사용할 수 있습니다. 그러나 둘 사이에는 큰 차이가 있습니다. . 요점은 forEach가 스트림의 종료 작업이라는 것입니다. 이는 스트림이 처리되었으며 더 이상 작업을 수행할 수 없음을 의미합니다. forEach 이후 스트림, 그러나 peek 메서드는 괜찮습니다. 위의 경우에서 볼 수 있듯이 요소를 인쇄하기 위해 처음으로 peek를 호출한 후 요소 뒤에는 요소의 처음 세 자리를 가로채는 맵 작업이 이어질 수도 있습니다. 끈.
이것이 peek() 메서드와 forEach의 가장 큰 차이점입니다.1.7、limit 和 skip:截取流中的部分元素
limit()和skip()都是用于截取Stream流中部分元素的方法,两者区别在于,limit()返回一个包含前n个元素的新流,skip()则返回一个丢弃前n个元素后剩余元素组成的新流。
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; System.out.print("取数组前5个元素:"); Arrays.stream(arr).limit(5).forEach(n -> System.out.print(n + " ")); // 输出结果为:1 2 3 4 5 System.out.print("跳过前3个元素,取剩余数组元素:"); Arrays.stream(arr).skip(3).forEach(n -> System.out.print(n + " ")); // 输出结果为:4 5 6 7 8 9 10로그인 후 복사二、Stream终止操作
Stream的终止操作是指执行Stream流链中最后一个步骤,到这一步就会结束整个流处理。在Java8中,Stream终止操作包括forEach、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst和findAny等。这些终止操作都有返回值。需要注意一点是,如果没有执行终止操作的话,Stream流是不会触发执行的,例如,一个没有终止操作的peek()方法代码是不会执行进而打印——
list.stream().peek(t -> System.out.println("ddd"))로그인 후 복사当加上终止操作话,例如加上collect,就会打印出“ddd”——
list.stream().peek(t -> System.out.println("ddd")).collect(Collectors.toList());로그인 후 복사下面按类别分别讲解各个终止操作的使用。
2.1、forEach:遍历流中的每个元素
该forEach前面已经提到,这里不做过多介绍。
2.2、count:统计流中元素的数量
count可以统计流中元素的数量并返回结果。
假设有一个包含多个手机号字符串的列表,需要统计去重后的手机号数量,就可以使用count方法——
List<String> numbers = Arrays.asList("13378520000", "15138510000","13178520000", "15138510000"); long count = numbers.stream() .distinct()//去重 .count();//统计去重后的手机号 System.out.println(count); //打印结果:3로그인 후 복사2.3、reduce:将流中的所有元素归约成一个结果
reduce()可以将流中的所有元素根据指定规则归约成一个结果,并将该结果返回。
常用语法格式如下:
Optional<T> result = stream.reduce(BinaryOperator<T> accumulator);로그인 후 복사可见,reduce方法会返回一个Optional类型的值,表示归约后的结果,需要通过get()方法获取Optional里的值。
假设有一个包含多个手机号字符串的List列表,需要在去重之后,再将列表所有字符串拼按照逗号间隔接成一个字符串返回,那么就可以通过reduce来实现——
List<String> numbers = Arrays.asList("13378520000", "15138510000","13178520000", "15138510000"); Optional result = numbers.stream() .distinct() //去重 .reduce((a ,b) -> a+","+b);//指定规则为,相临两个字符通过逗号“,”间隔 System.out.println(result.get()); //打印结果:13378520000,15138510000,13178520000로그인 후 복사2.4、collect:将流中的元素收集到一个容器中,并返回该容器
collect的作用是将流中的元素收集到一个新的容器中,返回该容器。打个比喻,它就像一个采摘水果的工人,负责将水果一个个采摘下来,然后放进一个篮子里,最后将篮子交给你。我在前面的案例当中,基本都有用到collect,例如前面2.1的filter过滤用法中的List filterdNumbers = numbers.stream().filter(s -> s.startsWith("133")).collect(Collectors.toList()),就是将过滤出前缀为“133”的字符串,将这些过滤处理后的元素交给collect这个终止操作。这时collect就像采摘水果的员工,把采摘为前缀“133”的“水果”通过toList()方法收集到一个新的List容器当中,然后交给你。最后你就可以得到一个只装着前缀为“133”的元素集合。
在Java8的collect方法中,除里toList()之外,还提供了例如toSet,toMap等方法满足不同的场景,根据名字就可以知道,toSet()返回的是一个Set集合,toMap()返回的是一个Map集合。
2.5、min 和 max:找出流中的最小值和最大值
min和max用来查找流中的最小值和最大值。
假设需要在查找出用户列表中年龄最小的用户,可以按照以下代码实现——
List<People> peopleList = Arrays.asList( new People("王二",20), new People("李二",30), new People("张四",31) ); //查找年龄最小的用户,若没有则返回一个null People people = peopleList.stream().min(Comparator.comparing(People::getAge)).orElse(null); System.out.println(people); //打印结果:People{name='王二', age=20}로그인 후 복사max的用法类似,这里不做额外说明。
2.6、anyMatch、allMatch 和 noneMatch:判断流中是否存在满足指定条件的元素
2.6.1、anyMatch
anyMatch用于判断,如果流中至少有一个元素满足给定条件,那么返回true,反之返回false,即 true||false为true这类的判断。
假设在一个手机号字符串的List列表当中,判断是否包含前缀为“153”的手机号,就可以使用anyMatch——
List<String> numbers = Arrays.asList("13378520000", "15138510000","13178520000", "15338510000"); boolean hasNum = numbers.stream().anyMatch(n -> n.startsWith("153")); System.out.println(hasNum); //打印结果:true로그인 후 복사2.6.2、allMatch
allMatch用于判断,流中的所有元素是否都满足给定条件,满足返回true,反之false,即true&&false为false这类判断。
假设在一个手机号字符串的List列表当中,判断手机号是否都满足前缀为“153”的手机号,就可以用allMatch——
List<String> numbers = Arrays.asList("13378520000", "15138510000","13178520000", "15338510000"); boolean hasNum = numbers.stream().allMatch(n -> n.startsWith("153")); System.out.println(hasNum); //打印结果:false로그인 후 복사2.6.3、noneMatch
noneMatch用于判断,如果流中没有任何元素满足给定的条件,返回true,如果流中有任意一个条件满足给定条件,返回false,类似!true为false的判断。
假设在一个手机号字符串的List列表当中,判断手机号是否都不满足前缀为“153”的手机号,就可以用noneMatch——
List<String> numbers = Arrays.asList("13378520000", "15138510000","13178520000", "1238510000"); //numbers里没有前缀为“153”的手机号 boolean hasNum = numbers.stream().noneMatch(n -> n.startsWith("153")); System.out.println(hasNum); //打印结果:true로그인 후 복사这三个方法其实存在一定互相替代性,例如在3.6.1中,满足!anyMatch表示所有手机号都不为“153”前缀,才得到true,这不就是noneMatch,主要看在项目当中如何灵活应用。
2.7、findFirst 和 findAny:返回流中第一个或任意一个元素
2.7.1、findFirst
findFirst用于返回流中第一个元素,如果流为空话,则返回一个空的Optional对象——
假设需要对一批同手机号的黑名单用户按照时间戳降序排序,然后取出第一个即时间戳为最早的用户,就可以使用findFirst——
List<People> peopleList = Arrays.asList( new People("王二","13178520000","20210409"), new People("李二","13178520000","20230401"), new People("张四","13178520000","20220509"), new People("赵六","13178520000","20220109") ); /** * 先按照时间升序排序,排序后的结果如下: * People{name='王二', tel='13178520000', time='20210409'} * People{name='赵六', tel='13178520000', time='20220109'} * People{name='张四', tel='13178520000', time='20220509'} * People{name='李二', tel='13178520000', time='20230401'} * *排序后,People{name='王二', tel='13178520000', time='20210409'}成了流中的第一个元素 */ People people = peopleList.stream().sorted(Comparator.comparing(People::getTime)).findFirst().orElse(null); System.out.println(people); //打印结果:People{name='王二', tel='13178520000', time='20210409'}로그인 후 복사2.7.2、findAny
findAny返回流中的任意一个元素,如果流为空,则通过Optional对象返回一个null。
假设有一个已经存在的黑名单手机号列表blackList,现在有一批新的手机号列表phoneNumber,需要基于blackList列表过滤出phoneNumber存在的黑名单手机号,最后从过滤出来的黑名单手机号当中挑选出来出来任意一个,即可以通过findAny实现——
//blackList是已经存在的黑名单列表 List<String> blackList = Arrays.asList("13378520000", "15138510000"); //新来的手机号列表 List<String> phoneNumber = Arrays.asList("13378520000", "13178520000", "1238510000","15138510000","13299920000"); String blackPhone = phoneNumber.stream() //过滤出phoneNumber有包含在blackList的手机号,这类手机号即为黑名单手机号。 .filter(phone -> blackList.contains(phone)) //获取过滤确定为黑名单手机号的任意一个 .findAny() //如果没有则返回一个null .orElse(null); System.out.println(blackPhone); //打印结果:13378520000로그인 후 복사三、并行流
前面的案例主要都是以顺序流来讲解,接下来,就是讲解Stream的并行流。在大数据量处理场景下,使用并行流可以提高某些操作效率,但同样存在一些需要考虑的问题,并非所有情况下都可以使用。
3.1、什么是并行流:并行流的概念和原理
并行流是指通过将数据按照一定的方式划分成多个片段分别在多个处理器上并行执行,这就意味着,可能处理完成的数据顺序与原先排序好的数据情况是不一致的。主要是用在比较大的数据量处理情况,若数据量太少,效率并不比顺序流要高,因为底层其实就使用到了多线程的技术。
并行流的流程原理如下:
1、输入数据:并行流的初始数据一般是集合或者数组,例如Arrays.asList("13378520000", "13178520000", "1238510000","15138510000","13299920000");
2、划分数据:将初始数据平均分成若干个子集,每个子集可以在不同的线程中独立进行处理,这个过程通常叫“分支”(Forking),默认情况下,Java8并行流使用到了ForkJoinPool框架,会将Arrays.asList("13378520000", "13178520000", "1238510000","15138510000","13299920000")划分成更小的颗粒进行处理,可能会将该数组划分成以下三个子集:
[13378520000, 13178520000] [1238510000, 13338510000] [13299920000]
3、处理数据:针对划分好的子集并行进行相同的操作,例如包括过滤(filter)、映射(map)、去重(distinct)等,这个过程通常叫“计算”(Computing),例如需要过滤为前缀包括“133”的字符集合,那么,各个子集,就会处理得到以下结果:
[13378520000] [13338510000] []
4、合并结果:将所有子集处理完成的结果进行汇总,得到最终结果。这个过程通常叫“合并”(Merging),结果就会合并如下:
[13378520000,13338510000]
5、返回结果:返回最终结果。
通俗而言,就是顺序流中,只有一个工人在摘水果,并行流中,是多个工人同时在摘水果。
3.2、创建并行流:通过 parallel() 方法将串行流转换为并行流
可以通过parallel()方法将顺序流转换为并行流,操作很简单,只需要在顺序流上调用parallel()即可。
List<String> numbers = Arrays.asList("13378360000","13278240000","13178590000","13558120000"); //通过stream().parallel()方法创建一个并行流,使用map()方法将每个字符串转换为截取前7位的字符,最后使用collect()方法将结果收集到一个新列表中 List<String> filNums = numbers.stream().parallel().map(s -> s.substring(0,7)).collect(Collectors.toList()); System.out.println(filNums); //打印结果:[1337836, 1327824, 1317859, 1355812]로그인 후 복사3.3、并行流的注意事项:并行流可能引发的线程安全,以及如何避免这些问题
在使用并发流的过程中,可能会引发以下线程安全问题:并行流中的每个子集都在不同线程运行,可能会导致对共享状态的竞争和冲突。
避免线程问题的方法如下:避免修改共享状态,即在处理集合过程当中,避免被其他线程修改集合数据,可以使用锁来保证线程安全。
使用无状态操作:在并行流处理过程尽量使用无状态操作,例如filter、map之类的,可以尽量避免线程安全和同步问题。
四、Optional
4.1、什么是 Optional:Optional 类型的作用和使用场景
在实际开发当中,Optional类型通常用于返回可能为空的方法、避免null值的传递和简化复杂的判断逻辑等场景。调用Optional对象的方法,需要通过isPresent()方法判断值是否存在,如果存在则可以通过get()方法获取其值,如果不存在则可以通过orElse()方法提供默认值,或者抛出自定义异常处理。
4.2、如何使用 Optional:如何使用 Optional 类型
使用Optional类型主要目的是在数据可能为空的情况下,提供一种更安全、更优雅的处理方式。
以下是使用Optional类型的常用方法:
4.2.1、ofNullable()和isPresent()方法
将一个可能为null的对象包装成Optional类型的对象,然后根据isPresent方法判断对象是否包含空值——
String str = null; Optional<String> optStr = Optional.ofNullable(str); if (optStr.isPresent()){ System.out.println("Optional对象不为空"); }else { System.out.println("Optional对象为空"); } //打印结果:Optional对象为空로그인 후 복사4.2.2、get()方法
获取Optional对象中的值,如果对象为空则抛出NoSuchElementException异常——
String str = null; Optional<String> optStr = Optional.ofNullable(str); if (optStr.isPresent()){ System.out.println("Optional对象不为空"); }else { System.out.println("Optional对象为空"); optStr.get(); }로그인 후 복사控制台打印结果:
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.util.Optional.get(Optional.java:135)
at com.zhu.fte.biz.test.StreamTest.main(StreamTest.java:144)
Optional对象为空4.2.4、orElse()方法
获取Optional对象中的值,如果对象为空则返回指定的默认值——
String str = null; Optional<String> optStr = Optional.ofNullable(str); if (optStr.isPresent()){ System.out.println("Optional对象不为空"); }else { System.out.println("Optional对象为空,返回默认值:" + optStr.orElse("null")); } //打印结果:Optional对象为空,返回默认值:null로그인 후 복사当然,如果不为空的话,则能正常获取对象中的值——
String str = "测试"; Optional<String> optStr = Optional.ofNullable(str); if (optStr.isPresent()){ System.out.println("Optional对象不为空,返回值:" + optStr.orElse("null")); }else { System.out.println("Optional对象为空,返回默认值:" + optStr.orElse("null")); } //打印结果:Optional对象不为空,返回值:测试로그인 후 복사那么,问题来了,它是否能判断“ ”这类空格的字符串呢,我实验了一下,
String str = " "; Optional<String> optStr = Optional.ofNullable(str); if (optStr.isPresent()){ System.out.println("Optional对象不为空,返回值:" + optStr.orElse("null")); }else { System.out.println("Optional对象为空,返回默认值:" + optStr.orElse("null")); } //打印结果:Optional对象不为空,返回值:로그인 후 복사可见,这类空字符串,在orElse判断当中,跟StringUtils.isEmpty()类似,都是把它当成非空字符串,但是StringUtils.isBlank()则判断为空字符串。
4.2.5、orElseGet()方法
orElseGet()和orElse()类似,都可以提供一个默认值。两者区别在于,orElse方法在每次调用时都会创建默认值,而orElseGet只在需要时才会创建默认值。
4.3、Optional 和 null 的区别: Optional 类型与 null 值的异同
两者都可以表示缺失值的情况,两者主要区别为:Optional类型是一种包装器对象,可以将一个可能为空的对象包装成一个Optional对象。这个对象可以通过调用
ofNullable()
、of()
或其他方法来创建。而null值则只是一个空引用,没有任何实际的值。Optional类型还可以避免出现NullPointerException异常,具体代码案例如下:
String str = null; //错误示范:直接调用str.length()方法会触发NullPointerException //int length = str.length() //通过Optional类型避免NullPointerException Optional<String> optionalStr = Optional.ofNullable(str); if (optionalStr.isPresent()){//判断Optional对象是否都包含非空值 int length = optionalStr.get().length(); System.out.println("字符串长度为:" + length); }else { System.out.println("字符串为空!"); } //使用map()方法对Optional对象进行转换时,确保返回对结果不为null Optional<Integer> optionalLength = optionalStr.map(s -> s.length()); System.out.println("字符串长度为:" + optionalLength.orElse(-1)); // 使用orElse()方法提供默认值로그인 후 복사五、扩展流处理
除里以上常用的流处理之外,Java8还新增了一些专门用来处理基本类型的流,例如IntStream、LongStream、DoubleStream等,其对应的Api接口基本与前面案例相似,读者可以自行研究。
最后,需要注意一点是,在流处理过程当中,尽量使用原始类型数据,避免装箱操作,因为装箱过程会有性能开销、内存占用等问题,例如,当原始数据int类型被装箱成Integer包装类型时,这个过程会涉及到对象的创建、初始化、垃圾回收等过程,需要额外的性能开销。
위 내용은 Java8에서 스트림 스트리밍 프로그래밍을 사용하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!