Stream入门
-
Stream 不是集合 , 也不是数据结构 , 不可以保存数据
-
Stream 有点类似于高级 的 Iterator , 可以用于算法和计算
-
不同于迭代器 , Stream 可以并行化操作 , 数据被分为很多段 , 在不同的线程中进行处理
-
数据源、零个或多个中间操作 ( intermediate ) 以及零个或一个终端操作 (terminal )
-
所有中间操作都是惰性的 , 在管道开始工作之前,任何操作都不会产生任何效果
-
终端操作有点像水龙头 , 开启了水龙头后 , 水才会流动 , 中间操作才会执行
一、Strean如何创建?[Strean数据源]
数据源是数组
stream = Stream.of("1", "2", "3");
//等同于
String[] arr = new String[]{"1", "2", "3"};
Stream<String> stream = Arrays.stream(arr);
数据源是集合
//集合 List ,ap set 都可以
List<String> list = new ArrayList<>();
list.add("我");
list.add("是");
list.add("集合");
Stream<String> stream = list.stream(); //顺序流
ParallelStream<String> parallelStream =list.parallelStream(); //并行流
通过parallelStream()
可以创建并行流,默认线程池不是我们常用的ThreadPoolTaskExecutor
而是ForkJoinPool
二、Stream 流是如何工作的?[中间操作和终端操作]
流表示包含着一系列元素的集合,我们可以对其做不同类型的操作,用来对这些元素执行计算。听上去可能有点拗口,让我们用代码说话:
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList
.stream() // 创建流
.filter(s -> s.startsWith("c")) // 执行过滤,过滤出以 c 为前缀的字符串
.map(String::toUpperCase) // 转换成大写
.sorted() // 排序
.forEach(System.out::println); // for 循环打印
// C1
// C2
我们可以对流进行中间操作或者终端操作。
什么是中间操作?什么又是终端操作?
Stream中间操作,终端操作
- ①:中间操作会再次返回一个流,所以,我们可以链接多个中间操作,注意这里是不用加分号的。上图中的
filter
过滤,map
对象转换,sorted
排序,就属于中间操作。 - ②:终端操作是对流操作的一个结束动作,一般返回
void
或者一个非流的结果。上图中的forEach
循环 就是一个终止操作。
看完上面的操作,感觉是不是很像一个流水线式操作呢。
实际上,大部分流操作都支持 lambda 表达式作为参数,正确理解,应该说是接受一个函数式接口的实现作为参数。
三、不同类型的 Stream 流
1.按照执行调度区分
1.1串行流
1.2并行流
Stream中并行流使用的线程池是ForkJoinPool
@Test
public void case6(){
List<Person> persons = Arrays.asList(
new Person("Max", 18),
new Person("Max", 18),
new Person("Peter", 23),
new Person("Pamela", 23),
new Person("David", 12),
new Person("David", 12),
new Person("David", 12));
//直接创建并行流 parallelStream()
persons.parallelStream().peek((person)-> System.out.println(person.getName()+":"+Thread.currentThread().getName())).forEach(System.out::println);
//先用串行再转并行 parallel
persons.stream().distinct().parallel().peek((person)-> System.out.println(person.getName()+":"+Thread.currentThread().getName())).forEach(System.out::println);
}
2.按照数据类型区分
2.1基本数据类型流(int,long,double)
- IntStream
- LongStream
- DoubleStream
2.2对象流(Object)
- Stream
四、中间操作
//先定义一个操作对象下面用
List<Person> persons = Arrays.asList(
new Person("Max", 18),
new Person("Max", 18),
new Person("Peter", 23),
new Person("Pamela", 23),
new Person("David", 12));
1.map
遍历元素进行编辑,有返回值,可以类型转换
@Test
public void testMap() {
Stream.of("apple", "banana", "orange", "watermelon", "grape")
.map(e -> {
e = e.substring(1, 1);
return e.length();
}) //转成单词的长度 int
.forEach(System.out::println); //输出
}
2.mapToInt
对象流转基本数据流,当然这些map都能做到,mapToLong、mapToDouble 与mapToInt 类似
@Test
public void testMapToInt(){
Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.mapToInt(String::length) //转成int
.forEach(System.out::println);
}
3.peek
遍历元素进行编辑,打印信息,无返回值,不可以类型转换(其实就是提供给我们来观察流的元素的)
@Test
public void tesPeek() {
Stream.of( new StringBuilder("apple") ,new StringBuilder("banana"),new StringBuilder("orange"),new StringBuilder("watermelon"),new StringBuilder("grape"))
.peek(s-> s.append("aa")) //拼接上aa
.map(StringBuilder::toString)
.forEach(System.out::println); //输出
}
4.flatMap
flatMap 作用就是将元素拍平拍扁 ,将拍扁的元素重新组成Stream,并将这些Stream 串行合并成一条Stream
//flatMap()参数,函数式接口的返回值也是一个Stream流
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
//map()的参数,函数式接口的返回值是Object
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
@Test
public void testFlatMap(){
Stream.of("a-b-c-d","e-f-i-g-h")
.flatMap(e->Stream.of(e.split("-")))
.forEach(System.out::println);
}
5.distinct
去重,将根据equals 方法进行判断,如果要对自己自定义的bean 去重,则需要 重写equals方法,但是这不是唯一的方法,还可以自定义过滤规则
@Test
public void testDistinct() {
//Persons对象去重,默认根据equals
persons.stream()
.distinct()
.forEach(System.out::println);
//Integer去重
IntStream.of(1, 2, 2, 3)
.distinct()
.forEach(System.out::println);
}
6.Limit
限制元素的个数
@Test
public void testLimit(){
//Persons限制元素个数
persons.stream()
.limit(3)
.forEach(System.out::println);
//Integer限制元素个数
Stream.of(1,2,3,4,5,6)
.limit(3) //限制三个
.forEach(System.out::println); //将输出 前三个 1,2,3
}
7.skip
跳过前n个元素
@Test
public void testSkip() {
//Persons跳过前n个元素
persons.stream()
.skip(3)
.forEach(System.out::println);
//Integer跳过前n个元素
Stream.of(1,2,3,4,5,6)
.skip(3) //限制三个
.forEach(System.out::println); //将输出 前三个 1,2,3
}
8.sorted
排序比较器 底层依赖Comparable 实现,也可以提供自定义比较器
@Test
public void testSorted() {
///Persons排序,注意这里自定义排序规则
persons.stream()
.sorted(Comparator.comparingInt(Person::getAge).thenComparing(Person::getName))
.forEach(System.out::println);
//Integer排序
Stream.of(1,2,4,3,5,6)
.sorted()
.forEach(System.out::println);
}
#IntStream(特有的方法)
- IntStream
- LongStream
- DoubleStream
1.mapToObj
基本数据流转对象流
@Test
public void testMapObject() {
// 这里转成string对象,并给String开头+a
IntStream.of(1, 2, 3)
.mapToObj(String::valueOf)
.map(s -> "a" + s)
.forEach(System.out::println);
}
2.boxed
自动装箱,将基本类型int转成对象类型Integer
@Test
public void testBoxed() {
// 将基本类型转成对象类型
IntStream.range(0, 10).boxed().forEach(System.out::println);
//相当于
IntStream.range(0, 10).mapToObj(Integer::valueOf).forEach(System.out::println);
}
五、终端操作
//还是先整个集合
List<Person> persons = Arrays.asList(
new Person("Max", 18),
new Person("Max", 18),
new Person("Peter", 23),
new Person("Pamela", 23),
new Person("David", 12));
1.forEach
forEach不仅是Stream的终端操作,而且还是集合的语法糖
@Test
public void testForEach() {
persons.forEach(System.out::println);
}
2.count
求集合长度
@Test
public void testCount() {
long count = persons.stream().count();
System.out.println(count);
}
3.findFirst
获取流中第一个元素
@Test
public void testFindFirst() {
Optional<Person> optional = persons.stream().findFirst();
Person person = optional.orElse(null);
System.out.println(person);
}
4.findAny
获取流中任意一个元素
@Test
public void testFindAny() {
Optional<Person> optional = persons.stream().findAny();
Person person = optional.orElse(null);
System.out.println(person);
}
5.max
求最大值 ,根据规则排序取最后那个
@Test
public void testMax() {
Optional<Person> max1 = persons.stream().max((p1, p2) -> {
return p1.getAge().compareTo(p2.getAge());
});
Person person1 = max1.orElse(null);
System.out.println(person1);
//简化
Optional<Person> max2 = persons.stream().max(Comparator.comparing(Person::getAge));
Person person2 = max2.orElse(null);
System.out.println(person2);
}
6.min
求最小值,根据规则排序取第一个
@Test
public void testMin() {
Optional<Person> min1 = persons.stream().min((p1, p2) -> {
return p2.getAge().compareTo(p1.getAge());
});
Person person1 = min1.orElse(null);
System.out.println(person1);
//简化
Optional<Person> min2 = persons.stream().min(Comparator.comparing(Person::getAge));
Person person2 = min1.orElse(null);
System.out.println(person2);
}
7.toArray
转数组
@Test
public void testToArray() {
Object[] objects = persons.stream().toArray();
Arrays.stream(objects).forEach(System.out::println);
}
8.reduce
归并
对流中的数据按照你指定的计算方式计算出一个结果(缩减操作)
reduce的作用是把Stream中的元素结合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素在基础值上计算,计算结果在后面的元素计算
//求最大年龄的值
//可以使用max(其实基于reduce)或者reduce
//reduce求和,求差集,求什么都有,其实就是一组数据的连续计算
@Test
public void case5(){
List<Person> persons = Arrays.asList(
new Person("Max", 18),
new Person("Max", 18),
new Person("Peter", 23),
new Person("Pamela", 23),
new Person("David", 12),
new Person("David", 12),
new Person("David", 12));
//求最小年龄
int minAge = persons.stream().mapToInt(Person::getAge).reduce(Integer.MAX_VALUE,new IntBinaryOperator(){
@Override
public int applyAsInt(int left, int right) {
return left > right?right:left;
}
});
System.out.println(minAge);
//求最大年龄,简写
int maxAge = persons.stream().mapToInt(Person::getAge).reduce(Integer.MIN_VALUE, Math::max);
System.out.println(maxAge);
}
9.collect(最重要)
这里只介绍下简单的收集功能,也是最常用的,参考了这篇文章https://www.jianshu.com/p/6ee7e4cd5314
1.toList和toSet
@Test
public void testToListSet(){
List<Person> persons = Arrays.asList(
new Person("Max", 1),
new Person("Max", 2),
new Person("Peter", 3),
new Person("Pamela", 4),
new Person("David", 5));
//toList
List<Person> list1 = persons.stream().distinct().collect(Collectors.toList());
list1.forEach(System.out::println);
//toSet
Set<Person> set = persons.stream().collect(Collectors.toSet());
set.forEach(System.out::println);
}
2.toMap
@Test
public void testToMap(){
List<Person> persons = Arrays.asList(
new Person("Max", 1),
new Person("Max", 2),
new Person("Peter", 3),
new Person("Pamela", 4),
new Person("David", 5));
//key value 可以是对象本身(用Function.identity()表示)也可以是属性
Map<Person, Person> collect1 = persons.stream().collect(Collectors.toMap(Function.identity(), Function.identity()));
collect1.forEach((k,v)->{
System.out.println(k+":"+v);
});
Map<Integer, String> collect2 = persons.stream().collect(Collectors.toMap(Person::getAge, Function.identity()));
collect2.forEach((k,v)->{
System.out.println(k+":"+v);
});
}
3.groupingBy
//有三个重载方法
//groupingBy(Function)
//groupingBy(Function , Collector)
//groupingBy(Function, Supplier, Collector)
//Function 自定义分组规则
//Supplier 自定义Map的类型,默认HashMap::new
//Collector 自定义每组的格式,默认Collectors.toList()
@Test
public void testGroup(){
List<Person> persons = Arrays.asList(
new Person("Max", 1),
new Person("Min", 1),
new Person("Peter", 2),
new Person("Pamela", 3),
new Person("David", 4));
//1.按年龄分组
System.out.println("按年龄分组");
ConcurrentHashMap<Integer, Set<Person>> map1 = persons.stream().collect(Collectors.groupingBy(Person::getAge, ConcurrentHashMap::new, Collectors.toSet()));
map1.forEach((k,v)->{
System.out.println(k+":"+v);
});
//2.自定义分组规则
System.out.println("自定义分组规则");
Map<String, List<Person>> map2 = persons.stream().collect(Collectors.groupingBy((person -> {
if (person.getAge().equals(1)){
return "等于1";
}
if (person.getAge().equals(2) || person.getAge().equals(3)){
return "等于2或3";
}
if (person.getAge().equals(4)){
return "等于4";
}
return null;
} )));
map2.forEach((k,v)->{
System.out.println(k+":"+v);
});
//3.partitioningBy 分区(只能分两组)
//判断年龄是否为1
Map<Boolean, List<Person>> map3 = persons.stream().collect(Collectors.partitioningBy(person -> person.getAge().equals(1)));
map3.forEach((k,v)->{
System.out.println(k+":"+v);
});
}
4.Collectors.joining()
//拼接字符串,有三个重载方法
//Collectors.joining()
//Collectors.joining(CharSequence delimiter)
//Collectors.joining(CharSequence delimiter,CharSequence prefix,CharSequence suffix)
//delimiter是分隔符号,默认为""
//prefix是前缀,默认为""
//suffix是后缀,默认为""
@Test
public void testJoining(){
List<Person> persons = Arrays.asList(
new Person("Max", 1),
new Person("Peter", 2),
new Person("Pamela", 3),
new Person("David", 4));
String s1 = persons.stream().map(Person::getName).collect(Collectors.joining());
String s2 = persons.stream().map(Person::getName).collect(Collectors.joining("、"));
String s3 = persons.stream().map(Person::getName).collect(Collectors.joining("、","name:",";"));
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
#IntStream(特有的方法)
- IntStream
- LongStream
- DoubleStream
1.Reduce
归并操作
//reduce 有两个重载方法
//1.参数为起始值和运算规则,返回值是int 有起始值,有运算规则,两个参数,此时返回的类型和起始值类型一致。
T reduce(T identity, BinaryOperator<T> accumulator)
//2.参数为运算规则,返回值是Optional...
OptionalInt reduce(IntBinaryOperator op);
@Test
public void testReduce() {
//有起始值,有运算规则
int sum = IntStream.range(0, 10).reduce(0, (v1, v2) -> v1 - v2);
System.out.println(sum);
// 规约操作返回 optionalInt,不一定有值
OptionalInt reduce = IntStream.of().reduce((v1, v2) -> v1/v2);
reduce.ifPresent(System.out::println);
}
2.sum
求和
@Test
public void testSum() {
int sum = IntStream.rangeClosed(0, 10).sum();
System.out.println(sum);
}
3.average
求平均值
@Test
public void testAverage() {
OptionalDouble optionalDouble = IntStream.of(-2, 2, -9, 10, 9).average();
double average = optionalDouble.getAsDouble();
System.out.println(average);
}
六、注意事项
- 惰性求值(如果没有终结操作,只有中间操作是不会执行的)
- 流是一次性的(一旦一个流经过了一个终结操作,这个流就不能再被使用)
- 对流的操作不会影响原数据
无状态:指元素的处理不受之前元素的影响;
有状态:指该操作只有拿到所有元素之后才能继续下去。
非短路操作:指必须处理所有元素才能得到最终结果;
短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。
七、Optional
Optional是为了解决空指针的校验的臃肿
1.创建Optional
Optional.ofNullable(author)
推荐使用
Optional.of(author)
如果author==null 会报空指针
public class OptionalDemo {
public static void main(String[] args) {
//当不知道author是否为空,为了防止空指针异常
Author author = OptionalDemo.getAuthor();
//1.以前我们这样做非空
if (author != null) {
System.out.println(author.getAge());
}
//2.jdk8后可以将这个对象包装为Optional<Author>对象
Optional<Author> optionalAuthor = Optional.ofNullable(author);
System.out.println("optionalAuthor:" + optionalAuthor);
//然后进行判断
optionalAuthor.ifPresent(author1 -> System.out.println(author1.getName()));
//3.在方法中就包装好Optional
Optional<Author> optionalAuthor2 = OptionalDemo.getOptionalAuthor();
System.out.println("optionalAuthor2:" + optionalAuthor2);
optionalAuthor2.ifPresent(author1 -> System.out.println(author1.getName()));
}
public static Author getAuthor() {
Author author = new Author();
author.setAge(18);
author.setName("赵云");
// return null;
return author;
}
public static Optional<Author> getOptionalAuthor() {
Author author = new Author();
author.setAge(20);
author.setName("张飞");
author = null;
//return Optional.of(author); //传入对象不可为空
return Optional.ofNullable(author);
}
}
2.安全的消费
ifPresent()
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
3.获取对象
3.1.get
(不推荐使用,对象为null报错NoSuchElementException)
Author author1 = optionalAuthor2.get();
3.2.orElseGet
(对象如果为null,自定义返回对象)
optionalAuthor2.orElseGet(new Supplier<Author>() {
@Override
public Author get() {
Author author2 = new Author();
author2.setAge(18);
author2.setName("曹操");
return author2;
}
});
3.3.orElseThrow
(对象如果为null,抛出自已定义的异常)
optionalAuthor2.orElseThrow(new Supplier() {
@Override
public Exception get() {
return new Exception("我靠");
}
});
//简化
optionalAuthor2.orElseThrow((Supplier<Throwable>) () -> new RuntimeException("我靠"));
4.过滤
filter
判断对象是否符合标准,不符合的会被当作对象为null
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
5.判断
ifPresent
判断对象是否为空,为空返回false,不为空返回true
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
6.数据转换
map
对数据进行类型转换,和Stream中的一样
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
//返回值 也是Optional对象
Optional<String> optionalName = optionalAuthor2.map(Author::getName);