Java8新特性 - Stream API

时间:2023-03-09 19:26:12
Java8新特性 - Stream API

Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合进行操作,就类似与使用SQL执行的数据库操作。也可以使用Stream API来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

什么是Stream

是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列

集合讲的是数据,流讲的是计算

PS:

  • Stream自己不会存储元素
  • Stream不会改变源对象,相反,他们会返回一个持有结果的新Stream
  • Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行

下面的图可以比较直观的反映这一过程:

Java8新特性 - Stream API

  • 创建Stream

    一个数据源(数组、集合等),获取一个流
  • 中间操作

    一个中间操作链,对数据源的数据进行处理
  • 终止操作(终端操作)

    一个终止操作,执行中间操作链,并产生结果

创建Stream

通过Collection系列集合提供的stream()或parallelStream()

Java8中的Collection接口被扩展,提供了两个获取流的方法:

  • default Stream stream():返回一个顺序流
  • default Stream parallelStream():返回一个并行流

示例代码:

List<Integer> list = new ArrayList<>();
Stream<Integer> stream1 = list.stream();
Stream<Integer> stream2 = list.parallelStream();

由数组创建流

Java8的Arrays的静态方法stream()可以获取数据流

  • static Stream stream(T[] arrays):返回一个流

示例代码:

Integer[] integers = new Integer[10];
Stream<Integer> stream = Arrays.stream(integers);

由值创建流

通过Stream类中的静态方法of(),通过显示值创建一个流,可以接收任意数量的参数

  • public static Stream of(T ... values):返回一个流

示例代码:

Stream<Integer> stream = Stream.of(1, 2, 3);

创建无限流

使用静态方法Stream.iterate()和Stream.generate(),创建无限流

  • 迭代:public static Stream iterate(final T seed, final UnaryOperator f)
  • 生成:public static Stream generate(Supplier s)

示例代码:

// 迭代
Stream stream1 = Stream.iterate(0, (x) -> x + 2);
// 生成
Stream stream2 = Stream.generate(() -> Math.random());

中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称之为“惰性求值”

筛选与切片

  • filter:结合搜lambda,从流中排除元素
  • limit:截断流,使其元素不超过给定数量
  • skip(n):跳过元素,返回一个删除了前n个元素的流;若流中元素不足n个,则返回一个空流;与limit(n)互补
  • distinct:筛选,通过流所生成的元素的hashCode()和equals()去除重复元素

示例代码:

public class TestStreamApi {
private static List<Demo> demoList = Arrays.asList(
new Demo(1, "哈哈哈"),
new Demo(2, "嘿嘿嘿嘿"),
new Demo(3, "呵呵呵"),
new Demo(4, "恩恩恩恩"),
new Demo(5, "哼哼哼"),
new Demo(6, "啧啧啧"),
new Demo(5, "哼哼哼"),
new Demo(8, "哼")
); public static void main(String[] args) {
// 中间操作不会执行任何操作
Stream<Demo> demoStream = demoList.stream()
.filter((x) -> x.getRemark().length() == 3)
.limit(4)
.skip(1)
.distinct(); // 终止操作一次性执行全部内容
// 内部迭代:迭代操作由Stream API完成
demoStream.forEach(System.out::println);
}
}

运行结果:

3-呵呵呵

5-哼哼哼

6-啧啧啧

注意:distinct筛选通过流所生成的元素的hashCode()和equals()去除重复元素,所以需要重写Demo的hashCode()和equals()方法。

映射

  • map:接收Lambda,将元素转换成其它形式或提取信息;接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有的流连接成一个流

示例代码:

