Stream学习

一、参考

可以参考:https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/

二、Stream简介

主要是对java中集合的一些相关操作提供了很好用的一些api,他类似于 Iterator,但是比其操作集合更加方便,而且其对并行操作也有很好的优势。

流的操作主要分为两种:


  • Intermediate:后面可以跟随很多操作,比如在操作filter之后还可以进行map等操作,其常见的操作有:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered。
  • Terminal:这可以理解为是流的结束操作,就是流操作的最后一步( short-circuiting可改变),其常见的有: forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator。
    – (Short-circuiting:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit)

三、流的构造

1.常见的构造方法

// 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();

2.数值流的构造

IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
IntStream.range(1, 3).forEach(System.out::println);
IntStream.rangeClosed(1, 3).forEach(System.out::println);

3.流转换为其它数据结构

// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();

四、流的详细操作

对象:

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person{
private Long Id;
private String name;
private Integer age;
}

1. map: 理解其为转换流,可以将一个输入流中的每个元素,映射成输出流的另一个元素。

eg: 将Person集合里面的每个Person名字单独提取出来(List<Person> -> List<String>)

public class MainTest {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person(1L, "zs", 18));
people.add(new Person(2L, "lss", 25));
List<String> names = people.stream().map(Person::getName).collect(Collectors.toList());
names.forEach(System.out::println);
}
}

// 结果:
/*
zs
lss
*/

2. flatmap: 可以将一个层级的集合进行降级操作,然后再进行其他一级流操作。

eg: 从List<List<Person>>中取出name的集合:

public class MainTest {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person(1L, "zs", 18));
people.add(new Person(2L, "lss", 25));
List<List<Person>> twoLevel = new ArrayList<>();
twoLevel.add(people);
List<String> names = twoLevel.stream()
.flatMap(peopleList -> peopleList.stream()
.map(Person::getName)).collect(Collectors.toList());
names.stream().forEach(System.out::println);
}
}

// 结果:
/*
zs
lss
*/

3. filter:过滤操作,符合条件的返回。

eg: 获取年龄小于18岁的Person。

public class MainTest {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person(1L, "zs", 18));
people.add(new Person(2L, "lss", 25));
people.stream().filter(person -> person.getAge() < 20).forEach(System.out::println);
}
}

// 结果:
/*
Person(Id=1, name=zs, age=18)
*/

4. forEach: 遍历输出操作。

forEach(System.out::println)
forEach(System.out::print)
forEach(System.out::printf)

5. reduce: 这个方法的主要作用是把 Stream 元素组合起来。然后前面有个起始值,后面跟一个特定的操作。这里需要注意的是:如果reduce()第一个参数无初识值,返回的则是一个Optional

// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 过滤,字符串连接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
filter(x -> x.compareTo("Z") > 0).
reduce("", String::concat);

6. limit/skip:limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。

eg:取出很多对象中的前10个并丢弃前三个。

public class MainTest {
public static void main(String[] args) {
List<Person> persons = new ArrayList();
for (Long i = 1L; i <= 10000; i++) {
Person person = new Person(i, "name" + i, 18);
persons.add(person);
}
List<String> personList2 = persons.stream().
map(Person::getName).limit(10).skip(3).collect(Collectors.toList());
System.out.println(personList2);
}
}

// 结果:
/*
[name4, name5, name6, name7, name8, name9, name10]
*/

7. sorted: 排序操作,底层还是CompareTo

public class MainTest {
public static void main(String[] args) {
List<Person> persons = new ArrayList();
for (Long i = 1L; i <= 5; i++) {
Person person = new Person(i, "name" + i, 18);
persons.add(person);
}
List<Person> personList2 = persons.stream()
.limit(2)
.sorted(Comparator.comparing(Person::getName).reversed())
.collect(Collectors.toList());
System.out.println(personList2);
}
}

// 结果:
/*
[Person(Id=2, name=name2, age=18), Person(Id=1, name=name1, age=18)]
*/

8. min/max/distinct: min 和 max 的功能也可以通过对 Stream 元素先排序,再 findFirst 来实现,但前者的性能会更好,为 O(n),而 sorted 的成本是 O(n log n)。同时它们作为特殊的 reduce 方法被独立出来也是因为求最大最小值是很常见的操作。

eg:取年龄最大/最小的对象,取出其age并去重后返回。

public class MainTest {
public static void main(String[] args) {
List<Person> persons = new ArrayList();
for (Long i = 1L; i <= 5; i++) {
Person person = new Person(i, "name" + i, 18 + i.intValue());
persons.add(person);
}
int maxAge = persons.stream()
.mapToInt(Person::getAge)
.distinct()
// .min()
.max().getAsInt();
System.out.println(maxAge);
}
}

// 结果:
/*
23
*/

9. Match:Stream 有三个 match 方法,从语义上说:


  • allMatch:Stream 中全部元素符合传入的 predicate,返回 true
  • anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
  • noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true

List<Person> persons = new ArrayList();
persons.add(new Person(1, "name" + 1, 10));
persons.add(new Person(2, "name" + 2, 21));
persons.add(new Person(3, "name" + 3, 34));
persons.add(new Person(4, "name" + 4, 6));
persons.add(new Person(5, "name" + 5, 55));
boolean isAllAdult = persons.stream().
allMatch(p -> p.getAge() > 18);
System.out.println("All are adult? " + isAllAdult);
boolean isThereAnyChild = persons.stream().
anyMatch(p -> p.getAge() < 12);
System.out.println("Any child? " + isThereAnyChild);

// 结果:
/*
All are adult? false
Any child? true
*/

五、进阶操作:自己生成流

1. Stream.generate:通过实现 Supplier 接口,你可以自己来控制流的生成。

eg:生成 10 个随机整数。

Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
//Another way
IntStream.generate(() -> (int) (System.nanoTime() % 100)).
limit(10).forEach(System.out::println);

2. Stream.iterate: iterate 跟 reduce 操作很像,接受一个种子值,和一个 UnaryOperator(例如 f)。然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,以此类推。

Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));

// 结果:
/*
0 3 6 9 12 15 18 21 24 27
*/

3. 用 Collectors 来进行 reduction 操作: java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组

groupingBy/partitioningBy:

eg: 按照年龄归组

Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()).
limit(100).
collect(Collectors.groupingBy(Person::getAge));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
}

// 结果:
/*
Age 0 = 2
Age 1 = 2
Age 5 = 2
Age 8 = 1
Age 9 = 1
Age 11 = 2
……
*/

eg: 按照未成年人和成年人归组。

Map<Boolean, List<Person>> children = Stream.generate(new PersonSupplier()).
limit(100).
collect(Collectors.partitioningBy(p -> p.getAge() < 18));
System.out.println("Children number: " + children.get(true).size());
System.out.println("Adult number: " + children.get(false).size());

// 结果:
/*
Children number: 23
Adult number: 77
……
*/

六、总结

首先谢谢大家的阅读,希望对阅读的人有所帮助,欢迎交流,githup、gitee(yysimple),有很多源码,大家可以fork、star。

Stream只是更加便利于集合的操作,但也有很多条件,比如参数必须 是lambda表达式等。

再次谢谢大家的阅读!