1. Stream

Java8中有两大最为重要的改变。第一个是Lambda表达式的出现。另外一个则是Stream API。我们可以通过Stream API对集合进行各种操作。且通过Stream API对集合进行操作是非常方便高效的。


1.1 Stream是什么

Stream是加工数据的流水线

Stream本身不会存数据,也不会改变源对象。Stream操作是延迟执行的,也就是说Stream会等到需要结果的时候才执行。


1.2 Stream三板斧

A. 创建Stream:根据一个数据源,创建出一个操作该数据源的流水线

B. 中间操作:一个中间操作链,指定对数据源中的数据如何处理

C. 终止操作:一个终止操作,执行中间操作链,并产生结果


4. Stream_List



1.2.1 创建Stream

@Test

public void test() {

通过Collections.stream()来根据集合创建Stream

List<String> list = new ArrayList<>(Arrays.asList("Andy Eason G.E.M".split(" ")));

Stream<String> stream = list.stream();


通过Arrays.stream()来根据数组创建Stream

User[] users = new User[10];

Stream<User> stream2 = Arrays.stream(users);


通过Stream.of()来根据直接提供的数据创建Stream

Stream<String> stream3 = Stream.of("foo bar baz".split(" "));


通过“迭代”创建无限Stream

Stream<Integer> stream4 = Stream.iterate(0, x -> x + 2);


通过“生成”创建无限Stream

Stream<Integer> stream5 = Stream.generate(() -> new Random().nextInt(100));


Stream targetStream = stream5;

targetStream.limit(10).forEach(System.out::println);

}


1.2.3 中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发了终止操作,否则中间操作是不会有实际的执行的。在终止操作时一次性全部处理,称为“惰性求值”。


测试数据

@Before

public void before() {

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

User user = new User(1, "Andy", LocalDate.parse("1961-09-27", formatter), 3000d);

User user2 = new User(2, "Eason", LocalDate.parse("1974-07-27", formatter), 2500d);

User user3 = new User(3, "G.E.M", LocalDate.parse("1991-08-16", formatter), 4000d);

蔡徐坤", LocalDate.parse("1998-08-02", formatter), 1200d);

关晓彤", LocalDate.parse("1997-09-17", formatter), 6000d);


userList = new ArrayList<>(Arrays.asList(user, user2, user3, user4, user5));

}



筛选

filter:从流中过滤出指定元素, 筛选的特点是,Stream中的数据类型以前是什么,筛选完以后的结果也是什么类型

@Test

public void test() {

Stream<User> s = userList.stream();

Stream<User> s2 = s.filter(u -> u.getBalance() > 3000);

// print false

System.out.println(s == s2);

s2.forEach(System.out::println);

}


切片

limit:指定从流中获取数据的个数

@Test

public void test() {

userList.stream()

.filter(u -> u.getBalance() > 1000d)

.limit(3)

.forEach(System.out::println);

}


skip:跳过满足条件的前n个元素,若流中元素不足n个,则返回一个空流

@Test

public void test() {

userList.stream()

.filter(u -> u.getBalance() > 1000d)

.skip(2)

.forEach(System.out::println);

}


distinct:消除重复元素 (distinct利用hashCode和equals来判断元素是否重复,所以我们要让目标类重写Object的hashCode和equals方法,而如果在目标类上使用了lombok的@Data注解,则lombok会帮我们重写hashCode和equals方法)

@Test

public void test() {

userList.stream()

.filter(u -> u.getBalance() > 3000)

.distinct()

.forEach(System.out::println);

}


映射

map 映射的特点是, 流中的数据以前是什么类型,经过映射以后,类型可能会变成另外一种类型

@Test

public void test() {

userList.stream()

.map(u -> u.getName())

.forEach(System.out::println);

}

以上代码可以进一步简化为以下代码:

@Test

public void test() {

userList.stream()

.map(User::getName)

.forEach(System.out::println);

}


flatMap

有如下场景,出现了嵌套Stream对象的情况,即一个Stream的元素又是一个Stream:

public class Foo {


public static Stream<Character> f1(String str) {

List<Character> list = new ArrayList<>();

for (int i = 0; i < str.length(); i++) {

list.add(str.charAt(i));

}

return list.stream();

}

}


遍历Stream中的每个元素,而每个元素又是一个Stream,需要再对这个Stream元素进行遍历:

@Test

public void test() {

List<String> list = Arrays.asList("Andy Eason G.E.M S.H.E Ekin".split(" "));


Stream<Stream<Character>> stream = list.stream()

.map(Foo::f1);


stream.forEach(s -> s.forEach(System.out::println));

}


