Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API( java.util.stream .*) 。
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
一、什么是Stream?
流(Stream)到底是什么呢:是数据渠道,用于操作数据源(集合、数组等)所生成的元素。集合讲的是数据,流讲的是计算。
需要注意的是:
1.Stream自己不会存储元素
2.Stream不会改变源对象,相反他们会返回一个持有结果的新Stream
3.Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行
Stream的操作分三个步骤:
1.创建Stream:通过一个数据源(如集合、数组)获取流
2.中间操作:一个中间操作链,对数据源的数据进行处理
3.终止操作(终端操作):一个终止操作,执行中间操作链并产生结果
二、Stream的创建
2.1 通过Collection系列集合提供的stream()(串行流)或parallelStream()(并行流)
List list = new ArrayList<>();
Stream stream1 = list.stream();
2.2 通过Arrays中的静态方法stream()获取数组流
Employee[] emps = new Employee[10];
Stream stream2 = Arrays.stream(emps);
他还有重载形式,能够处理对应的基本类型的数组:
2.3 由值创建流:通过Stream类中的静态方法of(),可以接受任意数量的参数
Stream stream3 = Stream.of("aa", "bb", "cc");
2.4 由函数创建:创建无限流
方式一:迭代
Stream stream4 = Stream.iterate(0, (x) -> x + 2);
stream4.forEach(System.out::println);
方式二:生成
Stream.generate(() -> Math.random()).forEach(System.out::println);
三、中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理称为"惰性求值"。
讲解之前我们先创建一个集合,添加员工信息:
List employees = Arrays.asList(
new Employee("张三", 18, 9999.99),
new Employee("李四", 58, 5555.99),
new Employee("王五", 26, 3333.33),
new Employee("赵六", 36, 6666.66),
new Employee("田七", 12, 8888.88),
new Employee("田七", 12, 8888.88),
new Employee("田七", 12, 8888.88)
);
3.1 筛选与切片:
3.1.1 filter-接收Lambda,从流中排除某些元素
例:获取年龄超过35岁的员工
@Test
public void test1(){
//中间操作:如果没有终止操作不会执行任何操作
Stream stream = employees.stream()
.filter((e) -> {//内部迭代:迭代操作由Stream API完成
System.out.println("Stream API的中间操作");//如果没有下面的终止操作这里不会打印输出
return e.getAge() > 35;
});
//终止操作:一次性执行全部内容即"惰性求值"
stream.forEach(System.out::println);
}
3.1.2 limit-截断流,使其元素不超过给定数量
例:获取工资超过5000的员工,取结果的前两位
@Test
public void test2(){
//中间操作:不会执行任何操作
employees.stream()
.filter((e) -> {//只要找到满足条件的结果后后续的迭代操作将不再继续执行
System.out.println("我会输出么");
return e.getSalary() > 5000;
})
.limit(2)
.forEach(System.out::println);
}
3.1.3 skip(n)-跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
例:获取工资超过5000的员工结果并去掉结果的前两位
@Test
public void test3(){
//中间操作:不会执行任何操作
employees.stream()
.filter((e) -> {
System.out.println("我会输出么");
return e.getSalary() > 5000;
})
.skip(2)
.forEach(System.out::println);
}
3.1.4 distinct-筛选,通过流所生成元素的hashCode()和equals()去除重复元素
例:
@Test
public void test4(){
//中间操作:不会执行任何操作
employees.stream()
.filter((e) -> {
System.out.println("我会输出么");
return e.getSalary() > 5000;
})
.distinct()
.forEach(System.out::println);
}
3.2 映射
3.2.1 map-接收Lambda,将元素转换成其他形式或提取信息.接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新元素
@Test
public void test5(){
List list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
//转大写
list.stream()
.map((str) -> str.toUpperCase())
.forEach(System.out::println);
//提取名字
employees.stream()
.map((Employee::getName))
.forEach(System.out::println);
}
3.2.2 flatMap-接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
例:注意测试类为TestStreamMiddleOperation2
//解析字符串,将字符串中的字符一个个提取出来放到集合中
public static Stream filterCharacter(String str) {
List list = new ArrayList<>();
for (Character ch : str.toCharArray()) {
list.add(ch);
}
return list.stream();
}
@Test
public void test6(){
List list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
Stream> stream = list.stream()
.map(TestStreamMiddleOperation2::filterCharacter);//{{a,a,a},{b,b,b}}
stream.forEach((s) -> s.forEach(System.out::println));
System.out.println("----------------------");
Stream sm = list.stream()
.flatMap(TestStreamMiddleOperation2::filterCharacter);//{a,a,a,b,b,b}
sm.forEach(System.out::println);
}
3.3 排序
List
3.3.1 sorted()-自然排序(Comparable)
list.stream()
.sorted()
.forEach(System.out::println);
3.3.2 sorted(Comparator com)-定制排序
employees.stream()
.sorted((e1, e2) -> {
if (e1.getAge() == e2.getAge()) {
return e1.getName().compareTo(e2.getName());
}
return Integer.compare(e1.getAge(), e2.getAge());
})
.forEach(System.out::println);
四、终止操作:终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
创建一个表示员工信息的集合并添加数据|:
List employees = Arrays.asList(
new Employee("张三", 18, 9999.99, Employee.Status.FREE),
new Employee("李四", 58, 5555.99, Employee.Status.BUSY),
new Employee("王五", 26, 3333.33, Employee.Status.VOCATION),
new Employee("赵六", 36, 6666.66, Employee.Status.FREE),
new Employee("田七", 12, 8888.88, Employee.Status.BUSY)
);
Employee类:
public class Employee {
private int id;
private String name;
private int age;
private double salary;
private Status status;
public Employee() {
}
public Employee(int id) {
this.id = id;
}
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee(String name, int age, double salary, Status status) {
this.name = name;
this.age = age;
this.salary = salary;
this.status = status;
}
public Employee(Integer id, Integer age) {
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int 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 String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
", status=" + status +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return id == employee.id &&
age == employee.age &&
Double.compare(employee.salary, salary) == 0 &&
Objects.equals(name, employee.name) &&
status == employee.status;
}
@Override
public int hashCode() {
return Objects.hash(id, name, age, salary, status);
}
public enum Status {
FREE,
BUSY,
VOCATION
}
}
4.1 查找与匹配
allMatch-检查是否匹配所有元素
anyMatch-检查是否至少匹配一个元素
noneMatch-检查是否没有匹配所有元素
findFirst-返回第一个元素
findAny-返回当前流中的任意元素
count-返回流中元素的总个数
max-返回流中最大只
min-返回流中最小值
使用方式:
@Test
public void test1() {
boolean b1 = employees.stream()
.allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b1);
boolean b2 = employees.stream()
.anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b2);
boolean b3 = employees.stream()
.noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b3);
//如果返回的值有可能为空,则会把该值封装到Optional容器中去
Optional optional1 = employees.stream()
// .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.sorted(Comparator.comparing(Employee::getSalary))
.findFirst();
System.out.println(optional1.get());
Optional optional2 = employees.parallelStream()
.filter((e) -> e.getStatus().equals(Employee.Status.FREE))
.findAny();
System.out.println(optional2.get());
Long count = employees.stream().count();
System.out.println(count);
Optional max = employees.stream()
// .max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
.max(Comparator.comparing(Employee::getSalary));
System.out.println(max.get());
Optional min = employees.stream()
.map(Employee::getSalary)
.min(Double::compare);
System.out.println(min.get());
}
4.2 规约:reduce(T identity, BinaryOperator) / reduce(BinaryOperator):可以将流中元素反复结合起来得到一个值
@Test
public void test2(){
List list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
//计算List集合中数据的和
//返回Integer:因为有起始值0,返回一定不为空
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(sum);
//求员工薪资总和
//返回Optional:没有起始值,返回值可能为空(后面会介绍Optional)
Optional op = employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
}
4.3 收集:collect-将流转换为其它形式.接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
4.3.1 组函数
@Test
public void test4(){
//总数
Long count = employees.stream()
.collect(Collectors.counting());
System.out.println(count);
//工资平均值
Double avg = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avg);
//工资总和
Double sum = employees.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(sum);
//工资最大值的员工信息
Optional max = employees.stream()
.collect(Collectors.maxBy(Comparator.comparing(Employee::getSalary)));
System.out.println(max.get());
//最小工资
Optional min = employees.stream()
.map(Employee::getSalary)
.collect(Collectors.minBy(Double::compare));
System.out.println(min.get());
}
4.3.2 分组
@Test
public void test5(){
Map> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(map);
}
4.3.3 分级分组
@Test
public void test6(){
Map>> collect = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
if (e.getAge() <= 35) {
return "青年";
} else if (e.getAge() <= 50) {
return "中年";
} else {
return "老年";
}
})));
System.out.println(collect);
}
4.3.4 分区:满足条件的一个区,不满足的一个区
@Test
public void test7(){
Map> collect = employees.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() > 8000));
System.out.println(collect);
}
4.3.5 组函数的另一种方式
@Test
public void test8(){
DoubleSummaryStatistics collect = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(collect.getMax());
}
4.3.6 字符串连接
@Test
public void test9(){
String collect1 = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining());
String collect2 = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(","));
String collect3 = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(",", "==", "---"));
System.out.println(collect1);
}
五、练习
练习一:
public class TestStreamPractice4 {
List employees = Arrays.asList(
new Employee("张三", 18, 9999.99, Employee.Status.FREE),
new Employee("李四", 58, 5555.99, Employee.Status.BUSY),
new Employee("王五", 26, 3333.33, Employee.Status.VOCATION),
new Employee("赵六", 36, 6666.66, Employee.Status.FREE),
new Employee("田七", 12, 8888.88, Employee.Status.BUSY)
);
/**
* 1.给定一个数字列表,如何返回一个由每个数的平方构成的列表呢
* 给定[1,2,3,4,5],返回[1,4,9,16,25]
*/
@Test
public void test1() {
Integer[] nums = new Integer[]{1, 2, 3, 4, 5};
Arrays.stream(nums)
.map((x) -> x * x)
.forEach(System.out::println);
}
/**
* 2.怎样用map和reduce方法数一数流中有多少个Employee呢
*/
@Test
public void test2(){
Optional sum = employees.stream()
.map((e) -> 1)
.reduce(Integer::sum);
System.out.println(sum.get());
}
}
练习二:
给定两个类:
交易员类:
public class Trader {
private String name;
private String city;
public Trader() {
}
public Trader(String name, String city) {
this.name = name;
this.city = city;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Trader [name=" + name + ", city=" + city + "]";
}
}
交易类:
public class Transaction {
private Trader trader;
private int year;
private int value;
public Transaction() {
}
public Transaction(Trader trader, int year, int value) {
this.trader = trader;
this.year = year;
this.value = value;
}
public Trader getTrader() {
return trader;
}
public void setTrader(Trader trader) {
this.trader = trader;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
@Override
public String toString() {
return "Transaction [trader=" + trader + ", year=" + year + ", value="
+ value + "]";
}
}
测试类:
public class TestTransaction {
List transactions = null;
@Before
public void before(){
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");
transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
}
//1. 找出2011年发生的所有交易, 并按交易额排序(从低到高)
@Test
public void test1(){
transactions.stream()
.filter((t) -> t.getYear() == 2011)
// .sorted((t1, t2) -> Integer.compare(t1.getValue(), t2.getValue()))
.sorted(Comparator.comparing(Transaction::getValue))
.forEach(System.out::println);
}
//2. 交易员都在哪些不同的城市工作过?
@Test
public void test2(){
transactions.stream()
.map((t) -> t.getTrader().getCity())
.distinct()
.forEach(System.out::println);
}
//3. 查找所有来自剑桥的交易员,并按姓名排序
@Test
public void test3(){
transactions.stream()
.filter((t) -> t.getTrader().getCity().equals("Cambridge"))
.map(Transaction::getTrader)
.sorted((t1, t2) -> t1.getName().compareTo(t2.getName()))
.distinct()
.forEach(System.out::println);
}
//4. 返回所有交易员的姓名字符串,按字母顺序排序
@Test
public void test4(){
transactions.stream()
.map((t) -> t.getTrader().getName())
.sorted()
.forEach(System.out::println);
System.out.println("-----------------------------------");
String str = transactions.stream()
.map((t) -> t.getTrader().getName())
.sorted()
.reduce("", String::concat);
System.out.println(str);
System.out.println("------------------------------------");
transactions.stream()
.map((t) -> t.getTrader().getName())
.flatMap(TestTransaction::filterCharacter)
.sorted((s1, s2) -> s1.compareToIgnoreCase(s2))
.forEach(System.out::print);
}
public static Stream filterCharacter(String str){
List list = new ArrayList<>();
for (Character ch : str.toCharArray()) {
list.add(ch.toString());
}
return list.stream();
}
//5. 有没有交易员是在米兰工作的?
@Test
public void test5(){
boolean bl = transactions.stream()
.anyMatch((t) -> t.getTrader().getCity().equals("Milan"));
System.out.println(bl);
}
//6. 打印生活在剑桥的交易员的所有交易额
@Test
public void test6(){
Optional sum = transactions.stream()
.filter((e) -> e.getTrader().getCity().equals("Cambridge"))
.map(Transaction::getValue)
.reduce(Integer::sum);
System.out.println(sum.get());
}
//7. 所有交易中,最高的交易额是多少
@Test
public void test7(){
Optional max = transactions.stream()
.map((t) -> t.getValue())
.max(Integer::compare);
System.out.println(max.get());
}
//8. 找到交易额最小的交易
@Test
public void test8(){
Optional op = transactions.stream()
.min((t1, t2) -> Integer.compare(t1.getValue(), t2.getValue()));
System.out.println(op.get());
}
}