概述
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)); } }
小结
Stream 流的使用步骤?
- 获取 Stream 流对象
- 使用中间方法处理数据
- 使用终结方法处理数据
如何获取 Stream 流对象?
- 单列集合:Collection 中的默认方法
- 双列集合:不能直接获取,可借助 keySets、values 转换成单列集合间接获取
- 数组:Arrays 工具类型中的静态方法
- 零散的数据:Stream 接口中的静态方法 of
Stream 常见方法?
- 中间方法:filter、limit、skip、distinct、concat、map
- 终结方法:forEach、count、collect
Lambda 怎么简化?
- 首先,数据类型可以省略
- 如果形参只有一个,小括号可以省略
- 如果形参有多个,小括号就不能省略了
- 如果方法体只有一行,那么大括号可以省略,return 可以省略分号可以省略
注:上述所有方法皆为函数式接口,因此均可以使用 Lambda 表达式进行替换,或使用方法引用替换(下期介绍)。