标签:代码 ESS 消费 out 结果 方案 接受 lse src
Stream简直不要太好使啊!!!
Supplier<T>
,主要方法:T get()
,这是一个生产者,可以提供一个T对象。Consumer<T>
,主要方法:void accept(T)
,这是一个消费者,默认方法:andthen()
,稍后执行。Predicate<T>
,主要方法:boolean test(T t)
,这是一个判断者,默认方法:and()
:且,or()
:或,negate()
:非。Function<T,R>
,主要方法:R apply(T t)
,这是一个修改者,默认方法:compose()
:优先执行,andThen()
,稍后执行,identity()
:直接传自身。Function f1 = e -> e + 1;
Function f2 = e -> e * 1;
f1.andthen(f2).apply(t)//即f2.apply(f1.apply(t))
f1.compose(f2).apply(t)//即f1.apply(f2.apply(t))
而identify()相当于 s -> s
常用Stream方法:
中间操作是惰性求值,中间操作过后返回的还是Stream,因此可以继续操作;结束操作是立即求值,返回具体的数据。
流不能被重用,因此推荐链式写法。
上边仅仅是Stream的方法,经常使用的还有Stream的静态方法,比如:Stream.concat()
,用来合成两个流;Stream.of()
方法,直接获取流。
Arrars.stream(arr)
或者IntStream.of(arr)
进行初始的流转化,而Stream.of(arr)
会把arr当做数组对象传入,而非直接操作数组。(Arrays.stream 是为了弥补Stream.of无法使用数组参数而做的补偿方案.比如a[]传入Stream.of会被当成一个对象而传入Arrays.stream可以当成一个数组)具体区别可以由下面几个栗子看出来:
int[] arr = {1,2,3,4};
System.out.println(
Arrays.stream(arr).count());//4,因为数组里有4个数
System.out.println(
Stream.of(arr).count());//1,因为只有一个数组,并且这里的流不能调用sum()方法求和,因为引用对象没法相加
Stream.of(1,2,3,4).count();//4,因为有四个Integer,注意这里Stream.of()方法会自动装箱,把int变为Integer,这里也不能调用sum()方法
Arrays.stream(new int[]{1,2,3,4}).count();//4,等价于第一种情况,并且此时还有个box()方法可以装箱,因此使用流处理拆装箱非常方便
//但是!如果Stream.of()参数是一个引用类型的数组(注意是一个),比如String[]:
System.out.println(
Stream.of(new String[]{"My", "Java", "My", "Life!"}).count());//4
//它又可以正常使用,
//但是!!如果里面是两个String[]数组,它又不好使了:
String[] s1 = {"My", "Java", "My", "Life!"};
String[] s2 = {""};
System.out.println(
Stream.of(s1,s2).count());//2!!!
小结:说得好,数组我选择Arrays.Stream()! (`へ′*)ノ
forEach
forEach()
:void forEach(Consumer<? super E> action)
,
即对每个元素执行action,也就是最常见的遍历
Stream.of("My","Java","My","Life!")
.forEach(System.out::println);//打印每个元素
filter
filter()
:Stream<T> filter(Predicate<? super T> predicate)
,筛选,即挑选出符合predicate的元素
Stream.of("My", "Java", "My", "Life!")
.filter(s -> s.length() > 4);//筛选出长度大于4的元素,此时流里的元素为:"Java","Life!"
distinct
distinct()
:Stream<T> distinct()
,去重
Stream.of("My", "Java", "My", "Life!")
.distinct();//"My","Java","Life!"
sorted
sorted()
:Stream<T> sorted()
和Stream<T> sorted(Comparator<? super T> comparator)
,排序,无参为自然排序,有参为自定义比较器排序
Stream.of("My", "Java", "My", "Life!")
.sorted()//"Java","Life!","My","My"无参,也就是按照默认排序(这里就是ASCII码)
.forEach(System.out::println);
Stream.of("My", "Java", "My", "Life!")
.sorted((s1, s2) -> s2.length() - s1.length())//"Life!","Java","My","My",按照长度进行降序排序
.forEach(System.out::print);
map
map()
:<R> Stream<R> map(Function<? super T,? extends R> mapper)
,转换,也就是执行Function的功能
class Student{
private String name;
public Student(String name){
this.name = name;
}
}
Stream.of("My", "Java", "My", "Life!")
.map(String::toLowerCase)//转化为小写
.map(Student::new);//接着以名字为参数转化Student流
flatMap
flatMap()
:<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
,扁平化转换流,与map()
不同的是,map()
返回一个结果,而当返回结果数目未知时不太方便,使用flatMap()
会将多个数目不一的流合为一个流
Stream.of(Arrays.asList("My"), Arrays.asList("Java", "My", "Life!"))//此时,这是List流,里面是两个List对象,List里面才是字符串数据
.flatMap(list -> list.stream())//扁平化,即把List流转化为字符串流,这里也可以使用方法引用:.flatMap(Collection::stream)
.forEach(System.out::println);
count
count()
:long count()
,统计流中元素的数量,注意返回值为long
Stream.of("My", "Java", "My", "Life!")
.count();//4L
limit
limit()
:Stream<T> limit(long maxSize)
,限制长度,也可以理解为取前maxSize个元素,maxSize可以大于元素数目
Stream.of("My", "Java", "My", "Life!")
.limit(2);//"My","Java"
skip
skip()
:Stream<T> skip(long n)
,跳过前n个元素,和limit差不多
Stream.of("My", "Java", "My", "Life!")
.skip(2);//"My","Life!"
toArray
toArray
:Object[] toArray()
和<A> A[] toArray(IntFunction<A[]> generator)
,无参即返回Object[]数组,而有参时可以返回特定类型数组
Stream.of("My", "Java", "My", "Life!")
.toArray(String[]::new);//String[]数组,内容为:["My", "Java", "My", "Life!"]
parallel
parallel
:S parallel()
转化为并行流,但是单核甚至双核的情况下转化并行流反而会降低效率。
max
max()
:Optional<T> max(Comparator<? super T> comparator)
,以自定义比较方法返回一个最大对象,注意这里返回的是Optional对象。
Stream.of("My", "Java", "My", "Life!")
.max((o1, o2) -> o2.length()-o1.length())
.get();//"My"
Stream.of("My", "Java", "My", "Life!")
.max(Comparator.comparingInt(String::length))//"Life!",这里返回的是Optional,里面盛的是"Life!"
.stream()//这里调用的是Optional的方法,其实这里没有必要,直接调用get()方法就可以拿到"Life!"字符串了
.forEach(System.out::println);
说到了max()
就不得不提一下Optional是什么,简单来说Optional就是一个容器,用来盛放数据,并且它是容许为null的,这样就不用反复判断值是否为空了。可以看一下Optional的方法:
public final class Optional<T> {
//静态方法,返回空Optional对象
public static<T> Optional<T> empty() {
}
//静态方法,返回Optional对象,value不能为空
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
//静态方法,返回Optional对象,值允许为null
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
//虽然值允许为null,但还是在调用get时还是会抛出NoSuchElementException异常
public T get() {
}
//所以此方法可以判断值是否存在
public boolean isPresent() {
}
//如果存在,则执行action
public void ifPresent(Consumer<? super T> action) {
}
//JDK9方法↓
//如果值非空,执行action,值为null则执行emptyAction
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
}
//Optional对象也可以执行与Stream相同的部分方法
public Optional<T> filter(Predicate<? super T> predicate) {
}
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
}
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
}
//JDK9方法↓
//如果为null,则返回一个supplier提供的对象,与orElse和orElseGet方法类似。
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
}
//JDK9方法↓
//可以把Optional对象继续变成流
public Stream<T> stream() {
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
//重写equals方法
@Override
public boolean equals(Object obj) {
}
//重写hashCode方法
@Override
public int hashCode() {
}
//重写toString方法
@Override
public String toString() {
return value != null
? String.format("Optional[%s]", value)
: "Optional.empty";
}
}
orElse和orElseGet:
orElse
和orElseGet
这两个方法放上源码比较好理解:
首先,在值为null时,两个方法并无区别,都能获取到T;
但是,在非空时,如果orElse()
的参数是一个方法,该方法也会被执行,而orElseGet()
的参数supplier
并不会执行,这得益于Lambda的延迟执行。因此,相同情况下orElseGet()
性能更优。或者直接使用or,如果JDK允许的话
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
举个小栗子就能明白了:
static String B() {
System.out.println("B()执行了");
return "B";
}
public static void main(final String... args) {
System.out.println(Optional.ofNullable(null).orElse(B()));
System.out.println(Optional.ofNullable(null).orElseGet(() -> B()));
}
上面这种情况执行结果为:
B()执行了
B
B()执行了
B
并没有什么问题,但是当值非空的时候:
static String B() {
System.out.println("B()执行了");
return "B";
}
public static void main(final String... args) {
System.out.println(Optional.ofNullable("A").orElse(B()));
System.out.println(Optional.ofNullable("A").orElseGet(() -> B()));
}
执行结果为:
B()执行了
A
A
很明显orElseGet更节约性能,Lambda的延迟执行特性还是比较好用的。
咳咳,扯远了,继续说Stream的常用方法:
min
min()
:Optional<T> min(Comparator<? super T> comparator)
,与max类似,注意max()
和min()
最好使用引用方法,而不是自己定义Comparator,举个栗子:
Stream.of("My", "Java", "My", "Life!")
.min((o1, o2) -> o2.length()-o1.length());//"Life!"
这里使用的是min()方法,自定义Comparator为常见的降序,但是最后得到的反而是长度最长的,完全违背了函数式见文知意的思想。
所以想要比较长度的话这样写:
Stream.of("My", "Java", "My", "Life!")
.max(Comparator.comparingInt(String::length));
Stream.of("My", "Java", "My", "Life!")
.min(Comparator.comparingInt(String::length));
或者,你觉得max(),min(),count(),sum()
这些不好用,还有下面两个多能工供你使用
reduce
reduce()
:Optional<T> reduce(BinaryOperator<T> accumulator);
和T reduce(T identity, BinaryOperator<T> accumulator);
和<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);
一个reduce操作(也称为折叠)接受一系列的输入元素,并通过重复应用操作将它们组合成一个简单的结果,也就是持续迭代
理解reduce的含义重点就在于理解"累 加 器" 的概念,并且每次运算的结果会作为下一次运算的一个参数
Stream的一个参数和两个参数的方法的基本逻辑都是如此,差别仅仅在于一个参数的是result R = T1
,然后再继续与剩下的元素参与运算
分别为一个参数、两个参数、三个参数,这里需要先介绍一下这几个新接口:
BiFunction
public interface BiFunction<T, U, R>
R apply(T t, U u)
andthen
BinaryOperator
public interface BinaryOperator<T> extends BiFunction<T,T,T>
minBy
和maxBy
BiConsumer
void accept(T t, U u)
reduce一个接口参数可以很方便的进行求最大值、最小值、和,代码如下:
Integer sum = s.reduce((a, b) -> a + b).get();
Integer max = s.reduce((a, b) -> a >= b ? a : b).get();
Integer min = s.reduce((a, b) -> a <= b ? a : b).get();
reduce两个参数的对比一个参数的多了一个初始化的值,
T result = identity;
for (T element : this stream){
result = accumulator.apply(result, element)
}
return result;
所以reduce(0, (a, b) -> a + b)
和reduce((a, b) -> a + b)
是完全等价的。
reduice三个参数较为复杂,前两个参数与二参基本相同,第三个参数用于在并行计算下,合并各个线程的计算结果。需要注意的是,并行情况下初始值(也就是第一个参数)会被多次计算,具体可以参看此博客。
collect
collect()
:<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
和<R, A> R collect(Collector<? super T, A, R> collector)
收集装置,可以将元素以特定方式收集起来。需要了解的是收集器的参数决定了收集器的行为。
这里需要说一下Collector
接口,这里先上几个重要的静态方法:
生成Collection
toCollection()
将流转化为集合,参数为集合实现方式,比如:Stream.of("My", "Java", "My", "Life!")
.collect(Collectors.toCollection(ArrayList::new));//以ArrayList的形式转化为集合
toList()
和toset()
这两个可以视为上面的简化版本,无参,默认为ArrayList和HashSet实现:Stream.of("My", "Java", "My", "Life!")
.collect(Collectors.toList());
生成Map
toMap
与上面类似,只不过需要指定map的键和值:Stream.of("My", "Java", "my", "Life!")
.collect(Collectors.toMap(Function.identity(),//指定Key
s -> s.length()))//指定Value
//注意,这里键是不能重复的,否则会抛出异常,所以我把第二个"My"改成了"my",想要键可重复请继续往下看。
//运行结果
{Java=4, Life!=5, My=2, my=2}
partitioningBy()
适用于将Stream中的元素依据某个二值逻辑(满足条件,或不满足)分成互补相交的两部分,一参为一个Predicate接口,二参时可以将第二个参数指定为再一个Collector。Map<Boolean, List<String>> map = Stream.of("My", "Java", "my", "Life!")
.collect(Collectors.partitioningBy(s -> s.length() > 3));//以长度是否大于3分组
//运行结果
{false=[My, my], true=[Java, Life!]}
//二参可以指定一个下游收集器
Map<Boolean, Long> map = Stream.of("My", "Java", "my", "Life!")
.collect(Collectors.partitioningBy(s -> s.length() > 4, Collectors.counting()));//以长度是否大于4分组,并计数
//运行结果
{false=3, true=1}
groupingBy()
:上面的partitioningBy()
只能将数据分成两类,根本不够用是不是,groupingBy()
则可以做到更多。它分别有一参、二参和三参://一参可以当做partitioningBy用
Map<Boolean, List<String>> map = Stream.of("My", "Java", "My", "Life!")
.collect(Collectors.groupingBy(s -> s.length() > 4));//以长度是否大于4分组
//运行结果
{false=[My, Java, My], true=[Life!]}
//也可以当做增强型
Map<Integer, List<String>> map = Stream.of("My", "Java", "My", "Life!")
.collect(Collectors.groupingBy(String::length));//以长度分组
//运行结果
{2=[My, My], 4=[Java], 5=[Life!]}
//二参就更好用了
Map<String, Long> map = Stream.of("My", "Java", "My", "Life!")
.collect(Collectors.groupingBy(
Function.identity(),Collectors.counting()));//指定键为元素,值为出现次数,这种情况下键是可以重复的
//运行结果
{Java=1, Life!=1, My=2}
//或者
Map<Integer, Long> map = Stream.of("My", "Java", "My", "Life!")
.collect(Collectors.groupingBy(String::length,Collectors.counting()));//以长度为键,出现次数为值
//运行结果
{2=2, 4=1, 5=1}
//三参就更厉害了
Map<Integer, List<String>> map = Stream.of("My", "Java", "My", "Life!")
.collect(Collectors.groupingBy(
String::length,Collectors.mapping(s->s.toUpperCase(),Collectors.toList())));//注意这里下游收集齐还包含着更下游的收集齐
//运行结果
{2=[MY, MY], 4=[JAVA], 5=[LIFE!]}
你可能觉得三参的collect用不到,但是请看这里:
下游收集器还可以包含更下游的收集器,这绝不是为了炫技而增加的把戏,而是实际场景需要。考虑将员工按照部门分组的场景,如果我们想得到每个员工的名字(字符串),而不是一个个Employee对象,可通过如下方式做到:
// 按照部门对员工分布组,并只保留员工的名字 Map<Department, List<String>> byDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment, Collectors.mapping(Employee::getName,// 下游收集器 Collectors.toList())));// 更下游的收集器
超强!参见这篇博客
joining()
这是用来连接字符串的,无参为直接连接,一参指定连接符,三参可以指定连接符和首位字符:Stream.of("My", "Java", "My", "Life!")
.collect(Collectors.joining());//MyJavaMyLife!
Stream.of("My", "Java", "My", "Life!")
.collect(Collectors.joining("-")//My-Java-My-Life!
Stream.of("My", "Java", "My", "Life!")
.collect(Collectors.joining("-","[","]"))//[My-Java-My-Life!]
小结:用了Stream之后再也不想用普通的for循环了有木有啊!!!
标签:代码 ESS 消费 out 结果 方案 接受 lse src
原文地址:https://www.cnblogs.com/lixin-link/p/11020407.html