了解Stream
Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找,过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。
1.Stream自己不会储存数据 。
2.Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream。
3.Stream操作是延迟执行的。这意味着它们会等到需要结果的时候才执行。
注意:请区别io流和stream流
流组成:数据源,用于传输数据的管道(用于一系列流水线式中间操作),产生的新流。
Stream的三个操作步骤:1.创建Stream 2.中间操作 3.终止操作(终端操作,即输出,如println)
创建Stream
@Test
public void test1(){
//1.可以通过Conllection系列集合提供的stream()或parallelStream获取
//stream是串行流,parallelStream是并行流
List<String> list=new ArrayList<>();
Stream<String> stream=list.stream();
//2.通过Arrays中的静态方法stream()获取数组流
Employee[] employees=new Employee[10];
Stream<Employee> stream1 = Arrays.stream(employees);
//3.通过Stream类中的静态方法of()
Stream<String> stream2 = Stream.of("aa", "bb", "cc");
//4.创建无限流
//迭代
Stream<Integer> stream3 = Stream.iterate(0, x -> x + 2);
stream3.limit(10).forEach(System.out::println);
//生成
Stream.generate(()->Math.random()*10).limit(20).forEach(System.out::println);
}
Stream的中间操作
中间操作有:1.筛选和切片 2.映射 3.排序
注意:多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作(如:forEach),否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为惰性求值,或叫做延迟加载。
内部迭代:迭代操作由Stream API完成
区别外部迭代:
//外部迭代
@Test
public void test6(){
Iterator<Employee> iterator= employees.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
筛选和切片
/*
*筛选与切片
* filter - 接收Lambda,从流中排除某些元素
* limit - 截断流,是其元素不超过给定数量
* skip(n)- 跳过元素,返回一个扔掉了前n个元素的元素。若流中元素不足n个,则返回一个空流。与limit(n) 互补
* distinct - 筛选,通过流所生成元素的hashcode()和equal()去除重复元素(需要重写hashcode()和equals方法)
*/
@Test
public void test2(){
//中间操作
Stream<Employee> employeeStream = employees.stream().filter(x -> {
System.out.println("Stream API");
return x.getAge() < 20;});
//终止操作
employeeStream.forEach(System.out::println);
System.out.println("================================");
employees.stream().filter(x -> x.getAge() > 20).limit(2).forEach(System.out::println);
System.out.println("================================");
employees.stream().filter(x -> x.getAge()> 20).skip(2).forEach(System.out::println);
System.out.println("================================");
employees.stream().filter(x -> x.getAge()> 20).distinct().forEach(System.out::println);
}
映射
/*
*映射
* map - 接收Lambda。将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素,
* 并将其映射成一个新的元素。
* flatMap - 接收一个函数作为参数,将流中的每个值都换成另一个流,将所有流连接成一个流
*
* map和flatMap就相当于add(Object o)和addAll(Collection coll)
* 若add方法中传入的值为一个集合,该方法会把整个集合添加到当前集合中
* addAll则是将传入集合中的所有值添加到当前集合中
*/
@Test
public void test3(){
//map作用,将集合流中的所有元素应用到指定函数上,然后生成一个新流
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
//将aaa,bbb,ccc映射到toUpperCase函数上,最后产生一个新流
list.stream().map(str->str.toUpperCase()).forEach(System.out::println);
System.out.println("================================");
employees.stream().map(Employee::getName).forEach(System.out::println);
//flatMap前
System.out.println("================================");
Stream<Stream<Character>> streamStream = list.stream().map(Stream1::filterCharater);
streamStream.forEach((t)->t.forEach(System.out::println));
//flatMap后
System.out.println("================================");
list.stream().flatMap(Stream1::filterCharater).forEach(System.out::println);
}
public static Stream<Character> filterCharater(String str){
List<Character> list=new ArrayList<>();
for(Character c:str.toCharArray()){
list.add(c);
}
return list.stream();
}
排序
/*
* 排序
* sorted()- 自然排序(Comparable)
* sorted(Comparator com)- 定制排序(Comparator)
* */
@Test
public void test4(){
//自然排序
List<String> list=Arrays.asList("aaa","bbb","ccc");
list.stream().sorted().forEach(System.out::println);
//定制排序
employees.stream().sorted((x,y)->x.getAge()-y.getAge()).forEach(System.out::println);
}
终止操作
/*
* 查找与匹配
* allMatch - 检查是否匹配所有元素
* anyMatch - 检查是否至少匹配一个元素
* noneMatch - 检查是否没有匹配所有元素
* findFirst - 返回第一个元素
* findAny - 返回当前流中的任意元素
* count - 返回流中元素的总个数
* max - 返回流中最大值
* min - 返回流中最小值
* */
@Test
public void test5(){
//allMatch
boolean bl = employees.stream().allMatch(e -> e.getStatus().equals(Employee.Status.Free));
System.out.println(bl);
//anyMatch
boolean bl1 = employees.stream().anyMatch(e -> e.getStatus().equals(Employee.Status.Free));
System.out.println(bl1);
//noneMatch
boolean bl2 = employees.stream().anyMatch(e -> e.getStatus().equals(Employee.Status.Free));
System.out.println(bl2);
//findFirst
Optional<Employee> first = employees.stream().sorted((x, y) -> (int) (x.getSalary() - y.getSalary())).findFirst();
//如果Optional容器为空,则可以使用orElse方法存放一个值替代,相当于if(xxx==null)
first.orElse(new Employee("shen",1,1, Employee.Status.Busy));
System.out.println(first.get());
//findAny
Optional<Employee> any = employees.stream().filter(x -> x.getStatus().equals(Employee.Status.Free)).findAny();
System.out.println(any.get());
//区别:parallelStream是多个线程同时找,谁先找到算谁的
//stream是一个一个找
Optional<Employee> any1 = employees.parallelStream().filter(x -> x.getStatus().equals(Employee.Status.Free)).findAny();
System.out.println(any1.get());
//count
long count = employees.stream().count();
System.out.println(count);
//max
Optional<Employee> max = employees.stream().max((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
//Optional<Employee> max1 = employees.stream().max(Double::compare);报错原因:Double中compare的两个入参类型为Double
System.out.println(max.get());
//min
Optional<Double> min = employees.stream().map(Employee::getSalary).min(Double::compare);
System.out.println(min.get());
}
规约
过程:先将第一个参数0赋值给x,再从stream中的第一个元素赋值给y,x+y的返回值赋值给x,在从stream中取出第二个元素当作y,x+y的返回值赋值给x,以此类推。
/*
* 归约
* reduce(T identity,BinaryOperator)/reduce(BinaryOperator) - 可以将流中元素反复结合起来,得到一个值
* */
@Test
public void test7(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(reduce);
Optional<Double> reduce1 = employees.stream().map(Employee::getSalary).reduce(Double::sum);
// 等价于:Double reduce1 = employees.stream().map(Employee::getSalary).reduce(0.0, (x, y) -> x + y);
// 因为没有起始值,所以使用Optional(为了避免空指针),有起始值则使用Double
System.out.println(reduce1.get());
}
收集
/*
* 收集
* collect - 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方式
* */
@Test
public void test8(){
//存在一个支持Collector的工具类:Collectors
//List
List<String> collect = employees.stream().map(Employee::getName).collect(Collectors.toList());
collect.forEach(System.out::println);
//Set
Set<String> collect1 = employees.stream().map(Employee::getName).collect(Collectors.toSet());
collect1.forEach(System.out::println);
//指定一些特殊的数据结构,如HashSet
HashSet<String> collect2 = employees.stream().map(Employee::getName).collect(Collectors.toCollection(HashSet::new));
collect2.forEach(System.out::println);
//总数
Long collect3 = employees.stream().collect(Collectors.counting());
System.out.println(collect3);
//平均值
Double collect4 = employees.stream().collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(collect4);
//总工资
Double collect5 = employees.stream().collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(collect5);
//最大值
Optional<Employee> collect6 = employees.stream().collect(Collectors.maxBy((x, y) -> Double.compare(x.getSalary(), y.getSalary())));
System.out.println(collect6);
}
分组
/*
* 分组
* */
@Test
public void test9(){
Map<Employee.Status, List<Employee>> collect = employees.stream().collect(Collectors.groupingBy(Employee::getStatus));
//map不能使用forEach语句
System.out.println(collect);
//多级分组
Map<Employee.Status, Map<String, List<Employee>>> collect1 = employees.stream().collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((x) -> {
if (x.getAge() < 18) {
return "未成年";
} else {
return "成年";
}
})));
System.out.println(collect1);
}
分区
/*
* 分区
* 分为true和false两区
* */
@Test
public void test10(){
Map<Boolean, List<Employee>> collect = employees.stream().collect(
Collectors.partitioningBy(e -> e.getAge() > 18)
);
System.out.println(collect);
}
集合Summerizing
/*
* 集合总结
* */
@Test
public void test11(){
DoubleSummaryStatistics collect = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(collect.getMax());
System.out.println(collect.getMin());
System.out.println(collect.getAverage());
System.out.println(collect.getCount());
}
并行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。Java8对并行进行了优化,我们可以很容易得对数据进行并行操作。Stream API可以声明性地通过parallel()与sequential()在并行流与顺序流之间进行切换。
Fork/Join框架
Fork/Join框架就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行Join汇总。
Fork/Join框架的优势:
采用“工作窃取”模式(work-stealing):当处理多个子问题时,如果某个子线程处于等到状态,那么该线程会主动寻找其他线程尚未运行的子问题来执行。这种方式减少了线程的等待时间,提高了性能。
java8之前并行Fork/Join代码执行过程
缺点:操作起来非常麻烦
public class ForkJoinCalculate extends RecursiveTask<Long> {
long start;
long end;
long THRESHOLD=10000;
public ForkJoinCalculate(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = start - end;
if(length<+THRESHOLD){
long sum = 0;
for(long i=start;i<=end;i++){
sum+=i;
}
return sum;
}else {
long mid = (start + end) / 2;
ForkJoinCalculate left=new ForkJoinCalculate(start,mid);
left.fork();
ForkJoinCalculate right = new ForkJoinCalculate(mid, end);
right.fork();
return left.join()+right.join();
}
}
public static void main(String[] args) {
//ForkJoin的执行需要一个池
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task=new ForkJoinCalculate(1,100000L);
Long sum = pool.invoke(task);
System.out.println(sum);
}
}
java8之后执行并行代码,parallel()底层还是Fork/Join
优点:效率更高,代码更简单
@Test
public void test13(){
//顺序流变并行流用parallel(),parallel()底层是ForkJoin
LongStream.rangeClosed(0,10000).parallel().reduce(0,Long::sum);
}
Employee素材
List<Employee> employees = Arrays.asList(
new Employee("张三", 18, 1999,Employee.Status.Free),
new Employee("李四", 19, 929, Employee.Status.Busy),
new Employee("王五", 20, 123, Employee.Status.VOCATION),
new Employee("赵六", 10, 10000, Employee.Status.Free),
new Employee("田七", 30, 100, Employee.Status.Busy));