流是“从支持数据处理操作的源生成的一系列元素”。流中的元素是按需计算的。
集合的弊端:
1. 无法像SQL语句那样筛选数据,需要利用迭代器或者累加器筛选。
2. 并行处理集合比较繁琐,易出错。
流的优势:
3. 以声明性方式处理数据集合。
4. 透明地进行并行处理,无需写多线程代码。
一、流与集合
代码例子:
//传统集合处理方式
List<Dish> lowCaloricDishes = new ArrayList<>();
//筛选低卡路
for(Dish d : menu){
if(d.getCalories() < 400){
lowCaloricDishes.add(d);
}
}
//按卡路里排序
Collectioins.sort(lowCaloricDishes, new Comparator<Dish>(){
public int compare(Dish d1, Dish d2){
return Integer.compare(d1.getCalories(), d2.getCalories());
}
});
//获取名字集合
List<String> lowCaloricDishesName = new ArrayList<>();
for(Dish d : lowCaloricDishes){
lowCaloricDishesName.add(d.getName());
}
//流处理方式
List<String> lowCaloricDishesName = menu.stream()
.filter(d -> d.getCalories() < 400)
.sort(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());
//其中stream可以换成parallelStream来使用并行处理
集合的处理方式一般为外部迭代,而流处理为内部迭代。
二、流操作
流的操作有两类:中间操作和中端操作。
中间操作:输入类型为Stream<T>,输出类型为Stream<T>。
终端操作:输入类型为Stream<T>,输出类型为非Stream值。
三、使用流
- 筛选和切片:filter、distinct、limit、skip
- 映射:map、flatMap
- 查找和匹配:allMatch、anyMatch、noneMatch、findFirst、findAny
- 归约:reduce
1. 累加:stream().reduce(0, (a, b) -> a + b)
2. 累乘:stream().reduce(1, (a, b) -> a * b)
3. 最大值:stream().reduce(Integer::max)
4. 最小值:stream().reduce(Integer::min)
5. Lambda替换累加:stream().reduce(0, Integer::sum)
6. Lambda替换最小值:stream.reduce((x, y) -> x < y ? x : y)
经典案例(交易员和交易问题):
(1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
(2) 交易员都在哪些不同的城市工作过?
(3) 查找所有来自于剑桥的交易员,并按姓名排序。
(4) 返回所有交易员的姓名字符串,按字母顺序排序。
(5) 有没有交易员是在米兰工作的?
(6) 打印生活在剑桥的交易员的所有交易额。
(7) 所有交易中,最高的交易额是多少?
(8) 找到交易额最小的交易。
//Trader类
public class Trader {
private final String name;
private final String city;
public Trader(String name, String city) {
this.name = name;
this.city = city;
}
public String getName() {
return name;
}
public String getCity() {
return city;
}
@Override
public String toString() {
return "Trader{" +
"name='" + name + " in " +
"city='" + city +
'}';
}
}
//Transaction类
public class Transaction {
private final Trader trader;
private final int year;
private final int value;
public Transaction(Trader trader, int year, int value) {
this.trader = trader;
this.year = year;
this.value = value;
}
public Trader getTrader() {
return trader;
}
public int getYear() {
return year;
}
public int getValue() {
return value;
}
@Override
public String toString() {
return "Transaction{" +
"trader=" + trader +
", year=" + year +
", value=" + value +
'}';
}
}
//数据
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brain = new Trader("Brain", "Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brain, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
答案请参考: Steam流--交易员和交易问题答案
四、数值流
Stream API提供了原始类型流特化以支持处理数值流的方法。
Java8引入三个原始类型特化流接口:IntStream、DoubleStream、LongStream,分别将流中的元素特化为 int、double、long,从而避免装箱的成本。
- 映射到数值流
常用方法有mapToInt、mapToDouble、mapToLong,返回的是特化流而不是Stream<T>。
//下面例子中返回的是IntStream,而不是Stream<Integer>
int calories = menu.stream()
.mapToInt(Dish::getCalories)
.sum();
//sum方法中若流为空,则结果为0。
//IntStream还支持其他方法,如max、min、average等操作。
- 数值流转换为对象流
//将 Stream 转 换为数值流
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
//将数值流转 换为Stream
Stream<Integer> stream = intStream.boxed();
- 默认值OptionalInt
Optional类是一个可以表示值存在或不存在的容器。避免了流为空是,数值为0的情况。
Optional原始类型特化版本:OptionalInt、OptionalDouble、OptionalLong。
// 例如,要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt:
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getCalories)
.max();
// 现在,如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了:
//如果没有最大值的话,显式提供一个默认最大值
int max = maxCalories.orElse(1);
- 数值范围
Java 8引入了两个可以用于IntStream和LongStream的静态方法,range和rangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但 range是不包含结束值的,而rangeClosed则包含结束值。
//表示范围:[1, 100], 一个从1到100的偶数流
IntStream evenNumbers = IntStream.rangeClosed(1, 100)
.filter(n -> n % 2 == 0);
//结果为50,若改为range方法则结果为49。
System.out.println(evenNumbers.count());
五、构建流
-
由值创建流
//Stream.of创建显示流 Stream<String> stream = Stream.of("Java 8", "Lambdas ", "in ", "Action"); //empty得到空流 Stream<String> emptyStream = Stream.empty();
-
由数组创建流
//Arrays.stream从数组创建一个流 int[] numbers = {2, 3, 5, 7, 11, 13}; int sum = Arrays.stream(numbers).sum();
-
由文件生成流
例如统计一个文件中有多少各不相同的词:
-
由函数生成流
Stream API提供了从函数生成流的方式:Stream.iterate和Stream.generate,这两个操作可以创建无限流(即不像从集合中创建固定大小的流)。//依次在每个新产生的值上应用Lambda Stream.iterate(0, n -> n + 2) .limit(10) .forEach(System.out::println); //这个不是依次对每个新生成的值应用函数 Stream.generate(Math::random) .limit(5) .forEach(System.out::println);