一、筛选和切片
1、用谓词筛选(filter)
谓词:
用来描述或判定客体性质、特征或者客体之间关系的词项。在程序中解释是接收一个参数值,并返回true
或false
。
filter
方法会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。
public class Test05 {
public static void main(String[] args) {
//初始化数据
List<Dish> init = DishService.init();//这个初始化方法在流介绍中
//筛选出所有的素菜
List<Dish> list = init.stream().filter(Dish::isVegetarian).collect(Collectors.toList());
list.forEach(System.out::println);
}
}
/** 运行结果 */
Dish(name=白菜, vegetarian=true, calories=530, type=OTHER)
Dish(name=菠菜, vegetarian=true, calories=350, type=OTHER)
Dish(name=时令水果, vegetarian=true, calories=120, type=OTHER)
Dish(name=黄瓜, vegetarian=true, calories=550, type=OTHER)
2、筛选各异的元素(distinct)
distinct
方法会返回一个元素各异(根据流所生成元素的hashCode
和equals
方法实现)的流。去除重复元素。
public class Test06 {
public static void main(String[] args) {
List<Integer> nums = Arrays.asList(1, 2, 3, 5, 2, 3, 5);
List<Integer> list = nums.stream().distinct().collect(Collectors.toList());
System.out.println(list);
}
}
/** 运行结果 */
[1, 2, 3, 5]
3、截断流(limit)
limit
方法会返回一个不超过给定长度的流。取出前n个元素。
public class Test05 {
public static void main(String[] args) {
//初始化数据
List<Dish> init = DishService.init();
//筛选出所有的素菜,并取出前2个
List<Dish> list = init.stream().filter(Dish::isVegetarian).limit(2).collect(Collectors.toList());
list.forEach(System.out::println);
}
}
/** 运行结果 */
Dish(name=白菜, vegetarian=true, calories=530, type=OTHER)
Dish(name=菠菜, vegetarian=true, calories=350, type=OTHER)
4、跳过元素(skip)
skip
方法会返回一个去掉了前n个元素的流,如果流中元素不足n个,则返回一个空流,与limit互补。
public class Test05 {
public static void main(String[] args) {
//初始化数据
List<Dish> init = DishService.init();
//筛选出所有的素菜,跳过前2个元素
List<Dish> list = init.stream().filter(Dish::isVegetarian).skip(2).collect(Collectors.toList());
list.forEach(System.out::println);
}
}
/** 运行结果 */
Dish(name=时令水果, vegetarian=true, calories=120, type=OTHER)
Dish(name=黄瓜, vegetarian=true, calories=550, type=OTHER)
二、映射
1、对流中每一个元素应用函数(map)
map
方法会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。
public class Test07 {
public static void main(String[] args) {
//初始化数据
List<Dish> init = DishService.init();
//筛选出所有的菜名
List<String> list = init.stream().map(Dish::getName).collect(Collectors.toList());
System.out.println(list);
}
}
/** 运行结果 */
[猪肉, 牛肉, 鸡肉, 白菜, 菠菜, 时令水果, 黄瓜, 对虾, 三文鱼]
2、流的扁平化(flatMap)
flatMap
方法把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
public class Test08 {
public static void main(String[] args) {
//给定单词列表["Hello","World"],返回列表["H","e","l", "o","W","r","d"]
String [] array = {"Hello","World"};
//将数组转为流
Stream<String> world = Arrays.stream(array);
List<String> list = world.map(w -> w.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
System.out.println(list);
}
}
/** 运行结果 */
[H, e, l, o, W, r, d]
使用
flatMap
方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)
时生成的单个流都被合并起来,即扁平化为一个流。
三、查找和匹配
1、至少匹配一个元素(anyMatch)
anyMatch
方法判断流中是否有一个元素能匹配给定的谓词,返回一个boolean
,因此是一个终端操作。
public class Test04 {
public static void main(String[] args) {
List<String> title = Arrays.asList("Java8", "In", "Action");
//判断是否存在长度大于5的数据
boolean b = title.stream().anyMatch(i -> i.length() > 5);
System.out.println(b);
}
}
/** 运行结果 */
true
2、匹配所有元素(allMatch)
allMatch
方法与anyMatch
工作原理类似,但它会看看流中的元素是否都能匹配给定的谓词。
public class Test04 {
public static void main(String[] args) {
List<String> title = Arrays.asList("Java8", "In", "Action");
//判断所有字符长度是否大于5
boolean b = title.stream().allMatch(i -> i.length() > 5);
System.out.println(b);
}
}
/** 运行结果 */
false
3、都不匹配(noneMatch)
noneMatch
方法与allMatch
相对,它可以确保流中没有任何元素与给定的谓词匹配。如果匹配上返回false
。
public class Test04 {
public static void main(String[] args) {
List<String> title = Arrays.asList("Java8", "In", "Action");
boolean b = title.stream().noneMatch(i -> i.length() > 5);
System.out.println(b);
}
}
/** 运行结果 */
false
4、短路求值
有些操作不需要处理整个流就能得到结果。例如,假设你需要对一个用and连起来的大布尔表达式求值。不管表达式有多长,你只需找到一个表达式为false,就可以推断整个表达式将返回false,所以用不着计算整个表达式。这就是短路。
对于流而言,某些操作(例如allMatch、anyMatch、noneMatch、findFirst和findAny) 不用处理整个流就能得到结果。
只要找到一个元素,就可以有结果了
。同样,limit也是一个短路操作:它只需要创建一个给定大小的流,而用不着处理流中所有的元素。在碰到无限大小的流的时候,这种操作就有用了:它们可以把无限流变成有限流。
5、查找元素(findAny)
findAny
方法将返回当前流中任意元素,只要找到就会立即返回。
public class Test01 {
public static void main(String[] args) {
//初始化数据
List<Dish> init = DishService.init();
//找出一道素菜
Optional<Dish> any = init.stream().filter(Dish::isVegetarian).findAny();
System.out.println(any);
}
}
/** 运行结果 */
Optional[Dish(name=菠菜, vegetarian=true, calories=350, type=OTHER)]
在上面代码中,可以看到出现了一个新的类
Optional
,Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。findAny可能什么元素都没找到,这样就不会出现空指针问题了
。Optional类几个方法介绍:
6、查找第一个元素(findFirst )
findFirst
方法返回第一个元素。
public class Test02 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 3, 5, 6, 7, 9, 10);
//找出第一个可以被2整除的数
Optional<Integer> first = list.stream().filter(x -> x % 2 == 0).findFirst();
System.out.println(first);
}
}
/** 运行结果 */
Optional[6]
7、归约(reduce)
reduce
操作可以实现从Stream中生成一个值,其生成的值不是随意的,而是根据指定的计算模型。
定义了初始值
public class Test02 {
public static void main(String[] args) {
//定义了初始值,用来指定stream循环的初始值,如果stream为空,就直接返回该值
List<Integer> list1 = Arrays.asList();
Integer reduce1 = list1.stream().reduce(0, (a, b) -> a + b);
System.out.println(reduce1);
List<Integer> list2 = Arrays.asList(4, 5, 3, 9);
Integer reduce2 = list2.stream().reduce(0, (a, b) -> a + b);
System.out.println(reduce2);
}
}
/** 运行结果 */
0
21
reduce操作流的流程
:Lambda反复结合每个元素,直到流被归约成一个值。首先,0作为Lambda(a)的 第一个参数,从流中获得4作为第二个参数(b)。0 + 4得到4,它成了新的累积值。然后再用累 积值和流中下一个元素5调用Lambda,产生新的累积值9。接下来,再用累积值和下一个元素3调用Lambda,得到12。最后,用12和流中最后一个元素9调用Lambda,得到最终结果21。
无初始值
public class Test03 {
public static void main(String[] args) {
//没有定义初始值,但是会返回一个Optional对象
List<Integer> list1 = Arrays.asList();
Optional<Integer> reduce1 = list1.stream().reduce((a, b) -> a + b);
System.out.println(reduce1);
List<Integer> list2 = Arrays.asList(4, 5, 3, 9);
Optional<Integer> reduce2 = list2.stream().reduce((a, b) -> a + b);
System.out.println(reduce2);
}
}
/** 运行结果 */
Optional.empty
Optional[21]
四、数值流
1、原始类型流特化
public class Test04 {
public static void main(String[] args) {
//初始化数据
List<Dish> init = DishService.init();
//计算出总热量
Integer reduce = init.stream().map(Dish::getCalories).reduce(0, Integer::sum);
System.out.println(reduce);
}
}
上面代码,暗含一个装箱的成本。每个Integer都必须拆箱成一个原始类型, 再进行求和。如果能直接调用
sun()
方法岂不是更好?int calories = init.stream().map(Dish::getCalories).sum();
但是这种是不可能的,问题在于map
方法会生成一个Stream<T>
。虽然流中的元素是Integer
类 型,但Streams
接口没有定义sum
方法。Stream API还提供了原始类型流特化,专门支持处理数值流的方法。
Java 8引入了三个原始类型特化流接口来解决这个问题:
IntStream
、DoubleStream
和LongStream
,分别将流中的元素特化为int
、long
和double
,从而避免了暗含的装箱成本。每 个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到大元素的max。 此外还有在必要时再把它们转换回对象流的方法。要记住的是,这些特化的原因并不在于流的复杂性,而是装箱造成的复杂性——即类似int
和Integer
之间的效率差异。
2、映射到数值流
将流转化为特化版本的常用方法是
mapToInt
、mapToDouble
和mapToLong
。这些方法和map
方法的工作方式一样,只是它返回的是一个特化流,而不是Stream<T>
。
public class Test05 {
public static void main(String[] args) {
//初始化数据
List<Dish> init = DishService.init();
//流特化
IntStream intStream = init.stream().mapToInt(Dish::getCalories);
//计算总和,如果流是空的,sum默认返回0,IntStream还支持其他的方法,如max、min、average等
int sum = intStream.sum();
System.out.println(sum);
}
}
3、转换回对象流
一旦有了数值流,可能会想把它转换回非特化流。例如,IntStream上的操作只能产生原始整数:IntStream的map操作接受的Lambda必须接受int并返回int(一个 IntUnaryOperator)。但是你可能想要生成另一类值,比如Dish。为此,你需要访问Stream 接口中定义的那些更广义的操作。要把原始流转换成一般流(每个int都会装箱成一个 Integer),可以使用
boxed
方法。
public class Test06 {
public static void main(String[] args) {
//初始化数据
List<Dish> init = DishService.init();
//流特化
IntStream intStream = init.stream().mapToInt(Dish::getCalories);
//调用boxed方法转为一般流
Stream<Integer> boxed = intStream.boxed();
//找出是3的倍数的数
List<Integer> collect = boxed.filter(x -> x % 3 == 0).collect(Collectors.toList());
System.out.println(collect);
}
}
/** 运行结果 */
[120, 300, 450]
4、默认值OptionalInt
对于三种原始流特化,也分别有一个Optional原始类型特化版本:OptionalInt、OptionalDouble和OptionalLong。
public class Test07 {
public static void main(String[] args) {
//初始化数据
List<Dish> init = DishService.init();
//流特化
IntStream intStream = init.stream().mapToInt(Dish::getCalories);
//找到最大元素,如果流为空,则会返回【OptionalInt.empty】
OptionalInt max = intStream.max();
System.out.println(max);
}
}
5、数值范围
Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成范围的数值,
range
和rangeClosed
。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但range
是不包含结束值的,而rangeClosed
则包含结束值。
public class Test08 {
public static void main(String[] args) {
//生成0-100的数字
IntStream intStream1 = IntStream.rangeClosed(1, 100);
IntStream intStream2 = IntStream.range(1, 100);
//统计生成个数
long count1 = intStream1.count();
long count2 = intStream2.count();
System.out.println("rangeClosed生成数字个数:" + count1);
System.out.println("range生成数字个数:" + count2);
}
}
/** 运行结果 */
rangeClosed生成数字个数:100
range生成数字个数:99
五、构建流
1、由值创建流
可以使用静态方法
Stream.of
,通过显式值创建一个流,它可以接受任意数量的参数。
Stream.empty
得到一个空流。
public class Test09 {
public static void main(String[] args) {
//由Stream.of生成一个流
Stream<Integer> integerStream = Stream.of(1, 2, 4, 5);
List<Integer> collect = integerStream.filter(x -> x % 2 == 0).collect(Collectors.toList());
System.out.println(collect);
}
}
/** 运行结果 */
[2, 4]
2、由数组创建流
可以使用静态方法
Arrays.stream
从数组创建一个流。它接受一个数组作为参数。
public class Test09 {
public static void main(String[] args) {
int[] num = {1, 2, 3, 4, 5};
int sum = Arrays.stream(num).sum();
System.out.println(sum);
}
}
/** 运行结果 */
15
3、由文件生成流
Java中用于处理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。 java.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法是 Files.lines,它会返回一个由指定文件中的各行构成的字符串流。
public class Test10 {
public static void main(String[] args) {
long uniqueWords = 0;
try (Stream<String> lines = Files.lines(Paths.get("D:\\Users\\yeyanbin\\Desktop\\data.txt"), StandardCharsets.UTF_8)) {
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(","))).distinct().count();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(uniqueWords);
}
}
上面代码使用
Files.lines
得到一个流,其中的每个元素都是给定文件中的一行。然后,可以对line
调用split
方法将行拆分成单词。应该注意的是,你该如何使用flatMap
产生一个扁平的单词流,而不是给每一行生成一个单词流。最后,把distinct
和count
方法链接起来,数数流中有多少各不相同的单词。