Stream
stream 是什么
流是Java 8的新成员,允许以声明式来处理数据的集合(通过查询语句来表示,并不是像以前的一样写一个方法来处理),就现在来说,你可以把它们看成遍历数据集的高级迭代器。
有下面这个集合YxUser这个pojo类记录的用户的基本信息,假如我们要查找出年龄在18-30的用户的姓名(并且按照年龄降序)在以前我们是怎么做的?
java7:
List<YxUser> ageList = new ArrayList<>();
for (YxUser y : list) {
if (y.getAge() <= 30 && y.getAge() >= 18) {
ageList.add(y);
}
}
Collections.sort(ageList, new Comparator<YxUser>() {
@Override
public int compare(YxUser d1, YxUser d2) {
return Integer.compare(d1.getAge(), d2.getAge());
}
});
List<String> name = new ArrayList<>();
for (YxUser d : ageList) {
name.add(d.getUsername());
}
虽然这个方式能够实现我们想要的东西,但是,我们使用了好几个变量,并且他们的作用只有一次,但是这个写感觉比较麻烦。如果别人要 id在3-9之间的的用户的姓名呢,这些又要改动。–>java8使用流
java8:
List<String> collect = list.stream()
.filter(e -> e.getAge() <= 30 && e.getAge() >= 18)
.sorted(comparing(YxUser::getAge))
.map(YxUser::getUsername)
.collect(Collectors.toList());
System.out.println("collect: " + collect);
并且这段代码可以利用多核框架执行
List<String> collect = list.parallelStream() .filter(e -> e.getAge() <= 30 && e.getAge() >= 18) .sorted(comparing(YxUser::getAge)) .map(YxUser::getUsername) .collect(Collectors.toList()); System.out.println("collect: " + collect);
在java8中:
1.代码是以声明式方式来写的:想完成什么(filter筛选年龄) 而不是像java7一样如果来实现(利用循环和if来控制)。如果有新的需求,那么不用重新复制代码,只需要更改主要条件即可。
2.把几个基础操作链接起来,来表达复杂的数据处理流水线(在filter后面接上sorted、map和collect操作,示意图如下),同时保持代码清晰可读。filter的结果被传给了sorted方法,再传给map方法,最后传给collect方法
而且新的stream api 表达能力特别强,写出来的东西别人可能不会,但是别人能够看懂你要做什么。
stream 特点
声明性:简洁、易读
声明性:简洁、易读
可复用:比较灵活
可并行:性能更好
流和集合的关系
流和集合的迭代器类似,只能够遍历一次。遍历完成后说明这个流已经被消费了。
stream:stream是只计算当前需要的数据,在迭代过程中,stream是放在内部迭代的,集合的迭代是放在外部。在外部迭代就会需要自己解决管理并行的问题。
外部迭代与内部迭代
外部迭代 :使用Collection接口需要用户去做迭代(比如用for-each)。
内部迭代: 使用Streams库,它帮你把迭代做了,还把得到的流值存在了某个地方,你只要给出一个函数说要干什么就可以了。
在上一个例子的筛选年龄18-30的两种方法,第一个方法就是外部迭代,第二个方法就是内部迭代。
两者的区别:
外部迭代:
-
Java 的 for-each 循环/迭代本质上是有序的,它必须按照容器指定的顺序处理元素。
-
它限制了 JVM控制流程的可能性,而正是这种可能性使得 JVM 可以通过重新排序、并行处理、短路、延迟处理来提供更好的性能。
内部迭代:
-
用户代码只需关注解决问题,无需关注如何解决的细节,从而变得更加清晰了。
-
内部迭代使得 JVM 利用短路、并行处理和乱序执行来提升性能成为可能(JVM 是否利用了这些来优化性能取决于 JVM 实现本身,但是有了内部迭代这些至少是可能的,而对于外部迭代来说则是不可能的)
流的操作
上图有两个操作
-
filter、map、sorted连成一个流水线。
-
collect 触发流水线执行并且关闭它。
连接起来的流称为 中间操作,关闭流的操作称为终端操作。
中间操作:它操作后会返回另外一个流,让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理——这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。
终端操作:他会从流的流水线生成一个结果。结果可以是任何东西(void\integer\list…),但是不能是流。
Stream使用
筛选
用到的例子
List<Integer> integerList =Arrays.asList(1,2,2,2,2,2,4,5,6,7,8);
List<YxUser> list = Arrays.asList(
new YxUser(1, "yanxgin", 12, "8237251670@qq.com", 1, true),
new YxUser(2, "caoxindi", 16, "2737827527@qq.com", 1, false),
new YxUser(3, "zhangsan", 18, "334899245@qq.com", 0, true),
new YxUser(4, "lisi", 23, "774892034@qq.com", 0, false),
new YxUser(5, "wangwu", 66, "43892475266@qq.com", 1, false),
new YxUser(6, "zhaoliu", 46, "54654742@qq.com", 0, false),
new YxUser(7, "liuqi", 30, "54375396@qq.com", 1, true)
);
**谓词筛选filter **
List<String> collect = list.stream()
.filter(e -> e.getId() > 2) //谓词筛选
.collect(Collectors.toList()); // 终端操作
distinct顾名思义:去掉重复的。
integerList.stream()
.filter(i->i%2==0)
.distinct()
.forEach(System.out::println);
//输出: 2,4,6,8
limit:返回前N个数据,类似mysql的limit上。
integerList.stream()
.sorted()
.limit(3)
.forEach(System.out::println);
//排序后将输出前三个,原来的数据 :1,2,2,2,2,2,4,5,6,7,8
// 输出:1 2 2
skip:过滤掉前n个元素。
integerList.stream()
.sorted()
.skip(2)
.limit(2)
.forEach(System.out::println);
//排序后,先过滤前两个,在输出前两个。实际输出的是第3,4两个。
// 原来的数据 :1,2,2,2,2,2,4,5,6,7,8
// 输出:2 2
映射
map:一般的用法:map就是取其中的一列
List<String> collect = list.stream()
.filter(e -> e.getId() > 2) // 中间操作
.map(YxUser::getUsername) // 中间操作
.collect(Collectors.toList()); // 终端操作
// 输出:
// collect: [zhangsan, lisi, wangwu, zhaoliu, liuqi]
flatMap:流的扁平化
例子:给定单词列表[“Hello”,“World”],你想要返回列表[“H”,“e”,“l”, “o”,“W”,“r”,“d”]。
List<String> strings = Arrays.asList("Hello", "World");
List<String[]> collect1 = strings.stream()
.map(e -> e.split(""))
.distinct()
.collect(Collectors.toList());
System.out.println("collect1 : " + collect1);
// 输出发现:collect1 : [[Ljava.lang.String;@799f7e29, [Ljava.lang.String;@4b85612c]
// 并不是我们想要的
这个方法的问题在于,传递给map方法的Lambda为每个单词返回了一个String[](String列表) 。因此,map返回的流实际上是Stream<String[]>类型的。你真正想要的是用Stream来表示一个字符流。
flatMap登场
List<String> collect2 = strings.stream()
.map(e -> e.split(""))
.flatMap(Arrays::stream)// 将各个流生成为扁平化的单个流
.distinct()
.collect(Collectors.toList());
// 输出:collect2 : [H, e, l, o, W, r, d]
匹配
anyMatch表示数据集中是不是有一个元素能够匹配给定的谓词
allMatch 表示流中的元素是否都能够匹配给定的谓词
noneMatch 表示流中没有匹配改给定的谓词
这三个方法都返回一个Boolearn,表明根据操作存在或者不存在。
boolean isHealthy = menu.stream()
.allMatch(d -> d.getAge() < 30);
查找
findAny方法表示返回当前流中的任意元素
Optional<YxUser> any = list.stream()
.filter(e -> e.getId() > 5)
.findAny();
Optional:是一个容器类,表示一个值存在还是不存在,避免findAny找不到值的时候导致null的情况
isPresent :表示optional包含值的时候返回true,反之false
ifPresent(Consumer t) :表示存在时,执行存在的代码块
T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。T orElse(T other)会在值存在时返回值,否则返回一个默认值
查找第一个元素 findFirst
Optional<Integer> first = integerList.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst();
归约
reduce:首先要有一个初始值,还有第二个参数是执行规约的规则
List<Integer> integerList = Arrays.asList(1, 2, 2, 2, 2, 2, 4, 5, 6, 7, 8);
Integer reduce = integerList.stream()
.reduce(0, (x, y) -> x + y);
Integer reduce = integerList.stream()
.reduce(0, Integer::sum);
这两个是一样的 还有Integer::MAX和MIN,初始值要注意
Integer::MAX和MIN
max和min不需要初始值,但是接受的时候,需要用Optional来接收
Optional<Integer> reduce = integerList.stream() .reduce(Integer::max); System.out.println("reduce: "+ reduce.get());
数值流
收集器使用groupingBy:通过用户的用户名进行分组如下
Map<String, List<YxUser>> collect3 = list.stream()
.collect(groupingBy(YxUser::getUsername));
/** 输出
collect3: {
lisi=[com.yangxin.demo.model.YxUser@69663380],
liuqi=[com.yangxin.demo.model.YxUser@5b37e0d2],
yanxgin=[com.yangxin.demo.model.YxUser@4459eb14],
caoxindi=[com.yangxin.demo.model.YxUser@5a2e4553],
zhaoliu=[com.yangxin.demo.model.YxUser@28c97a5],
zhangsan=[com.yangxin.demo.model.YxUser@6659c656],
wangwu=[com.yangxin.demo.model.YxUser@6d5380c2]}
*/
/***
*多级分组
*首先按照性别分组,然后按照id分组。
*/
Map<Integer, Map<String, List<YxUser>>> collect4 = list.stream()
.collect(groupingBy(YxUser::getSex, // 一级分类函数
groupingBy(e -> { // 二级函数
if (e.getId() > 5) return "hight";
else if (e.getId() < 4) return "small";
else return "midle";
})));
/** 输出
collect4: {
lisi={midle=[com.yangxin.demo.model.YxUser@69663380]},
liuqi={hight=[com.yangxin.demo.model.YxUser@5b37e0d2]},
yanxgin={small=[com.yangxin.demo.model.YxUser@4459eb14]},
caoxindi={small=[com.yangxin.demo.model.YxUser@5a2e4553]},
zhaoliu={hight=[com.yangxin.demo.model.YxUser@28c97a5]},
zhangsan={small=[com.yangxin.demo.model.YxUser@6659c656]},
wangwu={midle=[com.yangxin.demo.model.YxUser@6d5380c2]}}
*/
// 按照子组收集数据
Map<Integer, Long> collect = list.stream()
.collect(groupingBy(YxUser::getSex, counting()));
/**
* counting 可以换成maxBy、minBy
*/
如果是自己写的话,会嵌套多层循环,多级分组那么将会更难维护。
使用Collector收集数据
maxBy和minBy在collect中使用,但是参数是自定义的Comparator
Comparator<YxUser> comparator=Comparator.comparingInt(YxUser::getId);
Optional<YxUser> collect = list.stream()
.collect(minBy(comparator));
// 使用reducing
Optional<YxUser> mostCalorieDish = list.stream().
collect(reducing( (d1, d2) -> d1.getId() < d2.getId() ? d1 : d2));
summingInt,在collect中计算总和。
Integer collect = list.stream().collect(summingInt(YxUser::getId));
// 如果使用reducing
int total = list.stream()
.collect(reducing( 0, //初始值
YxUser::getId,//转换函数
Integer::sum);//累积函数
//第一个参数是归约操作的起始值,也是流中没有元素时的返回值,所以很显然对于数值和而言0是一个合适的值。
//第二个参数就是你在6.2.2节中使用的函数,将菜肴转换成一个表示其所含热量的int。
//第三个参数是一个BinaryOperator,将两个项目累积成一个同类型的值。这里它就是对两个int求和
int sum=list.stream()
.mapToInt(YxUSer::getId)
.sum();
还有类似的函数:averagingInt计算平均值
但是还可以通过summarizingInt可以一次性得到:对应的最大值、最小值、平均值、和、数量等信息,可以通过getter获取
joining连接字符串:
joining实现字符串连接,是使用的StringBuilder,进行字符串拼接的
String collect1 = list.stream().map(YxUser::getUsername).collect(joining());
System.out.println("collect1:" + collect1);
// 添加分割符
String collect2 = list.stream().map(YxUser::getUsername).collect(joining(", "));
System.out.println("collect2:" + collect2);
输出效果:
求和的几种形式:
list.stream().mapToInt(YxUser::getId).sum();
list.stream().map(YxUser::getId).reduce(Integer::sum).get();
list.stream().collect(reducing(0, YxUser::getId, Integer::sum));
list.stream().collect(reducing(0, YxUser::getId, (x, y) -> x + y));
字符串拼接的几种形式
list.stream().map(YxUser::getUsername).collect(reducing((s1,s2)->s1+s2)).get();
list.stream().collect(reducing("",YxUser::getUsername,(s1,s2)->s1+s2));
String collect2 = list.stream().map(YxUser::getUsername)
.collect(joining(", "));
// 从性能上考虑,建议使用joining
partitioningBy分区函数:
返回的主键是 boolean类型,只有true和false两种情况。分区其实就是分组的一种特殊情况。
Map<Boolean, List<YxUser>> collect = list.stream()
.collect(partitioningBy(YxUser::isX));
System.out.println("collect: " + collect);
函数大全
收集器Collector源码
Collector 首先有5个主要的函数:supplier、accumulator、combiner、finisher、characteristics。
supplier :调用这个函数的时候会创建一个空的累加器实例,供数据收集使用。
/** 官方的解释
* A function that creates and returns a new mutable result container.
*
* @return a function which returns a new, mutable result container
*/
Supplier<A> supplier();
accumulator : accumulator函数相当于是一个累加器,进行中间结果的处理。当遍历到流中第n个元素时,这个函数执行,时会有两个参数:保存归约结果的累加器(已收集了流中的前 n-1 个项目),还有第n个元素本身
/** 官方的解释
* A function that folds a value into a mutable result container.
*
* @return a function which folds a value into a mutable result container
*/
BiConsumer<A, T> accumulator();
finisher : finisher函数主要是最后的工作,主要是将最后的结果进行转换。finisher方法必须返回在累积过程的最后要调用的一个函数,以便将累加器对象转换为整个集合操作的最终结果。
/**
* Perform the final transformation from the intermediate accumulation type
* {@code A} to the final result type {@code R}.
*
* <p>If the characteristic {@code IDENTITY_TRANSFORM} is
* set, this function may be presumed to be an identity transform with an
* unchecked cast from {@code A} to {@code R}.
*
* @return a function which transforms the intermediate result to the final
* result
*/
Function<A, R> finisher();
supplier、accumulator、finisher这三个函数就完全够流的顺序归约了
combiner:combiner方法会返回一个供归约操作使用的函数,它定义了对流的各个子部分进行并行处理时,各个子部分归约所得的累加器要如何合并。
/**
* A function that accepts two partial results and merges them. The
* combiner function may fold state from one argument into the other and
* return that, or may return a new result container.
*
* @return a function which combines two partial results into a combined
* result
*/
BinaryOperator<A> combiner();
supplier、accumulator、finisher这三个函数加上combiner这个函数,可以对流进行并行归约了,有点相当于并发环境的fork/join框架,他主要的步骤有一下几步:
第一步:将原始的流分成子流,知道条件不能分为止(分得太小也不好)
第二步:所有的子流并行运行。
第三步:使用收集器combiner方法返回的函数,将所有的部分合并。
这个流程和并发的fork/join差不多,可以参考我一篇博客:https://www.cnblogs.com/yangdagaoge/articles/10541460.html。
characteristics :这个方法就是返回一个不可变的Characteristics,表示收集器的行为:
①:UNORDERED——归约结果不受流中项目的遍历和累积顺序的影响
②:CONCURRENT——accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。
③:IDENTITY_FINISH——这表明完成器方法返回的函数是一个恒等函数,可以跳过。
但是点击Collector的实现类的时候发现他只有一个Collectors实现类并且在Collectors中定义了一个内部类CollectorImpl,其中的实现特别简单。如下:
/**
* Simple implementation class for {@code Collector}.
*
* @param <T> the type of elements to be collected
* @param <R> the type of the result
*/
static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
// 一系列的成员函数
private final Supplier<A> supplier;
private final BiConsumer<A, T> accumulator;
private final BinaryOperator<A> combiner;
private final Function<A, R> finisher;
private final Set<Characteristics> characteristics;
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A,R> finisher,
Set<Characteristics> characteristics) {
this.supplier = supplier;
this.accumulator = accumulator;
this.combiner = combiner;
this.finisher = finisher;
this.characteristics = characteristics;
}
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Set<Characteristics> characteristics) {
this(supplier, accumulator, combiner, castingIdentity(), characteristics);
}
@Override
public BiConsumer<A, T> accumulator() {
return accumulator;
}
@Override
public Supplier<A> supplier() {
return supplier;
}
@Override
public BinaryOperator<A> combiner() {
return combiner;
}
@Override
public Function<A, R> finisher() {
return finisher;
}
@Override
public Set<Characteristics> characteristics() {
return characteristics;
}
}
1.toList源码
/**
* Returns a {@code Collector} that accumulates the input elements into a
* new {@code List}. There are no guarantees on the type, mutability,
* serializability, or thread-safety of the {@code List} returned; if more
* control over the returned {@code List} is required, use {@link #toCollection(Supplier)}.
*
* @param <T> the type of the input elements
* @return a {@code Collector} which collects all the input elements into a
* {@code List}, in encounter order
*/
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new,
//创建一个ArrayList类型的Supplier收集器
List::add,// 使用list的add函数将流中的数据添加到空结果容器中
(left, right) -> { left.addAll(right); return left; },
// lambda 表达式,将右边的list添加到左边的list中,这就是相当于一个combiner函数
CH_ID);// 表示收集器的行为参数
}
使用toList
List<User> collect = list.stream().collect(Collectors.toList());
toSet 的源码也是类型,不过吧Supplier 换成了 (Supplier<Set>) HashSet::new
/** * Returns a {@code Collector} that accumulates the input elements into a * new {@code Set}. There are no guarantees on the type, mutability, * serializability, or thread-safety of the {@code Set} returned; if more * control over the returned {@code Set} is required, use * {@link #toCollection(Supplier)}. * * <p>This is an {@link Collector.Characteristics#UNORDERED unordered} * Collector. * * @param <T> the type of the input elements * @return a {@code Collector} which collects all the input elements into a * {@code Set} */ public static <T> Collector<T, ?, Set<T>> toSet() { return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add, (left, right) -> { left.addAll(right); return left; }, CH_UNORDERED_ID); }
2. 字符拼接joining源码
①.无分隔符
/**
* Returns a {@code Collector} that concatenates the input elements into a
* {@code String}, in encounter order.
*
* @return a {@code Collector} that concatenates the input elements into a
* {@code String}, in encounter order
*
* CharSequence:这个是字符串序列接口
* joining的源码可得,实现字符串拼接是使用 StringBuilder实现的,
*/
public static Collector<CharSequence, ?, String> joining() {
return new CollectorImpl<CharSequence, StringBuilder, String>(
// 创建StringBuilder的结果容器
// StringBuilder::append:拼接函数(累加器部分)
StringBuilder::new, StringBuilder::append,
// 联合成一个值,combiner部分
(r1, r2) -> { r1.append(r2); return r1; },
// 最后结果的转换
StringBuilder::toString, CH_NOID);
}
CharSequence 这是个字符串的序列接口,String、StringBuffer、StringBuilder也是实现这个接口。它和String的区别就是,String可读不可变,CharSequence是可读可变
使用字符串拼接
static List<User> list = Arrays.asList(
new User("y杨鑫", 50, 5455552),
new User("张三", 18, 66666),
new User("李四", 23, 77777),
new User("王五", 30, 99999),
new User("赵柳", 8, 11111),
new User("王八蛋", 99, 23233)
);
public static void main(String[] args) {
String collect = list.stream().map(User::getUsername)
.collect(joining());
System.out.println("collect: " + collect);
}
输出/
collect: y杨鑫张三李四王五赵柳王八蛋
②.带分割符的
/**
* Returns a {@code Collector} that concatenates the input elements,
* separated by the specified delimiter, in encounter order.
* 返回一个带分割符的拼接串
* @param delimiter the delimiter to be used between each element
* @return A {@code Collector} which concatenates CharSequence elements,
* separated by the specified delimiter, in encounter order
* 将分割符传给了joining三参数的重载函数
*/
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter){
return joining(delimiter, "", "");
}
/**
* Returns a {@code Collector} that concatenates the input elements,
* separated by the specified delimiter, with the specified prefix and
* suffix, in encounter order.
*
* @param delimiter the delimiter to be used between each element
* @param prefix the sequence of characters to be used at the beginning
* of the joined result
* @param suffix the sequence of characters to be used at the end
* of the joined result
* @return A {@code Collector} which concatenates CharSequence elements,
* separated by the specified delimiter, in encounter order
*
* 在这个函数中,使用了一个叫StringJoiner的类,这个是java8的封装类,主要的功能是
* 按照 分割符delimiter,字符串开始 prefix,字符串结尾suffix,进行字符串的拼接
*/
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {
return new CollectorImpl<>(
// 创建一个Supplier结果容器
() -> new StringJoiner(delimiter, prefix, suffix),
// 字符串的添加相当于 accumulator累加器部分;merge是联合将两个数值整合成一个,相当于combiner部分
StringJoiner::add, StringJoiner::merge,
// toString做最后的结果转换
StringJoiner::toString, CH_NOID);
}
运行样例
String collect = list.stream().map(User::getUsername)
.collect(joining());
System.out.println("collect: " + collect);
String collect1 = list.stream().map(User::getUsername)
.collect(joining(","));
System.out.println("collect1: " + collect1);
String collect2 = list.stream().map(User::getUsername)
.collect(joining(",","[","]"));
System.out.println("collect2: " + collect2);
///输出//
collect: y杨鑫张三李四王五赵柳王八蛋
collect1: y杨鑫,张三,李四,王五,赵柳,王八蛋
collect2: [y杨鑫,张三,李四,王五,赵柳,王八蛋]
StringJoiner源码:
public final class StringJoiner { /** * prefix:表示字符串拼接的前缀 * suffix:表示字符串拼接的结尾 * delimiter: 表示分割符 * */ private final String prefix; private final String delimiter; private final String suffix; /* * StringBuilder的值。构造器从prefix开始添加元素,delimiter分割,但是没有 * 结尾符suffix,那么我们每次会更容易的去拼接字符串 */ private StringBuilder value; /* * 默认情况,由prefix和suffix拼接的字符串,在返回值的时候使用toString转换。 * 当没有元素添加的时候,那么这个为空,这很有可能被用户去覆盖一些其他值,包括空串 */ private String emptyValue; /** * 构造器只有delimiter分隔符,prefix和suffix将默认为空串, */ public StringJoiner(CharSequence delimiter) { this(delimiter, "", ""); } /** * 三参数的构造器 */ public StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix) { Objects.requireNonNull(prefix, "The prefix must not be null"); Objects.requireNonNull(delimiter, "The delimiter must not be null"); Objects.requireNonNull(suffix, "The suffix must not be null"); // make defensive copies of arguments this.prefix = prefix.toString(); this.delimiter = delimiter.toString(); this.suffix = suffix.toString(); this.emptyValue = this.prefix + this.suffix; } /** * 设置空值 */ public StringJoiner setEmptyValue(CharSequence emptyValue) { this.emptyValue = Objects.requireNonNull(emptyValue, "The empty value must not be null").toString(); return this; } /** * * 重写的toString,字符串将是prefix开始,suffix结尾,除非没有添加任何元素,那 * 么就返回空值 */ @Override public String toString() { if (value == null) { return emptyValue; } else { if (suffix.equals("")) { return value.toString(); } else { int initialLength = value.length(); String result = value.append(suffix).toString(); // reset value to pre-append initialLength value.setLength(initialLength); return result; } } } /** * 添加一个拼接的串 * * @param newElement The element to add * @return a reference to this {@code StringJoiner} */ public StringJoiner add(CharSequence newElement) { prepareBuilder().append(newElement); return this; } /** * 将拼接的字串合并 */ public StringJoiner merge(StringJoiner other) { Objects.requireNonNull(other); if (other.value != null) { final int length = other.value.length(); // lock the length so that we can seize the data to be appended // before initiate copying to avoid interference, especially when // merge 'this' StringBuilder builder = prepareBuilder(); builder.append(other.value, other.prefix.length(), length); } return this; } private StringBuilder prepareBuilder() { if (value != null) { value.append(delimiter); } else { value = new StringBuilder().append(prefix); } return value; } /** * 返回长度 */ public int length() { // Remember that we never actually append the suffix unless we return // the full (present) value or some sub-string or length of it, so that // we can add on more if we need to. return (value != null ? value.length() + suffix.length() : emptyValue.length()); } }
测试
StringJoiner joiner = new StringJoiner(",", "[", "]"); for (YxUser x : list) { joiner.add(x.getUsername()); } joiner.merge(joiner); // 如果没有merge将输出:joiner: [yanxgin,12,yan34xgin,56,78,90,666] /** 使用joiner.merge(joiner),将输出joiner: [yanxgin,12,yan34xgin,56,78,90,666,yanxgin,12,yan34xgin,56,78,90,666],使用merge将另外一个的StringJoiner合并进来,所以在这儿,他将已经又合并了一次 */ System.out.println("joiner: " + joiner);
3.toCollection源码
/**
*
* 返回一个{@Code Collector}收集器(输入的元素累加到新的收集器)
* {@Code Collection}集合是由工厂创建
*
* @param <T> 输入类型
* @param <C> 收集器{@code Collection}的结果类型
* @param 这个集合工厂collectionFactory将返回一个新的适当类型的收集器
* @return 按照顺序将输入的元素收集到一个{@Code Collector}并且返回
*
* 函数功能:按照collectionFactory收集器的类型重新收集流中的数据,
* 例如:
* {@Code
* LinkedList<YxUser> collect1 = list.stream().collect(Collectors.toCollection(LinkedList::new));
* // LinkedList可以换成Collection的其他集合类型
* }
*/
public static <T, C extends Collection<T>>
Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
return new CollectorImpl<>(collectionFactory, Collection<T>::add,
(r1, r2) -> { r1.addAll(r2); return r1; },
CH_ID);
}
实现列子
// toCollection的参数的意义就是创建一个类型的集合来收集他。
list.stream().collect(Collectors.toCollection(LinkedList::new));
list.stream().collect(Collectors.toCollection(TreeSet::new));
......
toList、toSet、toCollection区别:
toList:表示可以重复、有序。
toSet:表示不可重复、无序。
toCollection:自定义实现Collection的数据结构收集。
4. mapping源码
/**
* Adapts a {@code Collector} accepting elements of type {@code U} to one
* accepting elements of type {@code T} by applying a mapping function to
* each input element before accumulation.
*
* 在输入元素的累加前,使用mapping函数将一个接受U类型({@code U})的收集器调
* 整为接受T类型({@code T})的收集器。**感觉翻译不太对。
*
* @apiNote
* {@code mapping()} mapping适用于多层次的筛选,
* 例如,Person实体类集合中,计算出每个城市的姓名、
* <pre>{@code
* Map<City, Set<String>> lastNamesByCity
* = people.stream().collect(groupingBy(Person::getCity,
* mapping(Person::getLastName, toSet())));
* }</pre>
*
* @param <T> 输入元素的类型。
* @param <U> 接受元素的类型
* @param <A> 收集器的中间累加器的类型
* @param <R> 收集器的结果类型
* @param 应用于输入元素的函数
* @param downstream 收集器接受一个mapper的值
* @return a collector which applies the mapping function to the input
* elements and provides the mapped results to the downstream collector
*/
public static <T, U, A, R>
Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,
Collector<? super U, A, R> downstream) {
BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
return new CollectorImpl<>(downstream.supplier(),
(r, t) -> downstreamAccumulator.accept(r, mapper.apply(t)),
downstream.combiner(), downstream.finisher(),
downstream.characteristics());
}
样例
// 获取邮件的性别
list.stream().collect(groupingBy(YxUser::getEmail, mapping(YxUser::getSex, toList())));
5.collectingAndThen
/**
*
* 调整一个收集器{@Code Collector} 去完成一个额外的转换。例如,
* {@link #toList()}的调节使得收集器总是产生一个不可变的list。
* <pre>{@code
* List<String> people
* = people.stream().collect(collectingAndThen(toList(), Collections::unmodifiableList));
* }</pre>
*
* @param <T> 输入元素的类型
* @param <A> downstream collector收集器中间累加的结果类型
* @param <R> downstream collector收集器的结果
* @param <RR> 结果收集器的类型
* @param downstream a collector
* @param finisher 是一个完成最终功能的函数
* @return 返回一个收尾完成的结果搜集器
*/
public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,
Function<R,RR> finisher) {
// 获取收集器的行为特性。
Set<Collector.Characteristics> characteristics = downstream.characteristics();
// 如果这个搜集器 是一个恒等函数
if (characteristics.contains(Collector.Characteristics.IDENTITY_FINISH)) {
if (characteristics.size() == 1)
// 长度如果为1,那么置空
characteristics = Collectors.CH_NOID;
else {
// 使用EnumSet枚举集合类来创建一个具有characteristics特征的枚举。
characteristics = EnumSet.copyOf(characteristics);
// 去掉恒等行为
characteristics.remove(Collector.Characteristics.IDENTITY_FINISH);
//unmodifiableSet 表示是不可改的Set,与此类似的还有 unmodifiableMap
characteristics = Collections.unmodifiableSet(characteristics);
}
}
/不是太明白为什么要做这个if的操作。
return new CollectorImpl<>(downstream.supplier(),
downstream.accumulator(),
downstream.combiner(),
downstream.finisher().andThen(finisher),
characteristics);
}
获取性别分组中最高的Id
list.stream().collect(groupingBy(YxUser::getSex, collectingAndThen(maxBy(Comparator.comparingInt(YxUser::getId)), Optional::get)));
输出
collect3: {0=YxUser{id=6, username='90', password='222', lastVisitTime=null, email='8237216470@qq.com', activation=null, createTime=null}, 1=YxUser{id=7, username='666', password='222', lastVisitTime=null, email='823721670@qq.com', activation=null, createTime=null}}
6.counting
/**
* 返回一个流的数量,如果是空,那么返回为0.
*
* @implSpec
* 这个函数的实现是依靠于 reducing实现的。
* <pre>{@code
* reducing(0L, e -> 1L, Long::sum)
* }</pre>
*
* @param <T> 输入元素的类型
* @return a {@code Collector} 返回一个count
*/
public static <T> Collector<T, ?, Long>
counting() {
return reducing(0L, e -> 1L, Long::sum);
}
几种count的方式
long count = list.stream().count();
System.out.println("count: " + count);
Long collect4 = list.stream().collect(counting());
System.out.println("collect4: " + collect4);
list.stream().collect(reducing(0, YxUser::getId, (x, y) -> x + y));
7.minBy
/**
*
* minBy其实实现的原理也就是
* return (a,b)->comparator.compare(a,b) < 0 ? a : b
*
* 但是是使用Optional<T>来接受,防止空值
*
* @implSpec
* This produces a result equivalent to:
* <pre>{@code
* reducing(BinaryOperator.minBy(comparator))
* }</pre>
*
* @param <T> 输入元素的类型
* @param comparator 是一个比较器
* @return 一个最小值
*/
public static <T> Collector<T, ?, Optional<T>>
minBy(Comparator<? super T> comparator) {
return reducing(BinaryOperator.minBy(comparator));
}
使用minBy
//自己构造的比较器,是依据YxUser中的Id来比较。
Comparator<YxUser> comparator=Comparator.comparingInt(YxUser::getId);
list.stream().collect(groupingBy(YxUser::getUsername, minBy(comparator)));
maxBy和这个差不多。
summingInt 函数,是返回最大值、最小值、平均值、count值等,使用getter方法获取即可
IntSummaryStatistics collect5 = list.stream().collect(summarizingInt(YxUser::getId)); collect5.getAverage();
由于类型原因,还提供了summingLong、summingDouble。
求平均值的:averagingInt、averageingLong、averagingDouble
自定义一个收集器
在Collector和Collectors的源码得知,在Collectors中,使用内部类CollectorImpl来实现的Collector,然后其他的toList、joining等一系列的操作都是,依托于这个内部类CollectorImpl,所以我们可以自定义一个收集器。他的功能是:使用ArrayList来收集流中的数据:
/**
* @author yangxin
* @time 2019/8/6 15:02
*/
public class ToListCollector<T> implements Collector<T, List<T>, List<T>> {
// 既然是使用ArrayList来收集流中的数据,那么在supplier中要创建一个ArrayList结果容器,
//在CollectorImpl中,他在这儿将创建圈交给了外部。
@Override
public Supplier<List<T>> supplier() {
return ArrayList::new;
}
// 累加器部分,肯定就是添加元素
@Override
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}
// 联合部分,量右边的值归并到左边
@Override
public BinaryOperator<List<T>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;
};
}
@Override
public Function<List<T>, List<T>> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
// 为收集器添加IDENTITY_FINISH和CONCURRENT标记
return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH, CONCURRENT));
}
}
使用
List<YxUser> list = Arrays.asList(
new YxUser(1, "yanxgin", "222", "8237251670@qq.com", 1, true),
new YxUser(2, "12", "222", "8237216670@qq.com", 1, false),
new YxUser(3, "yan34xgin", "222", "823721670@qq.com", 0, true),
new YxUser(4, "56", "222", "823721670@qq.com", 0, false),
new YxUser(5, "78", "222", "82372163@qq.com", 1, false),
new YxUser(6, "90", "222", "8237216470@qq.com", 0, false),
new YxUser(7, "666", "222", "823721670@qq.com", 1, true)
);
// 自定义的收集器
List<YxUser> collect = list.stream()
.collect(new ToListCollector<YxUser>());
System.out.println("collect: " + collect);
自己写的ToListCollector 收集器,和toList的差不多,估计唯一的区别,就是toList有工厂类自己创建,自定义的收集器需要new。我们可以创建出适用自己的收集器
优化代码
在很多情况下使用java8的特性可以优化很多代码,使得代码更清晰、更易读。
①:使用Lambda表达式取代匿名类。
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println("word");
}
};
Runnable runnable1 = () -> System.out.println("hello");
需要注意的地方:
Lambda表达式中的this,和super的含义是不同的。在匿名类中,this表示本身;但在Lambda中,他表示的是包含类。然而,匿名类可以屏蔽包含类的变量,Lambda不能屏蔽。
还有一个问题是:假如有重载函数的时候,因为Lambda表达式的参数根据上下文来确定的,所以会出现有多个匹配项的存在。这个编译器会帮忙解决这个问题比如:IDEA
int n=10; Runnable runnable2 = () -> { int n = 2;//会报错 System.out.println("n: " + n); }; Runnable runnable = new Runnable() { @Override public void run() { int n = 2; System.out.println("word" + n); } }; runnable.run(); runnable2.run();
②:使用方法引用重构Lambda表达式。
③:使用StreamAPI重构命令式的数据处理。
使用Lambda重构面对对象的设计模式
1.策略模式
策略模式就是解决一类算法的通用解决方案。通常有3部分组成:①:一个母接口(他代表某个需求)。②:实现母接口的各种策略方法。③:策略对象的客户。
在传统的方法中,我们要使用策略模式这三个步骤不可少,但是有时候功能就比较简单,比如比较学生成绩大小,找出性别男女的人数、成绩平均值等,那么你需要去实现对应的策略函数,然后使用的时候如下:
// IsAllLowerCase、IsNumeric就是实现的策略函数
Validator numericValidator = new Validator(new IsNumeric());
boolean b1 = numericValidator.excute("aaaa");
Validator lowerCaseValidator = new Validator(new IsAllLowerCase ());
boolean b2 = lowerCaseValidator.excute("bbbb");
使用Lambda,那么就会省略策略实现部分:
// 函数式接口
public interface Factory {
boolean execete(String s);
}
// 客户端
public class FactoryImpl {
private final Factory factory;
public FactoryImpl(Factory factory) {
this.factory = factory;
}
public boolean execute(String s){
return factory.execete(s);
}
}
// 使用部分 策略模式,在简单的策略就用实现多余的策略方法。
FactoryImpl factory = new FactoryImpl((String s) -> s.matches("[a-z]+"));
System.out.println("boolean: " + factory.execute("ddd"));
2.模板方法
模板方法就是希望使用这个算法,并且呢可以支持对其中的修改,才能达到希望的效果,正常情况是使用一个抽象的方法,让别人继承这个类的时候实现方法,实现自己的功能。每一次都需要实现的话,重复度太大了。
public YxUser processCustomer(Consumer<YxUser> makeCustomerHappy) {
YxUser c = new YxUser();
makeCustomerHappy.accept(c);
return c;
}
/**
* 在processCustomer中直接使用Lambda将重写的部分写进去,这样避免了大量的继承实现。
*/
YxUser yx = new FactoryImpl().processCustomer(e -> e.setUsername("杨鑫"));
3.观察者模式
一个事件发生,主题去通知所有观察者,观察者会根据自己观察的东西进行操作。
实现观察者模式:①观察者Observer接口;②实现Observer接口的各种观察者。③实现观察者注册和通知的Subject接口和其实现。
使用Lambda,在简单的观察者模式来说,可以避免第②部分的操作,但是观察者的逻辑过多时,感觉lambda不适应。
mark
demo地址:https://github.com/fireshoot/java8