streamlist过滤出⼀个字段的列表_Java8StreamAPI详解
(三)——Str。。。
在流上可以执⾏很多操作,这些操作分为中间操作(返回Stream)和终结操作(返回确定类型的结果),中间操作允许链式串接。要注意,流上的操作不会改变数据源。
如下例:
long count = list.stream().distinct().count();
这⾥的distinct()⽅法就是⼀个内部操作,会在之前流的基础上创建⼀个元素唯⼀的新流,⽽count()⽅法就是⼀个终结操作,会返回流的⼤⼩。
Stream 操作
迭代 Iterating
Stream API可以替换for、for-each、while循环,使⽤该⽅法,开发者可以专注于操作的逻辑,⽽⽆需关⼼元素序列的迭代。如:
for (String string : list) {
if (ains("a")) {
return true;
}
}java stream
转换为Stream风格只需⼀⾏代码:
boolean isExist = list.stream().anyMatch(element -> ains("a"));
过滤 Filtering
filter()⽅法可⽤于挑选满⾜断⾔的流元素,举例来说,如果有⼀个这样的流:
ArrayList<String> list = new ArrayList<>();
list.add("One");
list.add("OneAndOnly");
list.add("Derek");
list.add("Change");
list.add("factory");
list.add("justBefore");
list.add("Italy");
list.add("Italy");
list.add("Thursday");
list.add("");
list.add("");
下⾯的代码会创建该列表对应的⼀个字符串流,到流中所有包含字符“d”的元素,并将过滤出的元素组成⼀个新的流:
Stream<String> stream = list.stream().filter(element -> ains("d"));
映射 Mapping
如果需要对流中的元素执⾏特定的函数进⾏转换,并将转换后的新元素收集到新的流中,可以使⽤map()⽅法:
List<String> uris = new ArrayList<>();
uris.add("");
Stream<Path> stream = uris.stream().map(uri -> (uri));
上⾯的代码会对初始流中的每个元素执⾏指定的lambda表达式,将Stream<String>转换为Stream<Pat
h>。
如果有⼀个流,其中每个元素都包含其对应的⼀串元素序列,要根据所有内部元素创建⼀个新流,应该使⽤flatMap()⽅法:
List<Detail> details = new ArrayList<>();
details.add(new Detail());
Stream<String> stream = details.stream().flatMap(detail -> Parts().stream());
在这个例⼦中,我们有⼀个元素为Detail类的列表,Detail类中包含字段PARTS,是⼀个字符串列表。通过使⽤flatMap()⽅法,字段PARTS中的每⼀个元素都被提取出来并添加到新的结果流中,之后,初始的Stream<Detail>会被丢弃。
匹配 Matching
Stream API提供了⼀组⽅便的⼯具来根据某些断⾔验证⼀系列元素,要实现该⽬标,可以使⽤以下三个⽅法之⼀:anyMatch(), allMatch(), noneMatch(),每个函数的功能都⼀⽬了然,这些都是返回布尔值的终结操作:
boolean isValid = list.stream().anyMatch(element -> ains("h")); // true
boolean isValidOne = list.stream().allMatch(element -> ains("h")); // false
boolean isValidTwo = list.stream().noneMatch(element -> ains("h")); // false
如果是空流,对于任意给定的断⾔,allMatch()⽅法都会返回true:
这是⼀个合理的默认值,因为我们不到任何不满⾜断⾔的元素。
同样的,对于空流,anyMatch() ⽅法⼀定会返回false:
同样,这也是合理的,因为我们不到满⾜该条件的元素。
归集 Reduction
Stream API中使⽤reduce()⽅法可以根据指定的⽅法将⼀系列元素归集为某个值,该⽅法接收两个参数:第⼀个是初始值,第⼆个是累加器函数。
假设您有⼀个整数列表,并且想要在某个初始值(这⾥使⽤23)基础上计算所有元素的总和,可以运⾏下⾯的代码,得到结果为
26(23+1+1+1):
List<Integer> integers = Arrays.asList(1, 1, 1);
Integer reduced = integers.stream().reduce(23, (a, b) -> a + b);
收集 Collecting
归集操作也可以通过collect()⽅法实现。在将流转换为集合、映射或使⽤⼀个字符串表⽰⼀个流时,该操作⾮常⽅便。还有⼀个⼯具类Collectors,提供了⼏乎所有常⽤的收集操作,对于⼀些复杂的任务,额可以创建⾃定义收集器。
List<String> resultList = list.stream().map(element -> UpperCase()).List());
该代码提供最后的collect()⽅法将字符串流转换为字符串列表。
搜索 Searching
在集合中的搜索意味着根据⼀个条件查元素或验证元素的存在性,这个条件也叫做谓词或断⾔。搜索元素可能有返回值,也可能没有,所以接⼝返回的是⼀个Optional;验证元素存在性时返回是的⼀个布尔值。
下⾯的⽰例中,通过findAny()查元素,通过anyMatch()检查是否存在满⾜条件的元素。
// searching for a element
Optional<Person> any = people.stream()
.filter(person -> Age() < 20)
.findAny();
// searching for existence
boolean isAnyOneInGroupLessThan20Years = people.stream()
.anyMatch(person -> Age() < 20);
重排序 Reordering
如果需要对集合中的元素进⾏排序,可以使⽤Stream中的sorted⽅法,该⽅法接收⼀个Comparator接⼝的实现类作为参数。可以使⽤Comparator中的comparing⼯⼚⽅法来创建对应的实例。
在下⾯的代码中,结果就是按照Person的age属性降序排列后的集合。
List<Person> peopleSortedEldestToYoungest = people.stream()
.sorted(Comparatorparing(Person::getAge).reversed())
.List());
与我们之前讨论的其它操作不同,排序操作是有状态的。这也就意味着,在将排序结果传递给后续的中间操作或终结操作时,该操作⽅法必须处理流中的所有元素。还有另⼀个类似的操作,就是distinct。
汇总 Summarizing
有时我们需要从集合中提取⼀些信息。⽐如,提取所有⽤户的年龄的总和,在Stream API中,可以使⽤终结操作。reduce和collect就是为此⽬的提供的通⽤终结操作。还有⼀些在其基础上创建的⾼级运算符,如sum、count、summaryStatistics等。
// calculating sum using reduce terminal operator
people.stream()
.mapToInt(Person::getAge)
.reduce(0, (total, currentValue) -> total + currentValue);
// calculating sum using sum terminal operator
people.stream()
.mapToInt(Person::getAge)
.sum();
// calculating count using count terminal operator
people.stream()
.mapToInt(Person::getAge)
.
count();
// calculating summary
IntSummaryStatistics ageStatistics = people.stream()
.mapToInt(Person::getAge)
.summaryStatistics();
reduce和collect是归集操作,reduce⽤于不可变归集,⽽collect⽤于可变的归集。不可变归集是⾸选的⽅法,但是对于重视性能的场景,应该优先选择可变收集。
分组 Grouping
分组也可以称为分类。有时,我们希望将⼀个集合分成⼏个组,在这种情况下产⽣的数据结构是⼀个Map,其中key表⽰分组因⼦,值为各组对应的属性。Stream API对于此类场景提供了upingBy⽅法。
在下⾯的例⼦中,都是要性别对数据进⾏分组,区别之处在于值。第⼀个例⼦中,为每个组创建了Person集合;第⼆个例⼦中,通
过Collectors.mapping()是提取每个⽤户的姓名,并创建姓名集合;第三个例⼦中,提取并计算每组的平均年龄。
// Grouping people by gender
Map<Gender, List<Person>> peopleByGender = people.stream()
.upingBy(Person::getGender, List()));
// Grouping person names by gender
Map<Gender, List<String>> nameByGender = people.stream()
.upingBy(Person::getGender, Collectors.mapping(Person::getName, List())));
// Grouping average age by gender
Map<Gender, Double> averageAgeByGender = people.stream()
.upingBy(Person::getGender, Collectors.averagingInt(Person::getAge)));
常见案例
在forEach循环中break
对⼀组元素进⾏遍历并对其中元素执⾏操作时,可以通过Stream中的forEach⽅法以⼲净、声明式的⽅式编写出代码。虽然这与循环是类似的,但是缺少了与break对应的终⽌迭代的语句。⼀个流可能很长,甚⾄是⽆限的,如果不需要继续对其进⾏处理,我们希望可以直接终⽌操作,⽽不是等到处理完所有元素。
使⽤⾃定义Spliterator
在Java 8中引⼊的Spliterator接⼝(可拆分迭代器)可⽤于对序列进⾏遍历和分区。它是流(尤其是并⾏流)的基本⼯具类。
tryAdvance()是单步遍历序列的主要⽅法。该⽅法接收⼀个Consumer作为参数,该消费者⽤于持续消费spliterator的元素,如果没有可遍历的元素则返回false。
我们可以创建⼀个⾃定义的Spliterator 作为Stream.spliterator的装饰器,并以此完成break操作。
⾸先,我们需要获取流的Spliterator并使⽤⾃定义的CustomSpliterator对其进⾏装饰,这⾥需要提供控制break⾏为的断⾔,最后我们再根据CustomSpliterator创建新流:
public class CustomTakeWhile {
public static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<T> predicate) {
CustomSpliterator<T> customSpliterator = new CustomSpliterator<>(stream.spliterator(), predicate);
return StreamSupport.stream(customSpliterator, false);
}
}
下⾯是CustomSpliterator的代码:
public class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
private Spliterator<T> splitr;
private Predicate<T> predicate;
private boolean isMatched = true;
public CustomSpliterator(Spliterator<T> splitr, Predicate<T> predicate) {
super(splitr.estimateSize(), 0);
this.splitr = splitr;
this.predicate = predicate;
}
@Override
public synchronized boolean tryAdvance(Consumer<? super T> consumer) {
boolean hadNext = Advance(elem -> {
if (st(elem) && isMatched) {
consumer.accept(elem);
} else {
isMatched = false;
}
});
return hadNext && isMatched;
}
}
可以看到上⾯的tryAdvance()⽅法,⾃定义的拆分器处理了装饰的拆分器中的元素,只要断⾔为真并且初始流中还有元素,就会⼀直进⾏处理;如果两个条件中任意为false,拆分器就会break,流操作也会结束。
假设我们有⼀个字符串项流,只要其中元素的长度是奇数,我们就持续处理其元素。测试代码如下:
@Test
public void whenCustomTakeWhileIsCalled_ThenCorrectItemsAreReturned() {
Stream<String> initialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");
List<String> result = CustomTakeWhile.takeWhile(initialStream, x -> x.length() % 2 != 0)
.List());
assertEquals(asList("cat", "dog"), result);
}
使⽤⾃定义forEach
虽然提供嵌⼊的break机制可能很有⽤,但是只关注forEach操作可能会更简单。
我们可以直接使⽤Stream.spliterator:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论