使用flatMap可以简化以上的代码:

@Test

public void test() {

List<String> list = Arrays.asList("Andy Eason G.E.M S.H.E Ekin".split(" "));


Stream<Character> stream = list.stream()

.flatMap(Foo::f1);


stream.forEach(System.out::println);

}


排序

sorted() 自然排序

@Test

public void test() {

List<String> list = Arrays.asList("Eason Andy Bob Dracula Clark".split(" "));


list.stream().sorted()

.forEach(System.out::println);

}


sorted(Comparator) 定制排序

@Test

public void test() {

List<String> list = Arrays.asList("Eason Andy Bob Dracula Clark ".split(" "));


list.stream().sorted((e1, e2) -> Integer.compare(e1.length(), e2.length()))

.forEach(System.out::println);

}


终止操作:查找与匹配

allMatch 是否所有元素都匹配条件

@Test

public void test() {

boolean b = userList.stream().allMatch(e -> e.getBalance() > 1000);

System.out.println("b = " + b);

boolean b2 = userList.stream().allMatch(e -> e.getBalance() > 3000);

System.out.println("b2 = " + b2);

}


anyMatch 是否有至少一个元素匹配条件

@Test

public void test() {

boolean b = userList.stream().anyMatch(e -> e.getBalance() < 1000);

System.out.println("b = " + b);

boolean b2 = userList.stream().anyMatch(e -> e.getBalance() > 3000);

System.out.println("b2 = " + b2);

}


noneMatch 是否所有元素都不匹配条件

@Test

public void test() {

boolean b = userList.stream().noneMatch(e -> e.getBalance() < 1000);

System.out.println("b = " + b);

boolean b2 = userList.stream().noneMatch(e -> e.getBalance() < 3000);

System.out.println("b2 = " + b2);

}


findFirst 返回第一个元素

@Test

public void test() {

userList.stream().filter(u -> u.getBalance() < 3000)

.findFirst().ifPresent(System.out::println);

}


findAny 返回当前流中的任意一个元素(在同步stream中也总是返回第一个,只有在异步stream中才具有随机性)

@Test

public void test() {

userList.stream().filter(u -> u.getBalance() < 3000)

.findAny().ifPresent(System.out::println);

}


count 返回流中元素的总个数

@Test

public void test() {

long count = userList.stream().filter(u -> u.getBalance() < 3000)

.count();

System.out.println("count = " + count);

}


max 返回流中的最大值

@Test

public void test() {

userList.stream()

.max((u, u2) -> u.getId().compareTo(u2.getId()))

.ifPresent(System.out::println);

}


min 返回流中的最小值

@Test

public void test() {

userList.stream().filter(u -> u.getBalance() < 3000)

.min((u, u2) -> u.getId().compareTo(u2.getId()))

.ifPresent(System.out::println);

}


forEach

@Test

public void test() {

userList.stream().forEach(System.out::println);


上下代码,效果等价============================");


userList.forEach(System.out::println);

}


终止操作:归约与收集

reduce 将流中元素反复结合起来,得到一个值

@Test

public void test() {

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

Integer sum = list.stream().reduce(0, (a, b) -> a + b);

System.out.println("sum = " + sum);

}


举一反三

@Test

public void test() {

Double sum = userList.stream()

.map(User::getBalance)

.reduce(0.0, Double::sum);

System.out.println("sum = " + sum);

}


注意,如果调用reduce的时候,没有传入第一个参数,则计算的值有可能为空,所以在没有为reduce传入第一个参数的情况下,reduce返回的是Optional:

@Test

public void test() {

userList.stream()

.map(User::getBalance)

.reduce(Double::sum)

.ifPresent(System.out::println);

}


collect 收集:将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中的元素做汇总。

收集所有用户的名字到一个Set集合中:

@Test

public void test() {

Set<String> set = userList.stream()

等价于 u -> u.getName();

.collect(Collectors.toSet());

等价于 name -> System.out.println(name)

}


收集所有用户的名字到一个List集合中:

@Test

public void test() {

List<String> list = userList.stream()

等价于 u -> u.getName();

.collect(Collectors.toList());

等价于 name -> System.out.println(name)

}


将结果存入Map中:

@Testpublic void test() {

Map<Integer, User> map = userList.stream().filter(u -> u.getBalance() > 3000).collect(Collectors.toMap(u -> u.getId(), u -> u));


map.forEach((k,v) -> {

System.out.println(k + " --- " + v);

});

}


