此次主要介绍Java 8的Stream以及如何与lambda配合使用。Stream做为Java 8的一大亮点,它与java.io包里的InputStream和OutputStream是彻底不一样的概念。Java 8中的Stream是对集合对象功能的加强,它专一于对集合对象进行各类很是便利、高效的聚合操做,或者大批量数据操做。Stream API借助于一样新出现的lambda表达式,极大的提升编程效率和程序可读性。能够说,Stream的出现,彻底改变了处理集合的方式。但愿你们在看完这篇文章后,能抛弃以前对集合用Iterator遍历并完成相关的聚合操做那种笨拙的方式,改用流来处理。
先用一个例子让你们感觉一下Stream的便捷。假设有这样一个Book类:java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private Integer id;
private String name;
private String type;
private Double price;
}
复制代码
如今有这样一个业务场景:要发现种类为“计算机”的全部图书,而后返回以价格增序排序好的图书ID集合:
传统方式算法
public class StreamTest {
public static void main(String[] args) {
List<Book> bookList = new ArrayList<>();
bookList.addAll(Arrays.asList(new Book(1, "Java核心技术", "计算机", 90.0),
new Book(2, "Java编程思想", "计算机", 100.0),
new Book(3, "浮生六记", "文学", 50.0)));
List<Book> computerBooks = new ArrayList<>();
for (Book b : bookList) {
if (b.getType().equals("计算机")) {
computerBooks.add(b);
}
}
Collections.sort(computerBooks, new Comparator<Book>() {
@Override
public int compare(Book b1, Book b2) {
return b1.getPrice().compareTo(b2.getPrice());
}
});
List<Integer> bookIds = new ArrayList<>();
for (Book b : computerBooks) {
bookIds.add(b.getId());
}
}
}
复制代码
使用Stream编程
public class StreamTest {
public static void main(String[] args) {
List<Book> bookList = new ArrayList<>();
bookList.addAll(Arrays.asList(new Book(1, "Java核心技术", "计算机", 90.0),
new Book(2, "Java编程思想", "计算机", 100.0),
new Book(3, "浮生六记", "文学", 50.0)));
List<Integer> bookIds = bookList.stream()
.filter(b -> b.getType().equals("计算机"))
.sorted(Comparator.comparing(Book::getPrice))
.map(Book::getId)
.collect(Collectors.toList());
}
}
复制代码
能够看到,原先繁琐的操做,在使用了Stream后,只用一句话就解决了。那Stream到底是什么呢?数据结构
Stream不是集合元素或者数据结构,它并不保存数据,而是有关数据的算法和计算的。能够把Stream理解成一个高级版本的Iterator。原始版本的Iterator,用户只能显式地一个一个遍历元素并对其执行某些操做;高级版本的Stream,用户只要给出须要对其包含的元素执行什么操做,好比 “过滤掉长度大于10的字符串”、“获取每一个字符串的首字母”等,Stream会隐式地在内部进行遍历,作出相应的数据转换(ps:也能够把Stream理解成一种处理数据的风格,这种风格将要处理的元素集合看做一种流,流在管道中传输,而且能够在管道的节点上进行处理,好比筛选、排序、聚合等)。
可能上面说的有些抽象,下面给出一个具体的例子。有这样一段代码:多线程
List<Integer> nums = new ArrayList<>();
nums.addAll(Arrays.asList(1, null, 3, 4, null, 6));
nums.stream().filter(num -> num != null)).count();
复制代码
上面这段代码的目的是获取一个List中不为null的元素的个数。经过这段代码,咱们来剖析一下Stream的结构: 并发
须要强调的是,在对一个Stream进行屡次Intermediate操做时,每次都对Stream的每一个元素进行转换,但实质上并无作N(转换次数)次for循环。转换操做都是lazy的,多个转换操做只会在Terminal操做的时候融合起来,一次循环完成。咱们能够这样简单的理解,Stream里有个操做函数的集合,每次转换操做就是把转换函数放入这个集合中,在Terminal操做的时候循环Stream对应的集合,而后对每一个元素执行全部的函数。
至于有哪些转换和聚合方法呢,你们能够自行查找。推荐一篇文章:ifeve.com/stream/ ,里面用示意图说明了几个典型的转换和聚合方法,很形象。框架
map的做用是把input Stream的每个元素,映射成output Stream的另一个元素,例如:ide
// 大小写转换
List<String> wordList = Arrays.asList("a", "b", "c");
List<String> newWorldList = wordList.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
复制代码
map生成的是1:1映射,每一个输入元素,都按照规则转换成为另一个元素。还有一些场景,是一对多映射关系的,这时须要flatMap:函数
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
Stream<Integer> outputStream = inputStream
.flatMap((childList) -> childList.stream());
复制代码
flatMap把input Stream中的层级结构扁平化,就是将最底层元素抽出来放到一块儿。最终output Stream里面已经没有List了,都是直接的数字。大数据
这个方法的主要做用是把Stream元素组合起来。它提供一个起始值,而后依照运算规则(BinaryOperator),和前面Stream的第一个、第二个、第n个元素组合。从这个意义上说,字符串拼接、数值的sum、min、max、average都是特殊的reduce。例如Stream的sum就至关于如下两种写法:
Integer sum = integers.reduce(0, (a, b) -> a+b);
Integer sum = integers.reduce(0, Integer::sum);
复制代码
reduce的用例以下:
// 字符串链接,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);
复制代码
Stream有三个match方法,从语义上说:
它们都不是要遍历所有元素才能返回结果。例如allMatch只要一个元素不知足条件,就skip剩下的全部元素,返回false。match的用例以下:
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);
复制代码
emmm就先写这几个吧,其实Stream另外一个便捷的地方在于它的一些方法能够直接根据方法名来判断用途。固然,想要掌握Stream的用法,仍是要——多用。
最后,说几个lambda表达式须要注意的地方:
初识lambda表达式,可能有的人还会以为很陌生,可是在掌握它的用法以后,必定能感觉到它的强大之处!