获取流的方法 | |
llist | Collection.stream |
map | map.keySet().stream() |
数组 | Stream.of(array) |
中间方法 | |
filter | 可以用于条件过滤 |
map | 如果需要将流中的元素映射到另一个流中,可以使用 map 方法 |
limit | 取用前几个(截取) |
skip | 跳过前几个元素 |
concat | 组合(合并流) |
distinct |
去除流中重复的元素(使用hashcode和equals方法来对比) |
flatMap | 映射(打开后再转换) |
sorted | 自然排序 |
sorted(Comparator com) | 定制排序 |
collect(Collector c) |
收集 |
iterate | 迭代 |
peek | 查看 |
终结方法 | |
allMatch | 检测匹配: |
count | 统计个数 |
forEach | 用于遍历 |
findFirst,findAny |
查找第一个或其中的一个元素,返回Optional类型 |
max(comparator c) min(comparator c) |
查找最大最小值 |
reduce |
规约 |
forEach为什么不能return?
根本原因:在lambda表达式中的return并不会终止循环,这是由于lambda的底层实现导致的
Stream(流)是一个来自数据源的元素队列并支持聚合操作
Stream操作的两个基础特征
Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
图中通过Collection.stream()方法得到Head也就是stage0,紧接着调用一系列的中间操作,不断产生新的Stream。这些Stream对象以双向链表的形式组织在一起,构成整个流水线,由于每个Stage都记录了前一个Stage和本次的操作以及回调函数,依靠这种结构就能建立起对数据源的所有操作。这就是Stream记录操作的方式。
————————————————
java.util.stream.Stream 是Java 8新加入的流接口。(并不是一个函数式接口)
获取一个流非常简单,有以下几种常用的方式:
根据Collection获取流
public static void main(String[] args) {
List list = new ArrayList<>();
Stream stream1 = list.stream();
Set set = new HashSet<>();
Stream stream2 = set.stream();
Vector vector = new Vector<>();
// ...
}
根据Map获取流
public static void main(String[] args) {
Map map = new HashMap<>();
Stream keyStream = map.keySet().stream();
Stream valueStream = map.values().stream();
Stream> entryStream = map.entrySet().stream();
}
根据数组获取流
如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法of ,使用很简单:
public static void main(String[] args) {
//使用 Stream.of
String[] array = { "张无忌", "张翠山", "张三丰", "张一元" };
Stream stream = Stream.of(array);
//使用Arrays的静态方法
Arrays.stream(array)
}
这些方法可以被分成两种:
用于遍历的方法,参数传入一个函数式接口:Consumer
public static void main(String[] args) {
Stream stream = Stream.of("张无忌", "张三丰", "周芷若");
stream.forEach(name‐> System.out.println(name));
}
可以用于条件过滤
可以通过 filter 方法将一个流转换成另一个子集流。
public static void main(String[] args) {
//创建一个流
Stream stream = Stream.of("张三丰", "刘德华", "张国荣", "彭于晏", "纳什", "吴彦祖", "吴绮蓉");
//对流中元素过滤,只要姓张的人
Stream stream2 = stream.filter(name -> {
return name.startsWith("张");
});
//遍历过滤后的流
stream2.forEach(name -> System.out.println(name));
}
如果需要将流中的元素映射到另一个流中,可以使用 map 方法。
该接口需要一个 Function 函数式接口参数
此前我们已经学习过 java.util.stream.Function 函数式接口,其中唯一的抽象方法为
R apply(T t);
这可以将一种T类型转换成为R类型,而这种转换的动作,就称为“映射"
map使用方法
/**
* stream流的map方法练习
* map方法可以将流中的元素映射到另一个流中
* map方法的参数是一个Function函数式接口
*/
@Test
public void test(){
//创建一个流,里面是字符串类型的整数
Stream stream1 = Stream.of("2", "32", "2", "33", "2");
//把stream1流中的整数全部转成int类型
Stream stream2 = stream1.map((String s) -> {
return Integer.parseInt(s);
});
//遍历
stream2.forEach((i)-> System.out.println(i));
}
正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来数一数其中的元素个数:
该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。基本使用:
public class Demo09StreamCount {
public static void main(String[] args) {
Stream original = Stream.of("张无忌", "张三丰", "周芷若");
//筛选姓张的
Stream result = original.filter(s ‐> s.startsWith("张"));
//输出个数
System.out.println(result.count()); // 2
}
}
limit 方法可以对流进行截取,只取用前n个。
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用:
public class Demo10StreamLimit {
public static void main(String[] args) {
Stream original = Stream.of("张无忌", "张三丰", "周芷若");
//截取前两个
Stream result = original.limit(2);
System.out.println(result.count()); // 2
}
}
如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
Stream skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用
public class Demo11StreamSkip {
public static void main(String[] args) {
Stream original = Stream.of("张无忌", "张三丰", "周芷若");
//跳过前两个,返回一个新的流
Stream result = original.skip(2);
System.out.println(result.count()); // 1
}
}
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
public class Demo12StreamConcat {
public static void main(String[] args) {
Stream streamA = Stream.of("张无忌");
Stream streamB = Stream.of("张翠山");
//合并成一个新的流
Stream result = Stream.concat(streamA, streamB);
}
}
去除流中重复的元素(使用hashcode和equals方法来对比)
内部传入一个Function函数式接口,跟map的区别就是这个会把流中的元素打开,再组合成一个新的流
// map和flatMap的练习
public class StreamDemo {
@Test
public void test(){
List list = Arrays.asList("aa","bb","cc","dd");
// 练习1 (map) 输出的全是大写
list.stream().map(s -> s.toUpperCase()).forEach(System.out::println);
System.out.println("----------");
// 练习2(map)流里还有流,需要两个遍历才行看到里面内容
Stream> streamStream = list.stream().map(StreamDemo::fromStringToStream);
streamStream.forEach(s -> {
s.forEach(System.out::println);
});
System.out.println("---------");
// 练习3(flatMap)流里还有流,使用flatMap可以直接把里面的流打开,一次遍历就可以了
Stream characterStream = list.stream().flatMap(StreamDemo::fromStringToStream);
characterStream.forEach(System.out::println);
}
/**
* 将字符串中的多个字符构成的集合转换为对应的stream
* @param str
* @return
*/
public static Stream fromStringToStream(String str){
ArrayList list = new ArrayList();
// 将字符串转成字符数组,并遍历加入list集合
for(Character c : str.toCharArray()){
list.add(c);
}
// 返回list集合的stream流
return list.stream();
}
}
看下一条里的代码
/**
* 排序的练习
*/
@Test
public void test2(){
List integers = List.of(124, 2, 15, 12, 51, -5, 5);
// 按照自然排序
integers.stream().sorted().forEach(System.out::println);
System.out.println("---------");
List integers2 = List.of(124, 2, 15, 12, 51, -5, 5);
// 定制排序(大到小),需要传入Comparator接口(如果流中的是引用类型,只能用定制排序)
// 简写:integers2.stream().sorted((e1,e2) -> e2-e1).forEach(System.out::println);
integers2.stream().sorted((e1,e2) -> {
return e2-e1;
}).forEach(System.out::println);
}
返回一个Boolean值
是否全部匹配:allMatch
是否至少匹配一个:anyMatch
是否没有匹配的:noneMatch
/**
* 匹配的练习
*/
@Test
public void test3(){
List integers = List.of(124, 2, 15, 12, 51, -5, 5);
// 判断是否全部大于5
boolean b = integers.stream().allMatch(i -> i > 5);
// 结束输出false
System.out.println(b);
System.out.println("-------");
List integers2 = List.of(124, 2, 15, 12, 51, -5, 5);
// 检测是否匹配至少一个元素
boolean b1 = integers2.stream().anyMatch(i -> i > 5);
// 输出true
System.out.println(b1);
System.out.println("-------");
List integers3 = List.of(124, 2, 15, 12, 51, -5, 5);
// 检查是否没有匹配的元素
boolean b2 = integers3.stream().noneMatch(i -> i > 1000);
// 输出true,全部不匹配
System.out.println(b2);
}
查找第一个元素:findFirst,返回Optional类型
查找其中一个元素:findAny,返回Optional类型
public void test4(){
List integers = List.of(124, 2, 15, 12, 51, -5, 5);
// 输出第一个元素
Optional first = integers.stream().findFirst();
// 输出结果是Optional[124]
System.out.println(first);
System.out.println("-------------");
List integers2 = List.of(124, 2, 15, 12, 51, -5, 5);
// 返回其中一个元素
Optional any = integers2.stream().findAny();
System.out.println(any);
}
max(comparator c)
min(comparator c)
/**
* 查找最大最小值
*/
@Test
public void test5(){
List list = new ArrayList<>();
list.add(new Person("马化腾",25,3000));
list.add(new Person("李彦宏",27,2545));
list.add(new Person("雷军",35,4515));
list.add(new Person("马云",55,9877));
// 查找年龄最大的人
Optional max = list.stream().max((e1, e2) -> e1.getAge() - e2.getAge());
// 返回马云,55岁年龄最大
System.out.println(max.get());
System.out.println("--------");
reduce(T identity ,BinaryOperator) 第一个参数是初始值,第二个参数是一个函数式接口
reduce(BinaryOperator) 参数是一个函数式接口
/**
* 归约的练习
*/
@Test
public void test6(){
List integers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 求集合里数字的和(归约)reduce第一个参数是初始值。
Integer sum = integers.stream().reduce(0, Integer::sum);
System.out.println(sum);
System.out.println("-------");
List list = new ArrayList<>();
list.add(new Person("马化腾",25,3000));
list.add(new Person("李彦宏",27,2545));
list.add(new Person("雷军",35,4515));
list.add(new Person("马云",55,9877));
// 求所有人的工资和(归约)
// 不用方法引用写法:Optional reduce = list.stream().map(person -> person.getSalary()).reduce((e1, e2) -> e1 + e2);
Optional reduce = list.stream().map(Person::getSalary).reduce(Integer::sum);
// 输出Optional[19937]
System.out.println(reduce);
}
collect(Collector c):将流转化为其他形式,接收一个Collector接口的实现
/**
* 收集的练习
*/
@Test
public void test7(){
List list = new ArrayList<>();
list.add(new Person("马化腾",25,3000));
list.add(new Person("李彦宏",27,2545));
list.add(new Person("雷军",35,4515));
list.add(new Person("马云",55,9877));
// 把年龄大于30岁的人,转成一个list集合
List collect = list.stream().filter(person -> person.getAge() > 30).collect(Collectors.toList());
// 遍历输出(输出雷军和马云)
for (Person person : collect) {
System.out.println(person);
}
System.out.println("----------");
List list2 = new ArrayList<>();
list2.add(new Person("马化腾",25,3000));
list2.add(new Person("李彦宏",27,2545));
list2.add(new Person("雷军",35,4515));
list2.add(new Person("马云",55,9877));
// 把姓马的人,转成Set集合
Set set = list2.stream().filter(person -> person.getName().startsWith("马")).collect(Collectors.toSet());
// 输出马云和马化腾
set.forEach(System.out::println);
}
可以使用Stream.iterate创建流值,即所谓的无限流。
//Stream.iterate(initial value, next value)
Stream.iterate(0, n -> n + 1)
.limit(5)
.forEach(x -> System.out.println(x));
输出:
0
1
2
3
4
peek接收的是一个Consumer函数,peek 操作会按照 Consumer 函数提供的逻辑去消费流中的每一个元素,同时有可能改变元素内部的一些属性
@Test
public void test1(){
List collect = Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("查看一下刚过滤出的值:" + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("查看一下转大写之后的值:" + e))
.collect(Collectors.toList());
System.out.println("-----分割线-----");
// 遍历过滤后的集合
for (String s : collect) {
System.out.println(s);
}
}
输出:
查看一下刚过滤出的值:three
查看一下转大写之后的值:THREE
查看一下刚过滤出的值:four
查看一下转大写之后的值:FOUR
-----分割线-----
THREE
FOUR
/**
* 1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
* 2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
* 3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
* 4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
* 5. 将两个队伍合并为一个队伍;存储到一个新集合中。
* 6. 根据姓名创建 Person 对象;存储到一个新集合中。
* 7. 打印整个队伍的Person对象信息。
*/
public class Demo3 {
public static void main(String[] args) {
//第一支队伍
ArrayList one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("石破天");
one.add("石中玉");
one.add("老子");
one.add("庄子");
one.add("洪七公");
//第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
//第一个队伍筛选之后只要前3个人;存储到一个新集合中。
Stream stream = one.stream();
Stream stream1 = stream.filter(name -> name.length() == 3).limit(3);
//第二支队伍
ArrayList two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张二狗");
//第二个队伍只要姓张的成员姓名;存储到一个新集合中。
//第二个队伍筛选之后不要前2个人;存储到一个新集合中。
Stream stream2 = two.stream();
Stream stream3 = stream2.filter(name -> name.startsWith("张")).skip(2);
//合并两个队伍
Stream concat = Stream.concat(stream1, stream3);
//把合并后的队伍根据姓名创建Person对象,并存入新的集合中,然后打印
concat.map(name -> new Person(name)).forEach(p -> System.out.println(p));
}
}