Stream 流是 Java8 新特性之一,我们在实际开发中借助 Stream 流搭配 Lambda 表达式,可以很方便的完成一些对集合的操作,可以显著的提升开发的效率和性能。
一、Stream 流的常见生成方式
通常我们会选择对以下类型生成 Stream 流:单列集合、双列集合、数组、零散的数据。
public class StreamDemo {
public static void main(String[] args) {
//Collection体系的集合可以使用默认方法stream()生成流
List<String> list = new ArrayList<String>();
Stream<String> listStream = list.stream();
Set<String> set = new HashSet<String>();
Stream<String> setStream = set.stream();
//Map体系的集合间接的生成流
Map<String,Integer> map = new HashMap<String, Integer>();
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
//数组可以通过Arrays中的静态方法stream生成流
String[] strArray = {"hello","world","java"};
Stream<String> strArrayStream = Arrays.stream(strArray);
//同种数据类型的多个数据可以通过Stream接口的静态方法of(T... values)生成流
Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
Stream<Integer> intStream = Stream.of(10, 20, 30);
}
}
二、Stream 流中间操作方法
1、常用中间操作方法
这里的中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作(其他中间操作或终结操作)
方法名 | 说明 |
---|---|
Stream filter(Predicate predicate) | 用于对流中的数据进行过滤 |
Stream limit(long maxSize) | 获取前几个元素 |
Stream skip(long n) | 跳过前几个元素 |
Stream distinct() | 元素去重,依赖(hashCode和equals方法) |
Stream sorted(Comparator<? super T> comparator) | Stream 流元素排序 |
static Stream concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
Stream map(Function<T,R> mapper) | 转换流中的数据类型 |
注:Stream.contact
合并两个 stream
流的时候,如果一个 stream 是 a 类型,另一个 stream 是 b 类型,那么最终合并为 a,b的父类型(类型提升)。
2、使用示例1
public class FilterDemo {
// 获取以张开头的元素
public static void main(String[] args) {
// 创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<>();
list.add("张三丰");
list.add("张无忌");
list.add("张翠山");
list.add("王二麻子");
list.add("张良");
list.add("谢广坤");
list.stream().filter(s ->s.startsWith("张")).forEach(s-> System.out.println(s));
}
}
3、使用示例2
public class LimitAndSkipDemo {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
//需求1:取前3个数据在控制台输出
list.stream().limit(3).forEach(s-> System.out.println(s));
System.out.println("--------");
//需求2:跳过3个元素,把剩下的元素在控制台输出
list.stream().skip(3).forEach(s-> System.out.println(s));
System.out.println("--------");
//需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
list.stream().skip(2).limit(2).forEach(s-> System.out.println(s));
}
}
4、使用示例3
public class SortedDemo {
// 按照年龄进行排序
public static void main(String[] args) {
ArrayList<String> manList = new ArrayList<>();
Collections.addAll(manList,"蔡坤坤,24","叶厚贤,23,","刘不甜,22","吴倩,24","谷加,30","肖亮亮,27");
manList.stream()
.sorted((o1,o2)->Integer.parseInt(o1.split(",")[1]) - Integer.parseInt(o2.split(",")[1]))
.forEach(x -> System.out.println(x));
}
}
5、使用示例4
public class ConcatAndDistinctDemo {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
//需求1:取前4个数据组成一个流
Stream<String> s1 = list.stream().limit(4);
//需求2:跳过2个数据组成一个流
Stream<String> s2 = list.stream().skip(2);
//需求3:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
Stream.concat(s1,s2).distinct().forEach(s-> System.out.println(s));
}
}
6、使用示例5
public class MapDemo {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌-15","周芷若-14","赵敏-13","张强-20","张三丰-100","张翠山-40");
// 需求:只获取里面1的年龄并进行打印
List<Integer> collect = list.stream()
.map(s -> Integer.parseInt(s.split("-")[1]))
.collect(Collectors.toList());
System.out.println(collect);
}
}
7、Stream 流使用注意事项
注意1:修改 Stream
流中的数据,不会影响原来集合或者数组中的数据。
注意2:中间方法,返回新的 Stream
流,原来的 Stream 流只能使用一次,建议使用链式编程()。因为每次调用中间方法都会返回新的 Stream 流,原来的 Stream 流会关闭。如:
public class Test1 {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
Stream<String> stream1 = list.stream().filter(s -> s.startsWith("张"));
Stream<String> stream2 = stream1.filter(s -> s.length() == 3);
stream2.forEach(s -> System.out.println(s));
stream1.filter(s -> s.length() == 2);
}
}
三、Stream 流终结操作方法
1、常用终结方法
终结操作的意思是,执行完终结方法之后,Stream
流将不能再执行其他操作了。
方法名 | 说明 |
---|---|
void forEach(Consumer action) | 遍历 |
long count() | 统计个数 |
toArray() | 收集流中的数据,放到数组中 |
R collect(Collector collector) | 收集流中的数据,放到集合中 |
工具类 Collectors
提供了具体的收集方式:
方法名 | 说明 |
---|---|
public static Collector toList() | 把元素收集到List集合中 |
public static Collector toSet() | 把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper,Function valueMapper) | 把元素收集到Map集合中 |
注意1:toSet()
会进行自动去重。
注意2:toMap()
如果我们要收集到 Map 集合当中,键重复会报错。
2、使用示例1
public class CountAndForEachDemo {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<>();
list.add("张三丰");
list.add("张无忌");
list.add("张翠山");
list.add("王二麻子");
list.add("张良");
list.add("谢广坤");
// 统计返回此流中的元素数
long count = list.stream().count();
System.out.println(count);
// 遍历打印流中元素
list.stream().forEach(x -> System.out.println(x));
}
}
3、使用示例2
public class ToArrayDemo {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<>();
list.add("张三丰");
list.add("张无忌");
list.add("张翠山");
list.add("王二麻子");
list.add("张良");
list.add("谢广坤");
// 收集流中的数据,放到数组中 (Object 数组)
Object[] objects = list.stream().toArray();
System.out.println(Arrays.toString(objects));
// 收集流中的数据,放到数组中 (指定类型 数组)
// toArray 方法参数就是创建一个指定类型的数组
// 底层会依次得到流里面的每一个数据,并把数据放到数组当中
// 返回值就是一个装着流里面所有数据的数组
String[] strings = list.stream().toArray(valuecount -> new String[valuecount]);
System.out.println(Arrays.toString(strings));
}
}
4、使用示例3
public class CollectDemo {
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌-男-15","周芷若-女-14","赵敏-女-13","张三丰-男-100","谢广坤-男-41");
// 需求1:把所有男性收集到list集合当中
List<String> newList1 = list.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toList());
System.out.println(newList1);
// 需求2:把所有男性收集到set集合当中
Set<String> newList2 = list.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toSet());
System.out.println(newList2);
// 需求3:把所有男性收集到map集合当中(键为姓名,值为年龄)
Map<String, Integer> map = list.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toMap(
s -> s.split("-")[0],
s -> Integer.parseInt(s.split("-")[2])));
System.out.println(map);
}
}
5、Stream 基本分组功能
方法名 | 说明 |
---|---|
Collector<T, ?, Map<K, List>> groupingBy(Function<? super T, ? extends K> classifier) | 这是最基本的 groupingBy 方法,classifier:键映射:该方法的返回值是键值对的 键 |
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream) | 这个重载方法在基本的 groupingBy 方法的基础上添加了一个 downstream 参数,downstream:值映射:通过聚合方法将同键下的结果聚合为指定类型,该方法返回的是键值对的 值 |
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, Supplier mapFactory, Collector<? super T, A, D> downstream) | 这个重载方法除了包含分类函数和 downstream 收集器外,还接收一个 mapFactory 参数,mapFactory:无参构造函数提供返回类型:提供一个容器初始化方法,用于创建新的 Map容器 (使用该容器存放值对) |
public class GroupDemo {
// 基本分组操作
public static void main(String[] args) {
// 初始化数据
Actor actor1 = new Actor("张三",18);
Actor actor2 = new Actor("李四",20);
Actor actor3 = new Actor("王五",22);
List<Actor> actors = new ArrayList<>();
Collections.addAll(actors,actor1,actor2,actor3);
// 按照姓名分组
Map<String, List<Actor>> collect = actors.stream().collect(Collectors.groupingBy(actor -> actor.getName()));
// 打印结果
System.out.println(collect);
// 将不同课程的学生进行分类
Map<String, List<Actor>> groupByCourse = actors.stream().collect(Collectors.groupingBy(actor -> actor.getName()));
System.out.println(groupByCourse
);
Map<String, List<Actor>> groupByCourse1 = actors.stream().collect(Collectors.groupingBy(actor -> actor.getName(), Collectors.toList()));
System.out.println(groupByCourse1);
// 上面的方法中容器类型和值类型都是默认指定的,容器类型为:HashMap,值类型为:ArrayList
// 可以通过下面的方法自定义返回结果、值的类型
Map<String, List<Actor>> groupByCourse2 = actors.stream()
.collect(Collectors.groupingBy(actor -> actor.getName(),
() -> new HashMap<>(),
Collectors.toList()));
System.out.println(groupByCourse2);
}
}
其他分组操作参考:Stream Collectors.groupingBy 的四种用法
四、Stream 综合小练习
public class Test {
public static void main(String[] args) {
// 现有两个 ArrayList 集合:
// 第一个集合:存储 6 名男演员的名字和年龄;第二个集合:存储 6 名女演员的名字和年龄
ArrayList<String> manList = new ArrayList<>();
ArrayList<String> womanList = new ArrayList<>();
Collections.addAll(manList,"蔡坤坤,24","叶厚贤,23,","刘不甜,22","吴倩,24","谷加,30","肖亮亮,27");
Collections.addAll(womanList,"赵晓英,35","杨颖,36","高媛媛,43","刘诗,35","杨小敏,33");
// 需求1:男演员只要名字为 3 个字的前两人
Stream<String> stream1 = manList.stream().filter(s -> s.split(",")[0].length() == 3)
.limit(2);
// 需求2:女演员只要姓杨的,并且不要第一个
Stream<String> stream2 = womanList.stream().filter(s -> s.split(",")[0].startsWith("杨"))
.skip(1);
// 需求3:把过滤后的男演员姓名和女演员合并到一起封装成 Actor 对象(Actor 对象属性有 name、age)
Stream<Actor> stream3 = Stream.concat(stream1, stream2)
.map(s -> new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1])));
// 需求5:将所有的演员对象都报存到 List 集合中
stream3.collect(Collectors.toList()).forEach(s -> System.out.println(s));
}
}