public class TestStreamApi {
private static List<Demo> demoList = Arrays.asList(
new Demo(1, "哈哈哈"),
new Demo(2, "嘿嘿嘿嘿")
); public static void main(String[] args) {
demoList.stream()
.map(Demo::getRemark)
.flatMap(TestStreamApi :: filterCharacter)
.forEach(System.out::println);
} public static Stream<Character> filterCharacter(String str) {
List<Character> list = new ArrayList<>();
for (Character c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
}

运行结果:













排序

  • sorted():自然排序
  • sorted(Comparator c):定制排序

示例代码:

public class TestStreamApi {
private static List<Demo> demoList = Arrays.asList(
new Demo(5, "哈哈哈"),
new Demo(2, "嘿嘿嘿嘿"),
new Demo(3, "呵呵呵"),
new Demo(2, "哼哼哼"),
new Demo(5, "啧啧啧")
); public static void main(String[] args) {
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
list.stream()
.sorted()
.forEach(System.out::println);
System.out.println("----------");
demoList.stream()
.sorted((x, y) -> {
if (x.getNum().equals(y.getNum())) {
return x.getRemark().compareTo(y.getRemark());
} else {
return x.getNum().compareTo(y.getNum());
}
})
.forEach(System.out::println);
}
}

运行结果:

aaa

bbb

ccc

2-哼哼哼

2-嘿嘿嘿嘿

3-呵呵呵

5-哈哈哈

5-啧啧啧

终止操作

查找与匹配

  • allMatch:检查是否匹配所有元素
  • anyMatch:检查是否匹配所有元素
  • noneMatch:检查是否没有匹配所有元素
  • findFirst:返回第一个元素
  • findAny:返回当前流中的任意元素
  • count:返回流中元素的总个数
  • max:返回流中的最大值
  • min:返回流中的最小值

示例代码:

public class TestStreamApi2 {
private static List<Demo> demoList = Arrays.asList(
new Demo("张三", 18, 6666.66, Demo.Status.BUSY),
new Demo("李四", 38, 3333.33, Demo.Status.FREE),
new Demo("王五", 28, 5555.55, Demo.Status.FREE),
new Demo("赵六", 48, 7777.77, Demo.Status.BUSY),
new Demo("王二麻子", 58, 8888.88, Demo.Status.VOCATION) ); public static void main(String[] args) {
// 是不是所有的对象都处于BUSY状态
System.out.println(demoList.stream()
.allMatch((d) -> d.getStatus().equals(Demo.Status.BUSY)));
// 是否有对象处于BUSY状态
System.out.println(demoList.stream()
.anyMatch((d) -> d.getStatus().equals(Demo.Status.BUSY)));
// 是否没有对象处于BUSY状态
System.out.println(demoList.stream()
.noneMatch((d) -> d.getStatus().equals(Demo.Status.BUSY)));
// 获取工资最高的
Optional<Demo> optionalDemo1 = demoList.stream()
.sorted((x, y) -> -Double.compare(x.getSalary(), y.getSalary()))
.findFirst();
System.out.println(optionalDemo1.get());
// 获取随机一个空闲的
Optional<Demo> optionalDemo2 = demoList.stream()
.filter((e) -> e.getStatus().equals(Demo.Status.FREE))
.findAny();
System.out.println(optionalDemo2.get());
// 总数
System.out.println(demoList.stream().count());
// 工资最高的
Optional<Demo> optionalDemo3 = demoList.stream()
.max((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
System.out.println(optionalDemo3.get());
// 最小的工资
Optional<Double> optionalDemo4 = demoList.stream()
.map(Demo::getSalary)
.max(Double::compare);
System.out.println(optionalDemo4.get());
}
} class Demo{
// 姓名
String name;
// 年龄
Integer age;
// 工资
Double salary;
// 状态
Status status; public Demo() {} public Demo(String name, Integer age, Double salary, Status status) {
this.name = name;
this.age = age;
this.salary = salary;
this.status = status;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} public Double getSalary() {
return salary;
} public void setSalary(Double salary) {
this.salary = salary;
} public Status getStatus() {
return status;
} public void setStatus(Status status) {
this.status = status;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Demo demo = (Demo) o;
return name.equals(demo.name) &&
age.equals(demo.age) &&
salary.equals(demo.salary) &&
status == demo.status;
} @Override
public int hashCode() {
return Objects.hash(name, age, salary, status);
} @Override
public String toString() {
return "Demo{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
", status=" + status +
'}';
} public enum Status{
FREE,
BUSY,
VOCATION
}
}

运行结果:

false

true

false

Demo{name='王二麻子', age=58, salary=8888.88, status=VOCATION}

Demo{name='李四', age=38, salary=3333.33, status=FREE}

5

Demo{name='王二麻子', age=58, salary=8888.88, status=VOCATION}

8888.88

归约

  • reduce(T identify, BinaryOperator) / reduce(BinaryOperator):可以将流中元素反复结合起来,得到一个值

示例代码:

public class TestStreamApi3 {
private static List<Demo> demoList = Arrays.asList(
new Demo("张三", 18, 6666.66, Demo.Status.BUSY),
new Demo("李四", 38, 3333.33, Demo.Status.FREE),
new Demo("王五", 28, 5555.55, Demo.Status.FREE),
new Demo("赵六", 48, 7777.77, Demo.Status.BUSY),
new Demo("王二麻子", 58, 8888.88, Demo.Status.VOCATION)
); public static void main(String[] args) {
Optional<Double> optional = demoList.stream()
.map(Demo::getSalary)
.reduce(Double::sum);
System.out.println(optional.get());
}
}

运行结果:

32222.190000000002

收集

  • collect:将流转换为其他形式。接收一个Collection接口的实现,用于给Stream中元素做汇总的方法

Collectors接口中方法的实现决定了如何对流执行收集操作(如搜集到List、Set、Map)。

  • toList:把流中元素收集到List
  • toSet:把流中元素收集到Set
  • toCollection:把流中元素收集到创建的集合
  • counting:计算流中元素的个数
  • summingInt:对流中元素的整数属性求和
  • averagingInt:计算流中元素Integer属性的平均值
  • summarizingInt:收集流中Integer属性的统计值
  • jioning:连接流中的每个字符串
  • maxBy:根据比较器选择最大值
  • minBy:根据比较器选择最小值
  • reducing:从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而规约成单个值
  • collectingAndThen:包裹另一个收集器,对其结果转换函数
  • groupingBy:根据某个属性值对流分组,属性为K,结果为V
  • partitioningBy:根据true、false进行分区

给定一个数组,方便测试:

    private static List<Demo> demoList = Arrays.asList(
new Demo("张三", 18, 6666.66, Demo.Status.BUSY),
new Demo("李四", 38, 3333.33, Demo.Status.FREE),
new Demo("王五", 28, 5555.55, Demo.Status.FREE),
new Demo("赵六", 48, 7777.77, Demo.Status.BUSY),
new Demo("王二麻子", 58, 8888.88, Demo.Status.VOCATION)
);

toList

示例代码:

// 收集 - toList
System.out.println("---------------->toList");
List<String> list = demoList.stream()
.map(Demo::getName)
.collect(Collectors.toList());
list.forEach(System.out::println);

运行结果:

张三

李四

王五

赵六

王二麻子

toSet

示例代码:

// 收集 - toSet
System.out.println("---------------->toSet");
Set<String> set = demoList.stream()
.map(Demo::getName)
.collect(Collectors.toSet());
set.forEach(System.out::println);

运行结果:

李四

张三

王二麻子

王五

赵六

toCollection

示例代码:

// 收集 - toCollection
System.out.println("---------------->toCollection");
HashSet<String> hashSet = demoList.stream()
.map(Demo::getName)
.collect(Collectors.toCollection(HashSet::new));
hashSet.forEach(System.out::println);

运行结果:

李四

张三

王二麻子

王五

赵六

counting

示例代码:

// 收集 - counting 计算总数
System.out.println("---------------->counting");
System.out.println(demoList.stream()
.collect(Collectors.counting()));

运行结果:

5

summingInt

示例代码:

// 收集 - summingInt 计算年龄总和
System.out.println("---------------->summingInt");
System.out.println(demoList.stream()
.collect(Collectors.summingInt(Demo::getAge)));

运行结果:

190

averagingInt

示例代码:

// 收集 - averagingInt 平均年龄
System.out.println("---------------->averagingInt");
System.out.println(demoList.stream()
.collect(Collectors.averagingInt(Demo::getAge)));

运行结果:

38.0

summarizingInt

示例代码:

// 收集 - summarizingInt
System.out.println("---------------->summarizingInt");
IntSummaryStatistics summaryStatistics = demoList.stream()
.collect(Collectors.summarizingInt(Demo::getAge));
// 最大值
System.out.println(summaryStatistics.getMax());
// 平均值
System.out.println(summaryStatistics.getAverage());
// 总和
System.out.println(summaryStatistics.getSum());

运行结果:

58

38.0

190

joining

示例代码:

// 收集 - joining 连接姓名
System.out.println("---------------->joining");
String s = demoList.stream()
.map(Demo::getName)
.collect(Collectors.joining(",", "开始->", "<-结束"));
System.out.println(s);

运行结果:

开始->张三,李四,王五,赵六,王二麻子<-结束

maxBy

示例代码:

// 收集 - maxBy 获取工资最高的人
System.out.println("---------------->maxBy");
Optional<Demo> max = demoList.stream()
.collect(Collectors.maxBy((x, y) -> Double.compare(x.getSalary(), y.getSalary())));
System.out.println(max.get());

运行结果:

Demo{name='王二麻子', age=58, salary=8888.88, status=VOCATION}

minBy

示例代码:

// 收集 - minBy 获取最低的
System.out.println("---------------->minBy");
Optional<Double> min = demoList.stream()
.map(Demo::getSalary)
.collect(Collectors.minBy(Double::compare));
System.out.println(min.get());

运行结果:

3333.33

groupingBy

示例代码:

// 收集 - groupingBy 根据状态分组
System.out.println("---------------->groupingBy");
Map<Demo.Status, List<Demo>> group = demoList.stream()
.collect(Collectors.groupingBy(Demo::getStatus));
System.out.println(group);
// 多级分组 先按状态分组,在按年龄分组
Map<Demo.Status, Map<String, List<Demo>>> group2 = demoList.stream()
.collect(Collectors.groupingBy(Demo::getStatus, Collectors.groupingBy((x) -> {
if (x.getAge() <= 30) {
return "青年";
} else if (x.getAge() <= 45) {
return "中年";
} else {
return "老年";
}
})));
System.out.println(group2);

运行结果:

{VOCATION=[Demo{name='王二麻子', age=58, salary=8888.88, status=VOCATION}], FREE=[Demo{name='李四', age=38, salary=3333.33, status=FREE}, Demo{name='王五', age=28, salary=5555.55, status=FREE}], BUSY=[Demo{name='张三', age=18, salary=6666.66, status=BUSY}, Demo{name='赵六', age=48, salary=7777.77, status=BUSY}]}

{VOCATION={老年=[Demo{name='王二麻子', age=58, salary=8888.88, status=VOCATION}]}, FREE={青年=[Demo{name='王五', age=28, salary=5555.55, status=FREE}], 中年=[Demo{name='李四', age=38, salary=3333.33, status=FREE}]}, BUSY={青年=[Demo{name='张三', age=18, salary=6666.66, status=BUSY}], 老年=[Demo{name='赵六', age=48, salary=7777.77, status=BUSY}]}}

partitioningBy

示例代码:

// 收集 - partitioningBy 分区
System.out.println("---------------->partitioningBy");
Map<Boolean, List<Demo>> partition = demoList.stream()
.collect(Collectors.partitioningBy((x) -> x.getAge() > 30));
System.out.println(partition);

运行结果:

{false=[Demo{name='张三', age=18, salary=6666.66, status=BUSY}, Demo{name='王五', age=28, salary=5555.55, status=FREE}], true=[Demo{name='李四', age=38, salary=3333.33, status=FREE}, Demo{name='赵六', age=48, salary=7777.77, status=BUSY}, Demo{name='王二麻子', age=58, salary=8888.88, status=VOCATION}]}