文章目录
- 一、基本概念
- 二、创建流
- 三、中间操作
- 3.1 filter
- 3.2 map
- 3.3 distinct
- 3.4 sorted
- 3.5 limit
- 3.6 skip
- 3.7 flatMap
- 四、终结操作
- 4.1 forEach
- 4.2 count
- 4.3 max&min
- 4.4 collect
- 4.4 查找与匹配
- 4.5 reduce
- 练习
一、基本概念
为了能够让工程师更方便地处理集合数据,Java8 中新增了一大特性,那就是 Stream (也就是流,也叫做流式计算)。利用新提供的 Stream API,可以让 Java 以声明性地迭代方式处理集合,它也类似于真正的流水线生产那样:流 = 工序 + 节点 + 数据。
流的注意事项:
- 惰性求值:如果没有终结操作,中间操作是不会执行的
- 流是一次性的:一旦一个流对象经过一个终结操作之后,这个流就不能再使用
- 不会影响原数据:我们可以在流中对数据做很多处理,但是正常情况下是不会影响原来集合中的元素的
为了更直观的比对传统集合或者数组操作的方式以及流式计算的方式,做了如下的准备工作:
定义Author类:
package StreamAPI;
import java.util.List;
@EqualsAndHashCode
public class Author {
private long id;
private String name;
private Integer age;
private String intro;
private List<Book> books;
public Author(long id, String name, Integer age, String intro, List<Book> books) {
this.id = id;
this.name = name;
this.age = age;
this.intro = intro;
this.books = books;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
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 String getIntro() {
return intro;
}
public void setIntro(String intro) {
this.intro = intro;
}
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Author author = (Author) o;
return id == author.id && Objects.equals(name, author.name) && Objects.equals(age, author.age) && Objects.equals(intro, author.intro) && Objects.equals(books, author.books);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age, intro, books);
}
定义Book类:
package StreamAPI;
@EqualsAndHashCode
public class Book {
private long id;
private String name;
private String category;
private Integer score;
private String intro;
public Book(long id, String name, String category, Integer score, String intro) {
this.id = id;
this.name = name;
this.category = category;
this.score = score;
this.intro = intro;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return id == book.id && Objects.equals(name, book.name) && Objects.equals(category, book.category) && Objects.equals(score, book.score) && Objects.equals(intro, book.intro);
}
@Override
public int hashCode() {
return Objects.hash(id, name, category, score, intro);
}
}
测试类:
package StreamAPI;
import com.company.Task4_3;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class StreamDemo {
public static void main(String[] args) {
}
private static List<Author> getAuthors(){
//数据初始化
Author author = new Author(1L,"蒙多",33,"一个从菜刀中悟哲理的祖安人",null);
Author author2 = new Author(2L,"亚拉索",15,"狂风也追逐不上他的思考速度",null);
Author author3 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
Author author4 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
//书籍列表
List<Book> books1 = new ArrayList<>();
List<Book> books2 = new ArrayList<>();
List<Book> books3 = new ArrayList<>();
books1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情",88,"用一把刀分割了爱恨"));
books1.add(new Book(2L,"一个人不能死在同一把刀下","个人成长,爱情",99,"讲述失恶"));
books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"哲学思想"));
books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"哲学思想"));
books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"哲学思想"));
books2.add(new Book(4L,"吹或不吹","爱情,个人传记",56,"哲学"));
books3.add(new Book(5L,"你的剑就是我的剑","爱情",56,"剑"));
books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家"));
books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家"));
author.setBooks(books1);
author2.setBooks(books2);
author3.setBooks(books3);
author4.setBooks(books3);
List<Author> authorList = new ArrayList<>(Arrays.asList(author,author2,author3,author4));
return authorList;
}
}
现在要获取打印年龄小于18岁的作家的名字,利用StreamAPI我们可以采用以下的方式进行打印:
public static void main(String[] args) {
List<Author> authors = getAuthors();
authors.stream()//把集合转换成流
.distinct()//去除重复元素
.filter(author->author.getAge()<18) //过滤符合条件的作者
.forEach(author->System.out.println(author.getName()));
}
二、创建流
单列集合:集合对象.stream()
List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();
数组:Arrays.stream(数组)或者Stream.of来创建
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
双列集合:转换成单列集合再创建
Map<String,Integer> map = new HashMap<>();
map.put("蜡笔小新",19);
map.put("黑子",17);
map.put("日向翔阳",16);
Stream<Map.Entry<String,Integer>> stream = map.entrySet().stream();
三、中间操作
3.1 filter
可以对流中的元素进行条件过滤,符合过滤条件的才能够继续留在流中。
例如:输出名字长度超过1的作家名字
List<Author> authors = getAuthors();
authors.stream()//把集合转换成流
.filter(author->author.getName().length()>1)
.forEach(author->System.out.println(author.getName()));
3.2 map
可以对流中的元素进行计算或转换
如:打印所有作家的名字,或者为了方便之后的操作,将集合转换为字符串类型
List<Author> authors = getAuthors();
authors.stream()
.map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
})
.forEach(s->System.out.println(s));
3.3 distinct
可以去除流中重复的元素,是依赖Object的equals方法来判断是否是相同对象的。所有要注意需要重写equals方法
如打印所有作家的名字,其中不能有重复元素
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.forEach(author->System.out.println(author.getName()));
3.4 sorted
可以对流中的元素进行排序,要实现Comparable接口
如对流中的元素按照年龄进行降序排列,并且要求不能有重复的元素。
第一种方式:调用无参的sorted()方法,因为要实现Comparable接口,因此先修改Author类的代码:
package StreamAPI;
import lombok.EqualsAndHashCode;
import java.util.List;
import java.util.Objects;
@EqualsAndHashCode
public class Author implements Comparable<Author> {
private long id;
private String name;
private Integer age;
private String intro;
private List<Book> books;
@Override
public int compareTo(Author o) {
return o.getAge() - this.getAge();
}
}
再对元素按照年龄降序排列:
public static void main(String[] args) {
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted()
.forEach(author->System.out.println(author.getName()));
}
第二种:直接调用有参的sorted()方法,不需要实现Comparable接口:
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted((o1,o2)->o2.getAge() - o1.getAge())
.forEach(author->System.out.println(author.getName()));
3.5 limit
可以设置流的最大长度,超出的部分将被抛弃
如对流中的元素按照年龄降序排列,并且要求不能有重复的元素,然后打印其中年龄最大的两个元素
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted((o1,o2)->o2.getAge() - o1.getAge())
.limit(2)
.forEach(author->System.out.println(author.getName()));
3.6 skip
跳过流中的n个元素,返回剩下的元素
如打印出了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排列
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted((o1,o2)->o2.getAge() - o1.getAge())
.skip(1)//跳过第一个
.forEach(author->System.out.println(author.getName()));
3.7 flatMap
map只能把一个对象转换成另一个对象在作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素
如:打印所有书籍的名字,要求对重复元素进行去重
List<Author> authors = getAuthors();
authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.forEach(book->System.out.println(book.getName()));
如:打印现有数据的所有分类,要求对分类进行去重,不能出现这种格式:哲学,爱情
List<Author> authors = getAuthors();
authors.stream()
.flatMap(author->author.getBooks().stream())
.distinct()
.flatMap(book->Arrays.stream(book.getCategory().split(",")))
.distinct()
.forEach(category->System.out.println(category));
四、终结操作
4.1 forEach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体操作
4.2 count
可以用来获取当前流中的元素个数
如:打印这些作家的所有书籍的数目,注意删除重复元素
List<Author> authors = getAuthors();
long count = authors.stream()
.flatMap(author->author.getBooks().stream())
.distinct()
.count();
System.out.println(count);
4.3 max&min
可以获取流中的最值
如:获取这些作家书籍的最高分和最低分
Stream< Author > --> Stream< Book > --> Stream< Integer >(分数的类型是int)
List<Author> authors = getAuthors();
Optional<Integer> max = authors.stream()
.flatMap(author->author.getBooks().stream())
.map(book->book.getScore())
.max((score1,score2)->score1-score2);
Optional<Integer> min = authors.stream()
.flatMap(author->author.getBooks().stream())
.map(book->book.getScore())
.min((score1,score2)->score1-score2);
System.out.println(min.get());
System.out.println(max.get());
4.4 collect
把当前流转换成一个集合
如:获取一个存放所有作者名字的List集合
List<Author> authors = getAuthors();
List<String> nameList = authors.stream()
.map(author->author.getName())
.collect(Collectors.toList());
System.out.println(nameList);
如:获取一个所有书名的Set集合
Set<Book> books = authors.stream()
.flatMap(author->author.getBooks().stream())
.collect(Collectors.toSet());
System.out.println(books);
如:获取一个Map集合,map的key为作者名,value为List< Book >
Map<String,List<Book>> map = authors.stream()
.distinct()
.collect(Collectors.toMap(author->author.getName(),author->author.getBooks()));
4.4 查找与匹配
anyMatch: 用来判断是否有任意符合匹配条件的元素,结果为boolean类型
如:判断是否有年龄29以上的作家
List<Author> authors = getAuthors();
boolean flag = authors.stream()
.anyMatch(author->author.getAge()>29);
System.out.println(flag);
allMatch: 用来判断是否都符合匹配条件
noneMatch: 判断是否都不符合匹配条件
findAny: 获取流中任意一个元素,该方法没有办法保证获取的一定是流中的哪一个元素
findFirst: 获取流中的第一个元素
4.5 reduce
将stream流中的元素组合起来,我们可以传入一个初始值,它会按照我们的计算方式一次拿流中的元素和在初始化值的基础上进行计算,计算结果再和后面的元素计算。
它内部的计算方式如下:
T result = identity;
for(T element : this stream)
result = accumulator.apply(result,element)
return result;
其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的
如:求所有作者年龄之和
List<Author> authors = getAuthors();
Integer sum = authors.stream()
.distinct()
.map(author->author.getAge())
.reduce(0,(result,element)->result+element);//初始值为0,result为最终变量,element为遍历的元素值
System.out.println(sum);
练习
现有Employee实体类:
package StreamAPI;
public class Employee {
public enum Type {MANAGER,SELLER,OFFICER};
private String name;
private String genger;
private Integer age;
private boolean married;
private Type type;
public Employee(String name, String genger, Integer age, boolean married, Type type) {
this.name = name;
this.genger = genger;
this.age = age;
this.married = married;
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGenger() {
return genger;
}
public void setGenger(String genger) {
this.genger = genger;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public boolean isMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", genger='" + genger + '\'' +
", age=" + age +
", married=" + married +
", type=" + type +
'}';
}
}
Company实体类:
package StreamAPI;
import java.util.List;
public class Company<T> {
public enum Type{BIG,SMALL};
private String name;
private Type type;
private List<T> employee;
public Company(String name, Type type, List<T> employee) {
this.name = name;
this.type = type;
this.employee = employee;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public List<T> getEmployee() {
return employee;
}
public void setEmployee(List<T> employee) {
this.employee = employee;
}
@Override
public String toString() {
return "Company{" +
"name='" + name + '\'' +
", type=" + type +
", employee=" + employee +
'}';
}
}
再给集合插入以下数据:
List<Company<Employee>> list = new ArrayList<>();
List<Employee> employees = Arrays.asList(
new Employee("张勇","男",28,true,Employee.Type.MANAGER),
new Employee("李强","男",22,false,Employee.Type.SELLER),
new Employee("王五","男",32,false,Employee.Type.SELLER),
new Employee("梅丽","女",26,true,Employee.Type.OFFICER),
new Employee("郑帅","男",29,false,Employee.Type.OFFICER),
new Employee("曾美","女",27,true,Employee.Type.SELLER),
new Employee("郝俊","男",22,true,Employee.Type.SELLER),
new Employee("方圆","女",24,false,Employee.Type.SELLER)
);
Company<Employee> moubao = new Company<Employee>("某宝",Company.Type.BIG,employees);
employees = Arrays.asList(
new Employee("吴琼","女",27,true,Employee.Type.SELLER),
new Employee("陈辰","女",28,false,Employee.Type.OFFICER),
new Employee("刘能","男",35,true,Employee.Type.OFFICER),
new Employee("周七","男",29,false,Employee.Type.OFFICER),
new Employee("汪旺","男",21,false,Employee.Type.OFFICER),
new Employee("胡涂","男",27,false,Employee.Type.OFFICER),
new Employee("杨茂","男",34,true,Employee.Type.MANAGER),
new Employee("朱坚","男",30,false,Employee.Type.MANAGER)
);
Company<Employee> mouxin = new Company<Employee>("某东",Company.Type.BIG,employees);
employees = Arrays.asList(
new Employee("冯过","男",35,false,Employee.Type.SELLER),
new Employee("何花","女",27,false,Employee.Type.MANAGER),
new Employee("卫精","男",25,true,Employee.Type.OFFICER),
new Employee("施工","男",28,false,Employee.Type.OFFICER),
new Employee("沈月","女",24,false,Employee.Type.OFFICER),
new Employee("乐欢","女",22,false,Employee.Type.OFFICER),
new Employee("安全","男",33,true,Employee.Type.MANAGER),
new Employee("林森","男",26,true,Employee.Type.SELLER)
);
Company<Employee> wahaha = new Company<Employee>("某哈哈",Company.Type.SMALL,employees);
//加入列表
list.add(moubao);
list.add(mouxin);
list.add(wahaha);
实现如下需求:统计公司类型为 BIG 的所有未婚员工,同时按年龄排序,并且未婚员 工不区分公司,也就是最终结果放在一个列表中(提示:整个需求可以用一行代码完成)
List<Employee> employeeList = list.stream()
.filter(conpany->conpany.getType() == Company.Type.BIG)
.flatMap(company->company.getEmployee().stream())
.filter(employee -> employee.isMarried() == false)
.sorted((o1,o2)->o1.getAge() - o2.getAge() )
.collect(Collectors.toList());
for(Employee e : employeeList)
System.out.println(e.toString());