Stream是Java8引入的一个重度使用lambda表达式的API。
Stream可以用流的方式处理数据集合,在Java8之前,我们处理这些集合,是需要迭代器的,比如iterator,这是外部迭代;而Stream是内部迭代,我们不用关心集合内部元素是如何迭代的,计算机会自动帮我们选择最适合的实现方式。
如何创建一个流
- 最常见的,有一个集合对象
List<String> strs = Arrays.asList("Java 8 ", "Lambdas ", "In ", "Action");
,直接调用strs.stream()
就得到一个Stream<String>
的流。
如果想使用并行流增加性能,请使用strs.parallelStream()
,或strs.stream().parallel()
。 - 由值创建:
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
- 由数组创建:
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
- 由文件创建:
// 统计文本文件中有多少个不同的单词
long uniqueWords = 0;
try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
} catch (IOException e) {
}
- 由函数生成流:
Stream.iterate()
和Stream.generate()
可以生产无限流,即元素有无穷多个。一般来说,应该使用limit(n)来对这种流加以限制,以避免产生无穷多个元素。
public void iterator() {
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
}
public void generate() {
Stream.generate(Math::random).limit(5).forEach(System.out::println);
}
Stream常用方法
Stream API 支持两种类型的操作:中间操作(如filter或map)和终端操作(如count、findFirst、forEach和reduce)。
我用一个筛选菜单的需求作为示例。
准备工作
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
@Override
public String toString() {
return name;
}
}
public enum Type {MEAT, FISH, OTHER}
public List<Dish> init() {
return Arrays.asList(
new Dish("pork", false, 800, Type.MEAT),
new Dish("beef", false, 700, Type.MEAT),
new Dish("chicken", false, 400, Type.MEAT),
new Dish("french fries", true, 530, Type.OTHER),
new Dish("rice", true, 350, Type.OTHER),
new Dish("season fruit", true, 120, Type.OTHER),
new Dish("pizza", true, 550, Type.OTHER),
new Dish("prawns", false, 300, Type.FISH),
new Dish("salmon", false, 450, Type.FISH));
}
过滤筛选
谓词:返回boolean的函数
- filter():接受一个谓词,返回符合条件的元素集合
@Test
public void filter() {
List<Dish> menu = init();
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(Collectors.toList());
Assert.assertEquals(4, vegetarianMenu.size());
}
- distinct():返回集合中各异的元素集合(去重)
@Test
public void distinct() {
List<Integer> numbers = Arrays.asList(5, 1, 2, 1, 3, 3, 2, 4);
numbers.stream().distinct().forEach(System.out::println);
}
- limit():截取流中指定数量的元素,返回一个不超过给定长度的流。如果流是有序的,则最多会返回前n个元素。
@Test
public void limit() {
List<Dish> menu = init();
menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.forEach(System.out::println);
}
- skip():跳过指定数量元素,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。
@Test
public void skip() {
List<Dish> menu = init();
menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.skip(2)
.forEach(System.out::println);
}
映射
- map():接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。
@Test
public void map() {
List<Dish> menu = init();
List<String> dishNames = menu.stream().map(m -> m.getName()).collect(Collectors.toList());
}
- flatMap():一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流,即扁平化为一个流。
@Test
public void flatMap() {
String[] arrayOfWords = {"Goodbye", "World"};
List<String> words = Arrays.asList(arrayOfWords);
words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.forEach(System.out::println);
}
上面例子中,split()
得到的是String[] 而不是String,因此各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,变为一个流。
匹配
匹配比较简单,返回一个boolean
- anyMatch():至少匹配一个
- allMatch():全部匹配
- noneMatch():全部不匹配,和allMatch相反
@Test
public void anyMatch() {
List<Dish> menu = init();
Assert.assertEquals(true, menu.stream().anyMatch(Dish::isVegetarian));
}
@Test
public void allMatch() {
List<Dish> menu = init();
Assert.assertEquals(true, menu.stream().allMatch(d -> d.getCalories() < 1000));
}
@Test
public void noneMatch() {
List<Dish> menu = init();
Assert.assertEquals(true, menu.stream().noneMatch(d -> d.getCalories() >= 1000));
}
查找
查找有2个方法:findFirst()
和findAny()
,返回一个Optional<T>
集合。
如果你不关心返回的元素是哪个,请使用findAny()
,因为它在使用并行流时限制较少。
@Test
public void findFirst() {
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree =
someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst(); // 9
System.out.println(firstSquareDivisibleByThree.get());
}
@Test
public void findAny() {
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree =
someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findAny(); // 9
System.out.println(firstSquareDivisibleByThree.get());
}
归约
归约在汇总结合内所有数据的时候使用。比如求 max,min,sum。
@Test
public void reduce() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, Integer::sum);
System.out.println(sum);
}
原始类型流特化
流在内部迭代的过程中,对基本类型会自动装箱和拆箱。为了避免不需要的装箱拆箱,Java8提供了IntStream
、DoubleStream
和LongStream
- 普通流转特化流:mapToInt(), mapToLong(), mapToDouble()
- 特化流转普通流:boxed()
public void boxedStream() {
List<Dish> menu = init();
// 特化
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
// 转回普通Stream
Stream<Integer> stream = intStream.boxed();
}
Java 8引入了两个可以用于IntStream和LongStream的静态方法,用于生成给定范围的数字流:
- range(min, max):随机生成的数字不包含max,即(min, max)
- rangeClosed(min, max):随机生成的数字包含max,即(min, max]