如果我们想把收集到的数据存入一个特殊类型的集合中呢?比如存入到一个LinkedHashSet。这时候可以使用Collectors.toCollection(Suplier):

@Test

public void test() {

LinkedHashSet<String> set = userList.stream()

.map(User::getName)

.collect(Collectors.toCollection(() -> new LinkedHashSet<>()));

set.forEach(System.out::println);

}


计算收集到的数据的总数:

@Test

public void test() {

Long count = userList.stream()

.map(User::getName)

.collect(Collectors.counting());

System.out.println("count = " + count);

}


计算收集到的数字类型数据的平均值

@Test

public void test() {

Double avg = userList.stream()

// .map(User::getBalance)

注意下面这一行的averagingDouble方法中的参数:"User::getBalance"代替了上一行的 map(User::getBalance)

.collect(Collectors.averagingDouble(User::getBalance));

System.out.println("avg = " + avg);

}


计算收集到的数字类型数据的总和

@Test

public void test() {

Double sum = userList.stream()

.collect(Collectors.summingDouble(User::getBalance));

System.out.println("sum = " + sum);

}


计算收集到的数据的最大值

@Test

public void test() {

Optional<User> max = userList.stream()

.collect(Collectors.maxBy((u, u2) -> u.getName().compareTo(u2.getName())));


max.ifPresent(System.out::println);

}


计算收集到的数据的最小值

@Test

public void test() {

Optional<User> min = userList.stream()

.collect(Collectors.minBy((u, u2) -> u.getName().compareTo(u2.getName())));


min.ifPresent(System.out::println);

}


分组

先收集用户的名字,再按照用户名字的长度分组,对所有用户名字进行分组:

@Test

public void test() {

Map<Integer, List<String>> map = userList.stream()

.map(User::getName)

.collect(Collectors.groupingBy(name -> name.length()));


map.forEach((k, v) -> System.out.println(k + ": " + v));

}


4. Stream_System_02




直接按照用户名字的长度,对用户对象本身进行分组(而不是对用户的名字分组)

@Test

public void test() {

Map<Integer, List<User>> map = userList.stream()

.collect(Collectors.groupingBy(u -> u.getName().length()));


map.forEach((k,v) -> System.out.println(k + ": " + v));


}


4. Stream_Test_03





多级分组,与SQL中,按多列分组的情况一样:

@Test

public void test() {

Map<Integer, Map<String, List<User>>> map = userList.stream()

.collect(Collectors.groupingBy(u -> u.getName().length(), Collectors.groupingBy(u -> {

Double balance = u.getBalance();

if (balance < 1000) {

潘";

} else if (balance < 3000) {

温饱";

} else if (balance < 6000) {

小康";

} else {

土豪";

}

})));


map.forEach((k, v) -> System.out.println(k + ": " + v));

}


4. Stream_System_04



按照用户的余额分组,余额大于2500的是一组,小于等于2500的是另一组:

@Test

public void test() {

Map<Boolean, List<User>> map = userList.stream()

.collect(Collectors.groupingBy(u -> u.getBalance() > 2500));


map.forEach((k, v) -> System.out.println(k + ": " + v));

}


当按照true和false来进行分组的时候,就可以直接使用分区完成,所以可以改写上面代码如下:

@Test

public void test() {

Map<Boolean, List<User>> map = userList.stream()

.collect(Collectors.partitioningBy(u -> u.getBalance() <= 2500));


map.forEach((k,v) -> System.out.println(k + ": " + v));

}


一次性计算多个聚合值:

@Test

public void test() {

DoubleSummaryStatistics statistics = userList.stream()

.collect(Collectors.summarizingDouble(User::getBalance));


System.out.println(statistics.getMax());

System.out.println(statistics.getMin());

System.out.println(statistics.getCount());

System.out.println(statistics.getAverage());

System.out.println(statistics.getSum());

}


连接

将所有用户的名字用"---"拼接起来:

@Test

public void test() {

String str = userList.stream()

.map(User::getName)

.collect(Collectors.joining("---"));


System.out.println(str);

}


4. Stream_System_05



@Test

public void test() {

String str = userList.stream()

.map(User::getName)

.collect(Collectors.joining("," , "(" , ")"));


System.out.println(str);

}


4. Stream_List_06



练习:

1. 给定一个数字列表,返回一个由每个数字的平方构成的List

@Test

public void test() {

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5)

.stream()

.map(e -> e * e)

.collect(Collectors.toList());


list.forEach(System.out::println);

}


2.给定一个User列表, 统计其中有多少个User,必须使用map和reduce来实现

@Test

public void test() {

userList.stream()

.map(u -> 1)

等价于 (a, b) -> a + b

.ifPresent(System.out::println);

}



