函数式编程Stream流(二)--Stream流
3.Stream流
3.1概述
java 8 的Stream使用的是函数式编程模式。如同他的名字一样,它可以方便的让我们对集合或数组进行链状流式的操作;
3.2案例数据准备
@Data
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class Author {
//id
private Long id;
//姓名
private String name;
//简介
private String intro;
//作品列表
private List<Book> books;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Book {
//id
private Long id;
//姓名
private String name;
//分类
private String category;
//评分
private Integer Score;
//简介
private String intro;
}
public class StreamDemo01 {
public static void main(String[] args) {
List<Author> authors = getAuthors();
System.out.println("authors = " + authors);
}
private static List<Author> getAuthors(){
//数据初始化
Author author = new Author(1L, "蒙多", "一个从菜刀中明悟这里的祖安人", null);
Author author2 = new Author(2L, "托儿所", "狂风也追逐不上他的思考速度", null);
Author author3 = new Author(3L, "易大师", "是这个时间在限制他的思维", null);
Author author4 = new Author(3L, "易大师", "是这个时间在限制他的思维", null);
//书籍列表
ArrayList<Book> book1 = new ArrayList<>();
ArrayList<Book> book2 = new ArrayList<>();
ArrayList<Book> book3 = new ArrayList<>();
ArrayList<Book> book4 = new ArrayList<>();
//
book1.add(new Book(1L, "刀的两侧是光明与黑暗", "哲学,爱情", 88, "用一把刀划分了爱恨"));
book1.add(new Book(2L, "一个人不能死在同一把刀下", "爱情,个人成长", 99, "讲述如何从失败走向成功"));
book2.add(new Book(3L, "那风吹不到的地方", "哲学", 85, "带你用思维去领略世界的尽头"));
book2.add(new Book(3L, "那风吹不到的地方", "哲学", 85, "带你用思维去领略世界的尽头"));
book2.add(new Book(4L, "吹或不吹", "爱情,个人成长", 99, "一个哲学家的联安观注定很难把他的伴侣带飞"));
book3.add(new Book(5L, "你的剑就是我的剑", "爱情", 56, "无法想象一个武者能对他的伴侣这么残忍"));
book3.add(new Book(6L, "风与剑", "个人传记", 100, "两个哲学家灵魂和肉体的碰撞会激起怎样的浪花"));
book3.add(new Book(6L, "风与剑", "个人传记", 100, "两个哲学家灵魂和肉体的碰撞会激起怎样的浪花"));
//
author.setBooks(book1);
author2.setBooks(book2);
author3.setBooks(book3);
author4.setBooks(book3);
List<Author> authorList = new ArrayList<>(Arrays.asList(author, author2, author3, author4));
return authorList;
}
}
3.3快速入门
3.3.1需求
我们可以调用getAuthors方法获取到作家的集合,现在需要打印所有年龄小于18的作家的名字,并且要注意去重
3.3.2实现
//获取所有的作家集合,
List<Author> authors = getAuthors();
//打印所有的作家
System.out.println("authors = " + authors);
//打印所有年龄小于18的作家的名字,并且要注意去重
authors.stream() //把author集合转换成Stream流
.distinct()//去去除掉重复的元素
.filter(new Predicate<Author>() {//过滤出年龄小于18岁的对象
@Override
public boolean test(Author author) {
return author.getAge() < 18;//如果 结果为TRUE 就会被留在stream 流里面
}
}).forEach(new Consumer<Author>() {//遍历stream流中的每一个元素,并进行
@Override
public void accept(Author author) {
System.out.println("author = " + author.getName());
}
});
小技巧:idea本身支持查看stream流的每个节点的数据
3.4常用操作————创建流、中间操作、终结操作
3.4.1创建流
单列集合:集合对象.stream
List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();
数组:Arrays.stream(数组)或者Stream.of来创建
Integer[] arr={1,2,3,4,5};
Stream<Integer> stream1 = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
双列集合:转换成单列集合后再创建
HashMap<String, Integer> map = new HashMap<>();
map.put("蜡笔小新",19);
map.put("黑子",17);
map.put("樱木花道",16);
Stream<Map.Entry<String, Integer>> stream3 = map.entrySet().stream();
stream3.filter(new Predicate<Map.Entry<String, Integer>>() {
@Override
public boolean test(Map.Entry<String, Integer> entry) {
return entry.getValue()>18;
}
}).forEach(new Consumer<Map.Entry<String, Integer>>() {
@Override
public void accept(Map.Entry<String, Integer> entry) {
System.out.println("entry = " + entry.getKey());
System.out.println("entry = " + entry.getValue());
}
});
3.4.2中间操作
中间操作filter
可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中。
例如:
.filter(new Predicate<Author>() {//过滤出年龄小于18岁的对象
@Override
public boolean test(Author author) {
return author.getAge() < 18;//如果 结果为true 就会被留在stream 流里面
}
})
中间操作map:可以把流中的元素进行计算或转换
例如:打印所有作家的姓名
List<Author> authors = getAuthors();
authors.stream().forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println("author = " + author.getName());
}
});
authors.stream().map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
}).forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("s = " + s);
}
});
计算操作:
authors.stream().map(new Function<Author, Integer>() {//输出所有的年龄,多此一举啊、、、
@Override
public Integer apply(Author author) {
return author.getAge();
}
}).map(new Function<Integer, Integer>() {//年龄+10岁,进行计算
@Override
public Integer apply(Integer age) {
return age + 10;
}
}).forEach(new Consumer<Integer>() {
@Override
public void accept(Integer age) {
System.out.println("age = " + age);
}
});
中间操作distinct
可以去除流中的重复元素
例如:
private static void testDistinct() {
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.forEach(author -> System.out.println("author = " + author));
}
注意:distinct 方法依赖的是Object的equals方法来判断是否相同的对象的(只比较地址值)。所以,如果stream的流中的元素是自定义对象的话,注意要重写equals方法(如果其他属性完全一样,但是地址值不同的话,那么这2个对象也是相等的)
中间操作sorted
可以对流中的元素进行排序。
例如:
对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素
private static void testSorted() {
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted(new Comparator<Author>() {
@Override
public int compare(Author o1, Author o2) {
return o1.getAge()-o2.getAge();//从小到大排序--升序
return o2.getAge()-o1.getAge();//从大到小排序--降序
}
}).forEach(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println("author = " +author.getName()+ ","+author.getAge());
}
});
}
注意:排序sorted()无参和sorted(Comparator<? super T> comparator)是2个逻辑,无参会把对象转换为Comparable对象,需要流中的对象实现Comparable接口,不然会报错
@Data
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class Author implements Comparable<Author> {
//id
private Long id;
//姓名
private String name;
//年龄
private Integer age;
//简介
private String intro;
//作品列表
private List<Book> books;
@Override
public int compareTo(Author o) {
return this.getAge()-o.getAge();
}
}
中间操作limit
可以设置流的最大长度,超出部分将被抛弃;
例如:
对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家姓名
private static void testLimit() {
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted(new Comparator<Author>() {
@Override
public int compare(Author o1, Author o2) {
return o2.getAge() - o1.getAge();
}
})
.limit(2)
.forEach(author -> System.out.println("author = " + author.getName() + "," + author.getAge()));
}
中间操作skip
跳过流中的前面n个元素,返回剩下的元素
例如:
打印除了年龄对大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序
private static void testSkip() {
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted(new Comparator<Author>() {
@Override
public int compare(Author o1, Author o2) {
return o2.getAge() - o1.getAge();
}
})
.skip(1)//剔除最大的人
.forEach(author -> System.out.println("author = " + author.getName() + "," + author.getAge()));
}
中间操作flatMap
map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象,作为流中的元素
例一:
打印所有Authors的书籍名字。要求对重复的元素进行去重
因为之每一个author里面都是一个List
/**
* 打印所有的书籍名字。要求对重复的元素进行去重
*/
private static void testflatMap1() {
List<Author> authors = getAuthors();
authors.stream()
.map(new Function<Author, List<Book>>() {
@Override
public List<Book> apply(Author author) {
return author.getBooks();
}
})
.forEach(new Consumer<List<Book>>() {
@Override
public void accept(List<Book> books) {
books.stream()
.distinct()
.forEach(new Consumer<Book>() {
@Override
public void accept(Book book) {
System.out.println("book = " + book);
}
});
}
});
}
使用flatMap,需要注意Stream<?>泛型的补充
private static void testflatMap2() {
List<Author> authors = getAuthors();
authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {//把list直接转换为Stream流
@Override
public Stream<Book> apply(Author author) {
List<Book> books = author.getBooks();
return books.stream();
}
})
.distinct()
.forEach(new Consumer<Book>() {
@Override
public void accept(Book book) {
System.out.println("book = " + book.getName());
}
});
}
例二:
打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情,也就是要对字符串进行分割
/**
* 打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情
* author ->List<Book>->book->String[]->输出
*/
private static void testflatMap3() {
List<Author> authors = getAuthors();
authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.flatMap(new Function<Book, Stream<String>>() {
@Override
public Stream<String> apply(Book book) {
return Arrays.stream(book.getCategory().split(","));
}
}).distinct()
.forEach(new Consumer<String>() {
@Override
public void accept(String category) {
System.out.println("cagegory= " + category);
}
});
}
3.4.3终结操作(必须要有)
终结操作forEach:遍历每一个元素,对流中的每一个元素进行消费
例如:输出所有作家的名字
.forEach(new Consumer<Author>() {//对每一个元素进行消费
@Override
public void accept(Author author) {
System.out.println("author = " + author.getName());
}
});
终结操作count--可以用来获取当前流中元素的个数
例子:打印这些作家的所处书籍的数目,注意删除重复元素
/**
* 打印这些作家的所出书籍的树数目,注意删除重复元素
* author->books->去重
*/
private static void testCount() {
List<Author> authors = getAuthors();
long count = authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct().count();
System.out.println("count = " + count);
}
终结操作min&max--可以用来获取当前流中的最大值或者最小值
/**
* 分别获取这些作家的所出的书籍的最高分和最低分,并打印
* author->books->去重-》最高分 max or min ( o1-o2)
* 当然也可以 sort +limt 来实现
*/
private static void testMaxMin() {
List<Author> authors = getAuthors();
Optional<Integer> max = authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.map(new Function<Book, Integer>() {
@Override
public Integer apply(Book book) {
return book.getScore();
}
})
.max(new Comparator<Integer>() {
@Override
public int compare(Integer score1, Integer score2) {
return score1 - score2;
}
});
System.out.println("max = " + max.get());
Optional<Integer> min = authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.map(new Function<Book, Integer>() {
@Override
public Integer apply(Book book) {
return book.getScore();
}
}).min(new Comparator<Integer>() {
@Override
public int compare(Integer score1, Integer score2) {
return score1 - score2;
}
});
System.out.println("min = " + min.get());
}
当然上面的操作也可以用 sort +limt 来实现
终结操作collect--把当前流转换成一个集合
.collect(Collectors.toList());
.collect(Collectors.toSet());
.collect(Collectors.toMap());
例子:
获取一个存放所有作者名字的List集合,去重
private static void testCollectList() {
List<Author> authors = getAuthors();
List<String> authorNames = authors.stream().map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
}).distinct().collect(Collectors.toList());
System.out.println("authorNames = " + authorNames);
}
获取一个所有书名的Set集合
/**
* 获取一个所有书名的Set集合
*/
private static void testCollecSet() {
List<Author> authors = getAuthors();
Set<String> booksNameSet = authors.stream().
flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.map(new Function<Book, String>() {
@Override
public String apply(Book book) {
return book.getName();
}
}).collect(Collectors.toSet()); //set集合本身就是不可以重复的,那么就可以省略distinct
System.out.println("booksNameSet = " + booksNameSet);
}
tips:set集合本身就是不可以重复的,那么就可以省略distinct
获取一个map集合,map的key为作者名,vaule为List
/**
* 获取一个map集合,map的key为作者名,vaule为List<Book>
*/
private static void testCollecMap() {
List<Author> authors = getAuthors();
Map<String, List<Book>> mapCollect = authors.stream().distinct()
.collect(Collectors.toMap(new Function<Author, String>() {//2个匿名内部类参数,注意 key需要唯一
@Override
public String apply(Author author) {
return author.getName();
}
}, new Function<Author, List<Book>>() {
@Override
public List<Book> apply(Author author) {
return author.getBooks();
}
}));
System.out.println("mapCollect = " + mapCollect);
}
注意:Map集合的key是唯一的需要去重;
终结操作--查找与匹配
anyMatch--是否没有一个人满足条件
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型;
例子:判断是否有年龄在29周岁以上的作家;
/**
* 例子:判断是否有年龄在29周岁以上的作家;
*/
private static void testAnyMatch() {
List<Author> authors = getAuthors();
boolean flag = authors.stream()
.map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge();
}
})
.anyMatch(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer > 29;
}
});
System.out.println("flag = " + flag);
}
allMatch --是否所有人都满足条件
可以用来判断是否都符合匹配条件,结果为boolean类型。如果都符合结果为true,否则结果为false;
例子:判断是否所有的作家都是成年人;
/**
* 例子:判断是否所有的作家都是成年人;
*/
private static void testAllMatch() {
List<Author> authors = getAuthors();
boolean flag = authors.stream()
.map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge();
}
})
.allMatch(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer > 18;
}
});
System.out.println("是否所有人都大于18周岁 = " + flag);
}
nonceMatch --是否没有人满足条件
可以判断流中的元素是否都不符合匹配条件。如果都不符合结果为true,否则结果为false
例子:判断是否所有的作家都都没有超过100岁的
/**
* 例子:判断是否所有的作家都是成年人
*/
private static void testNonceMatch() {
List<Author> authors = getAuthors();
boolean flag = authors.stream()
.map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge();
}
})
.noneMatch(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer > 100;
}
});
System.out.println("是否没有人大于100周岁 = " + flag);
}
findAny
获取流中的任意一个元素,该方法没有办法保证获取的一定是流中的第一个元素;
/**
* 输出任意一个年龄大于18周岁的作家名字
*/
private static void testFindAny() {
List<Author> authors = getAuthors();
Optional<Author> optionalAuthor = authors.stream()
.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 18;
}
}).findAny();
//如果数据存在,就打印,没有就不执行,避免空指针异常
optionalAuthor.ifPresent(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println("author = " + author.getName());
}
});
}
findFirst
获取流中的任意一个元素,该方法没有办法保证获取的一定是流中的第一个元素;
/**
* 获取一个年龄最小的作家,并输出他的名字
*/
private static void testFindFirst() {
List<Author> authors = getAuthors();
Optional<Author> optionalAuthor = authors.stream()
.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 18;
}
}).findFirst();
//如果数据存在,就打印,没有就不执行,避免空指针异常
optionalAuthor.ifPresent(new Consumer<Author>() {
@Override
public void accept(Author author) {
System.out.println("author = " + author.getName());
}
});
}
终结操作reduce归并--通常搭配map()使用
对流中的数据按照你指定的计算方式计算出一个结果。(缩减操作)
reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式一次拿流中的元素和在初始化值得基础上进行计算,计算结果再和后面的元素计算;
reduce两个参数的重载形式内部的计算方式如下:
T result = identity
for (T element :this stream){
result= accumlatior.apply(result ,element)
}
return result
其中identity就是我们通过方法参数传入的初始值,accoumlator的apply具体进行什么计算也是我们通过方法参数来确定的
例一:使用reduce 求所有作者的年龄和
/**
* 使用reduce 求所有作者的年龄和
*/
private static void testReduce1() {
List<Author> authors = getAuthors();
Integer sum = authors.stream().map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge();
}
}).reduce(0, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result + element;
}
});
System.out.println("sum = " + sum);
}
例二:使用reduce 求所有作者中年龄的最大值
/**
* 使用reduce 求所有作者中年龄的最大值
*/
private static void testReduce2() {
List<Author> authors = getAuthors();
Integer max = authors.stream().map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge();
}
}).reduce(Integer.MIN_VALUE, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result < element ? element : result;
}
});
System.out.println("max = " + max);
}
例三:使用reduce 求所有作者的年龄的最小值
/**
* 使用reduce 求所有作者的年龄的最小值
*/
private static void testReduce3() {
List<Author> authors = getAuthors();
Integer min = authors.stream().map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge();
}
}).reduce(Integer.MAX_VALUE, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result >element ? element : result;
}
});
System.out.println("min = " + min);
}
reduce 一个参数的重载形式内部的计算
boolean foundAny = false;
T result = null;
for (T element : this stream) {
if (!foundAny) {
foundAny = true;
result = element;
}
else
result = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();
求最小值代码如下
private static void testReduce4() {
List<Author> authors = getAuthors();
Optional<Integer> min = authors.stream().map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge();
}
}).reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result > element ? element : result;
}
});
System.out.println("min = " + min);
}
注意事项
- 惰性求值(如果没有终结操作,中间操作是不会得到执行的)
- 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
- 不会影响原数据(我们在流中可以多数据做很多处理,但是正常情况下是不会影响到原来集合中的元素。这往往也是我们期望的)