StramApi常用操作
什么是流 ?
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返.
流的结构
当我们使用一个流的时候,通常包括三个基本步骤:
获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图所示。
流管道 (Stream Pipeline) 的构成
生成 Stream Source的方式
从 Collection 和数组
Collection.stream()
Collection.parallelStream()
Arrays.stream(T array) or Stream.of()
从 BufferedReader
java.io.BufferedReader.lines()
静态工厂
java.util.stream.IntStream.range()
java.nio.file.Files.walk()
自己构建
java.util.Spliterator
其它
Random.ints()
BitSet.stream()
Pattern.splitAsStream(java.lang.CharSequence)
JarFile.stream()
流的操作类型分为两种:
Intermediate(中间):一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
Terminal(最终):一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
一个流操作的示例
int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
.mapToInt(w -> w.getWeight())
.sum();
stream() 获取当前小物件的 source,filter 和 mapToInt 为 intermediate 操作,进行数据筛选和转换,最后一个 sum() 为 terminal 操作,对符合条件的全部小物件作重量求和。
构造流的几种常见方法
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();
1.流转换为其它数据结构
1.1 转为数组 Array
String[] arr=Stream.toArray(String[]::new)
代码:
private static List<Person> getList() {
List<Person> list = new ArrayList<>();
list.add(new Person("范冰冰","女",28));
list.add(new Person("刘亦菲","女",24));
list.add(new Person("胡歌","男",30));
return list;
}
public static void main(String[] args) {
List<Person> list = getList();
//list 转 数组
Person[] arr= list.stream().toArray(Person[]::new);
Arrays.stream(arr).forEach(System.out::println);
}
效果:
1.2 转为集合 list,set Collection
List<String> list = stream.collect(Collectors.toList());
List<String> list = stream.collect(Collectors.toCollection(ArrayList::new));
Set set = stream.collect(Collectors.toSet());
Stack stack = stream.collect(Co`llectors.toColl`ection(Stack::new));
代码:
public static void main(String[] args) {
List<Person> list = getList();
//list 转 数组
Person[] arr= list.stream().toArray(Person[]::new);
//数组 转 list
List<Person> collect = Arrays.stream(arr).collect(Collectors.toList());
}
1.3 转为字符串
String str = stream.collect(Collectors.joining()).toString();
//Collectors.joining 收集器 支持灵活的参数配置,可以指定字符串连接时的 分隔符,前缀 和 后缀 字符串
代码:
final static String[] names = {"A", "B", "C", "D", "E"};
public static void main(String[] args) {
String collect = Stream.of(names).collect(Collectors.joining("-", "{", "}"));
System.out.println("字符串格式="+collect);
}
效果图:
2.流的操作
流的操作
接下来,当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。
Intermediate:
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
Terminal:
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
常用操作:
map: 把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素。
在项目中的应用:
1.list对象转换为某个属性的list字符串 (用来取list对象中的所有的对象的某个属性的list)
private static List<Person> getList() {
List<Person> list = new ArrayList<>();
list.add(new Person("范冰冰","女",28));
list.add(new Person("刘亦菲","女",24));
list.add(new Person("胡歌","男",30));
return list;
}
public static void main(String[] args) {
//获取所有的学生名字
List<Person> list = getList();
List<String> resList = list.stream().map(Person::getName).collect(Collectors.toList());
resList.stream().forEach(System.out::println);
}
2.list对象转化为某两个属性拼接的list字符串
public static void main(String[] args) {
//获取所有人 "名字+年龄" 格式 的list
List<Person> list = getList();
List<String> resList = list.stream().map(e -> e.getName()+"+"+e.getAge()).collect(Collectors.toList());
resList.stream().forEach(System.out::println);
}
3.list对象按照某个属性值进行分组
private static List<Person> getList() {
List<Person> list = new ArrayList<>();
list.add(new Person("范冰冰","女",28));
list.add(new Person("刘亦菲","女",24));
list.add(new Person("胡歌","男",30));
list.add(new Person("胡歌","女",24));
return list;
}
public static void main(String[] args) {
//根据名字进行分组
List<Person> list = getList();
Map<Integer, List<Person>> collect = list.stream().collect(Collectors.groupingBy(Person::getAge));
Set<Map.Entry<Integer, List<Person>>> entries = collect.entrySet();
for(Map.Entry<Integer, List<Person>> entity:entries){
System.out.println("key="+entity.getKey()+"value="+entity.getValue().size());
}
}
4.list对象按照某两个属性拼接进行分组
private static List<Person> getList() {
List<Person> list = new ArrayList<>();
list.add(new Person("范冰冰","女",28));
list.add(new Person("刘亦菲","女",24));
list.add(new Person("胡歌","男",24));
list.add(new Person("胡歌","女",24));
return list;
}
public static void main(String[] args) {
//根据 "性别-年龄" 格式进行分组
List<Person> list = getList();
Map<String, List<Person>> collect = list.stream().collect(Collectors.groupingBy(e -> e.getGender() + "-" + String.valueOf(e.getAge())));
Set<Map.Entry<String, List<Person>>> entries = collect.entrySet();
for(Map.Entry<String, List<Person>> entity:entries){
System.out.println("key="+entity.getKey()+" value="+entity.getValue().size());
}
}