2. 并行流与顺序流


2.1 Stream串行流和并行流切换

之前讲解的Stream都是顺序流,也就是单线程的。Stream也可以切换为并行流,并行流是一个把要处理的内容分成多个块,且用不同的线程分别处理每个块的流。通过Stream API中的parallel()与sequential()在并行流与顺序流之间进行切换。


@Test

public void test() {

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);


List<Integer> resultList = list.stream()

.parallel()

.map(x -> x * 10)

.collect(Collectors.toList());


resultList.forEach(System.out::println);

}


在并行的情况下,findAny的结果就具有随机性了

@Testpublic void test() {

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);


Optional<Integer> any = list.stream()

.parallel()

.filter(n -> n > 1)

.findAny();


System.out.println(any.isPresent() ? any.get() : null);

}


2.2 Fork/Join框架(了解)

fork/join框架是ExecutorService接口的一个实现,可以帮助开发人员充分利用多核处理器的优势,编写出并行执行的程序,提高应用程序的性能;设计的目的是为了处理那些可以被递归拆分的任务。


4. Stream_Test_07




Fork/Join 框架 与 线程池的区别

1. .采用 “工作窃取” 模式 (work-stealing)。当执行新的任务时它可以将其拆分成更小的任务执行,并将小任务加到线程队列中,当没有任务执行时,再从一个随机线程的队列中偷一个并把它放在自己的队列中


2. 相对于一般的线程池实现 ,fork/join 框架的优势体现在对其中包含的任务的处理方式上,在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join 框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题(窃取过来)来执行,这种方式减少了线程的等待时间,提高了性能!


3. Fork/Join执行大任务时,是阻塞的。


需求:计算1加到1_000_000_000的值


Fork/Join 步骤一:

import java.util.concurrent.ForkJoinPool;

import java.util.concurrent.RecursiveTask;


class MyForkJoin extends RecursiveTask<Long> {

@Override

protected Long compute() {

System.out.println("MyForkJoin.compute");

return 5L;

}

}


public class AppTest {

public static void main(String[] args) {

ForkJoinPool pool = new ForkJoinPool();

MyForkJoin mfj = new MyForkJoin();


System.out.println("A");

Long result = pool.invoke(mfj);

System.out.println("B");


System.out.println("result = " + result);

}

}


Fork/Join 步骤二:

import java.time.Duration;

import java.time.Instant;

import java.util.concurrent.ForkJoinPool;

import java.util.concurrent.RecursiveTask;


class MyForkJoin extends RecursiveTask<Long> {


private Long begin;

private Long end;



public MyForkJoin(Long begin, Long end) {

this.begin = begin;

this.end = end;

}


@Override

protected Long compute() {

Long sum = 0L;

for (Long i = begin; i <= end; i++) {

sum += i;

}

return sum;

}

}


public class AppTest {

public static void main(String[] args) {

ForkJoinPool pool = new ForkJoinPool();

MyForkJoin mfj = new MyForkJoin(1L, 1000000000L);


Instant start = Instant.now();

Long result = pool.invoke(mfj);

long elapse = Duration.between(start, Instant.now()).toMillis();


System.out.println("Task took " + elapse + " ms");

}

}


Fork/Join 步骤三

import java.time.Duration;

import java.time.Instant;

import java.util.concurrent.ForkJoinPool;

import java.util.concurrent.RecursiveTask;


class MyForkJoin extends RecursiveTask<Long> {


private Long begin;

private Long end;


private static final Long THURSHOLD = 10000000L;



public MyForkJoin(Long begin, Long end) {

this.begin = begin;

this.end = end;

}


@Override

protected Long compute() {


Long length = end - begin;



递推转折点

Long sum = 0L;

for (Long i = begin; i <= end; i++) {

sum += i;

}

return sum;

继续递推

Long middle = (begin + end) / 2;

MyForkJoin left = new MyForkJoin(begin, middle);

拆分出小任务,并把小任务压入某个线程的任务队列


MyForkJoin right = new MyForkJoin(middle + 1, end);

拆分出小任务,并把小任务压入某个线程的任务队列


return left.join() + right.join();

}

}

}


public class AppTest {

public static void main(String[] args) {

ForkJoinPool pool = new ForkJoinPool();

MyForkJoin mfj = new MyForkJoin(1L, 1000000000L);


Instant start = Instant.now();

Long result = pool.invoke(mfj);

long elapse = Duration.between(start, Instant.now()).toMillis();


System.out.println("Task took " + elapse + " ms");

}

}