文章列表
写在前面:
我是「境里婆娑」。我还是从前那个少年,没有一丝丝改变,时间只不过是考验,种在心中信念丝毫未减,眼前这个少年,还是最初那张脸,面前再多艰险不退却。
写博客的目的就是分享给大家一起学习交流,如果您对 Java感兴趣,可以关注我,我们一起学习。
前言:还没接触过流的同学可以深入研究下此篇文章,让你在写代码过程中可以让你达到事半功倍效果。
一、回忆Lambda表达式
在学习流之前,我们先回忆一下Lambda表达式和函数式接口
使用 案例 | lambda例子 | 对应函数式接口 |
---|---|---|
布尔表达式 | (List< String> list -> list.isEmpty()) | Predicate<(List< String>> |
创建对象 | () -> new Car() | Supplier< Car> |
消费一个对象 | (Car car) -> System.out.println(car.getColor) | Consumer< Car> |
从一个对象提取 | (String s) -> s.length() | Function< String,Integer> |
合并两个值 | (int a,int b) ->a*b | IntBinaryOperator |
比较两个值对象 | (Apple a1,Apple a2) -> a1.getWeigth().compareTo(a2.getWeigth()) | Comparator< Apple>或BiFunction< Apple,Apple,Integer> |
二、什么是流
流是Java 新的API,它允许你以声明性方式处理数据集合。可以把流看作是遍历数据集合的高级迭代器。另外,流还可以透明的并行处理,无需写任何多线程代码。
public class Car {
private String color;
private String brand;
}
例Java7之前,选出小汽车颜色代码如下:
List<Car> cars = Arrays.asList(new Car("bule", "aodi"), new Car("red", "BMW"), new Car("white", "benw"));
List<String> list = new ArrayList<>();
for (Car car : cars) {
list.add(car.getColor());
}
例Java8之后可以如下:
List<String> collect = cars.stream().map(Car::getColor).collect(toList());
总结一下:Java8中的Stream API可以让你写出这样的代码
- 声明性 更简洁,更易懂
- 可复合,更灵活
- 可并行,性能更好
看完上面代码我们还是有疑惑,流到底是什么呢?简短定义就是“从支持数据处理操作的源生成的元素序列”
- 元素序列,就像集合一样,流提供一个接口,可以访问特定元素类型的有序值。集合是数据结构,目的是以特定的时间/空间复杂度存储和访问元素。但流的目的在于表达计算。总的来说集合讲的是数据,而流讲的是计算。
- 源,流会使用一个提供数据的源,如集合、 数组或输入输出资源。
- 数据处理操作,流的数据处理功能支持类似数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort。流操作可以顺序执行,也可以并行执行。
- 流水线,很多流操作本身会返回一个流,这样多个操作就可以连接起来,形成一个大的流水线。
- 内部迭代,与使用迭代器显式迭代集合不同,流的迭代操作是在背后执行的。
1、流只能遍历一次
流还有一个特点就是只能遍历一次。遍历完之后,我们说这个流已经被消费掉了。
2、外部迭代和内部迭代
使用Collection接口需要用户去做迭代比如for-each,这称为外部迭代。相反,Streams库使用内部迭代。
采用内部迭代,项目可以透明地并行处理,或者用优化的顺序进行处理,要是使用 Java 过去的外部迭代方法,这些优化都是很困难的
三、流操作
1、谓词筛选
Streams接口支持filter方法,该操作会接受一个谓词作为参数。
List<Car> aodi = cars.stream().filter(car -> car.getBrand().equals("aodi")).collect(toList());
2、筛选各异元素
流还支持一个叫distinct的方法,它会返回一个元素各异的流。以下代码会筛选出所有基数。
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 != 0)
.distinct()
.forEach(System.out::println);
3、截段流
流支持limit方法,该方法会返回一个不超过给定长度的流。所需要的长度作为参数传递给limit。
List<Car> aodi = cars.stream().filter(car -> car.getBrand().equals("aodi")).limit(3).collect(toList());
请注意limit也可以用在无序流上。比如源是一个Set,这种情况下,limit的结果不会以人物顺序排列。
4、跳过元素
流还支持skip方法,返回一个扔掉了前n个元素的流。如果流中的元素不足n,则返回一个空流。注意。limit和skip是互补的。
List<Car> aodi = cars.stream().filter(car -> car.getBrand().equals("aodi")).skip(3).collect(toList());
5、map和flatMap
map和flatMap如何选用:假如你的集合流中包含子集合,那么使用flatMap可以返回该子集合的集合流
words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
如果想详细了解map和flatMap区别请看这篇文章:java8中map和flatMap区别
6、查找和匹配
另一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。
- anyMatch,流中是否有一个元素能匹配到给定的谓词
if(cars.stream().anyMatch(car -> car.getBrand().equals("aodi"))) {
}
- allMatch,是否匹配所有元素
if(cars.stream().allMatch(car -> car.getBrand().equals("aodi"))) {
}
- noneMatch,确保流中没有任何元素与给定的谓词匹配
if(cars.stream().noneMatch(car -> car.getBrand().equals("aodi"))) {
}
allMatch、anyMatch、noneMatch都用到了我们所谓的短路,这就是大家熟悉java中的&&和||运算符。
- findAny返回当前流中的任意元素。
Optional<Car> car = cars.stream().filter(car -> car.getBrand().equals("aodi")).findAny();
- findFirst()查出第一个元素
Optional<Car> car = cars.stream().filter(car -> car.getBrand().equals("aodi")).findFirst();
findAny()和findFirst()都会返回一个Optional泛型的对象。
Optional 是一个容器类,代表一个值存在或不存在。Optional方法如下:
- isPresent()将在Optional包含值的时候返回true。否则返回false
- ifPresent()会在值存在的时候执行给定代码块。
- get()会在值存在返回值。
- orElse()会在值存在时返回值。否则返回一个默认值。
7、归约reduce
reduce是一个终端操作,比如我们做求和操作。
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
比如求最大值和最小值
Optional<Integer> min = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
至此我们把流的基本操作都介绍完。下面我们总结一下哪些是中间操作哪些是终端操作。
———————————————————————————
如果喜欢这篇文章的话请关注我!