主要内容
Stream API的作用
Stream(流) API(是java 8新增的功能,它和lambda表达式共同提供了函数式编程的接口。Stream用于操作数据源,通常是数组或者是集合。流本身不保存数据,但是流与流之间可以传输数据,传输数据的过程可能会对数据进行特定的操作,例如过滤、排序等。另外,Stream并不会改变源数据,对任何数据的操作结果都是返回一个新的Stream。
filter、map和reduce
filter、map和reduce是Stream提供的三个方法,Stream API之所以强大,正正就是因为这三种操作。它们都是通过把特定操作应用到数据之中,然后把结果返回到一个新的Stream之中。
filter
filter是一个过滤器方法,通过把过滤规则传递给filter,filter就会根据这个规则计算结果。filter可应用的规则非常多,任何你想到的规则基本上都可以被正确使用。例如去除数据中的重复数据、获取数据中的奇数值/偶数值等等。
map
假如你有一个整数类型的集合,你想把集合中的每一个元素都乘以2,那么map是你的首选。也就是说,map的作用是把一个特定的操作(乘以2)应用到集合中的每一个数据之中,最后返回一个新的Stream。
reduce
reduce是一个缩减操作,因为reduce的最后返回结果只包含一个值。reduce每一次计算都是处理两个操作数,直到所有数据处理完毕,得出最后的结果只有一个值。例如获取最大值/最小值。
Stream的两种操作和状态
两种操作:
- 终端操作
- 中间操作
所有只返回一个值的操作都是终端操作,所以,reduce是终端操作,包括max和min,forEach方法也是终端操作。终端操作会消费流,被消费后的流不能再使用,否则会抛出IllegalStateException异常。
中间操作不会消费流,而是返回一个新的Stream,因此,任何中间操作都支持链式写法。例如filter和map,它们都是中间操作。也就是说,如果一个方法的返回值是Stream类型的,那么它就一定是中间操作。
两种状态:
- 有状态
- 无状态
两种状态是针对中间操作来说的,对于终端操作影响不大。
有状态是指在处理每一个元素的过程中,这些元素是互相依赖的。例如排序,排序要通过互相对比才能得到结果,任何元素都不能独立于其他元素之外完成排序。
无状态和有状态相反,它们无须依赖数据中的其他元素就可以完成工作,例如判断奇数和偶数。
一种操作是否有状态,决定了这个操作是否可以在并行流中正确执行。顾名思义,并行流是一种并行操作,它可以最大限度地利用cpu的计算能力,从而提高程序的执行速度。如果一种操作是有状态的,那么把它放到并行流中计算,计算结果很大可能会出错。
Optional对象
前面说了,Stream有两种操作,其实这两种操作分别对应两种不同的返回值。中间操作返回的类型是Stream,而这里的Optional对象,则是终端操作的返回值类型。
Optional对象是为了处理返回值可能存在、也可能不存在的场合提供的,换句话说,Optional对象不一定包含值。
Optional对象的两个核心方法是isPresent()
和get()
:
- isPresent()方法可判断对象中是否存在值,如果有值则返回true,否则返回false
- get方法用来获取对象中的值,如果对象中不包含值,则抛出NoSuchElementException异常。
通常会先使用isPresent方法判断对象是否有值,然后才调用get方法获取对象中的值。
流的创建
java 8 中的Collection接口提供了stream和parallelStream方法,前者返回一个普通的流对象,而后者则返回一个并行流对象。从这个接口返回的流对象用于处理集合元素。
如果需要处理数组,那么使用Arrays类提供的stream方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public static void main(String[] args) { //创建处理集合的流 ArrayList<Integer> myList = new ArrayList<>(); myList.add(7); myList.add(18); myList.add(10); myList.add(24); myList.add(17); myList.add(5); Stream<Integer> listStream = myList.stream(); //创建处理数组的流 int[] intArr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; IntStream intStream = Arrays.stream(intArr); } |
使用流进行排序(sorted)
流提供了sorted方法,用于对集合元素进行排序。下面是一个简单的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public static void main(String[] args) { ArrayList<Integer> myList = new ArrayList<>(); myList.add(7); myList.add(18); myList.add(10); myList.add(24); myList.add(17); myList.add(5); Stream<Integer> sortedStream = myList.stream().sorted(); sortedStream.forEach(integer -> System.out.print(integer + " ")); } |
没有参数的sorted方法使用的排序是由小到大的排序,如果想倒序排序,可以往sorted方法传入一个比较器
1 2 |
//倒序 Stream<Integer> sortedStream = myList.stream().sorted((x, y) -> y.compareTo(x)); |
上面的lambda表达式可以使用方法引用替换,使代码更加简介
1 2 |
//倒序 Stream<Integer> sortedStream = myList.stream().sorted(Comparator.reverseOrder()); |
由于sorted方法是有状态的,因此,不能把它放到并行流中处理数据。下面的代码排序结果是错误的,且每次运行的结果都不一样
1 2 |
//使用并行流处理排序 Stream<Integer> sortedStream = myList.parallelStream().sorted(); |
使用map
map可以对每一个元素应用同样的操作,例如想把集合中所有字符串的空格去掉,可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 |
public static void main(String[] args) { List<String> myList = new ArrayList<>(); myList.add(" hello "); myList.add("world "); myList.add(" lambda "); Stream<String> stringStream = myList.stream().map(s -> s.trim()); stringStream.forEach(System.out::println); } |
lambda表达式替换成方法引用
1 |
Stream<String> stringStream = myList.stream().map(String::trim); |
由于map是一个中间操作,因此可以结合其他中间操作如filter一起处理数据:
找出值为w开头的字符串并在字符串后面添加另一个字符串值:
1 2 3 4 5 6 7 8 9 10 11 12 |
public static void main(String[] args) { List<String> myList = new ArrayList<>(); myList.add(" hello "); myList.add(" world "); myList.add(" lambda "); myList.add("wide"); Stream<String> stringStream = myList.stream().filter(s -> s.trim().startsWith("w")).map(s -> s+" world"); stringStream.forEach(System.out::println); } |
使用reduce
reduce作用于集合,每次处理集合中的两个元素,直到所有元素处理完毕,最简单的例子就是计算集合元素的和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public static void main(String[] args) { List<Integer> myList = new ArrayList<>(); myList.add(10); myList.add(3); myList.add(15); myList.add(20); Optional<Integer> optional = myList.stream().reduce((a, b) -> a + b); if (optional.isPresent()){ Integer sum = optional.get(); System.out.println(sum); } } |
可用ifPresent()
方法替换isPresent()
方法,前者代码更加简介
1 2 3 4 5 6 7 8 9 10 11 12 |
public static void main(String[] args) { List<Integer> myList = new ArrayList<>(); myList.add(10); myList.add(3); myList.add(15); myList.add(20); Optional<Integer> optional = myList.stream().reduce((a, b) -> a + b); optional.ifPresent(System.out::println); } |
reduce内部是一个迭代操作,每次迭代的计算结果会保存到lambda的第一个参数之中,这里是a。
reduce还有两个参数的版本,这个版本会从第一个参数开始计算集合的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class ReduceDemo { private static int i = 0; public static void main(String[] args) { List<Integer> myList = new ArrayList<>(); myList.add(10); myList.add(3); myList.add(15); myList.add(20); Integer reduce = myList.stream().reduce(2, (a, b) -> { System.out.print(a + " "); return a + b; }); System.out.println(reduce); } } |
输出结果:
1 |
2 12 15 30 50 |
流转换为集合(collect)
通常情况下,我们都是通过集合来获取流,然后使用流操作集合中的数据,但是,有时候可能会反过来,把流中的数据转换为集合。好在,流提供了collect方法来完成这个转换,使过程变得简单。
collect方法需要配合Collectors接口一起工作,因为collect方法需要接受一个Collector对象为参数,而Collectors接口则提供了一些静态方法来获取Collector对象,常用的有toList和toSet方法。前者把流转换为List,后者把流转换为Set。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public static void main(String[] args){ List<Integer> myList = new ArrayList<>(); myList.add(10); myList.add(15); myList.add(9); myList.add(6); myList.add(20); //获取能被3整除的元素并排序 List<Integer> integerList = myList.stream().sorted() .filter(integer -> integer % 3 == 0) .collect(Collectors.toList()); integerList.forEach(System.out::println); //获取偶数 Set<Integer> integerSet = myList.stream().filter(integer -> integer % 2 != 0) .collect(Collectors.toSet()); integerSet.forEach(System.out::println); } |
collect方法还有另一个带三个参数的版本,这个版本可以对我们的转换过程做更多的控制,比如把数据装到什么类型的集合中,如何装。其中第三个参数是一个combiner,只有在使用parallelStream的时候才有用,它指定了多个线程的合并规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public static void main(String[] args) { List<String> stupidList = new ArrayList<>(); stupidList.add(" hello "); stupidList.add("world "); LinkedList<Object> collect = stupidList.stream().map(String::trim).collect( //()-> new LinkedList(), LinkedList::new, // (list, element) -> list.add(element), LinkedList::add, (listOne, listTwo) ->{ listOne.addAll(listTwo); }); collect.forEach(System.out::println); } |
上面的代码指定了目标集合为ListedList类型,并把数据正常装入链表中。当然,装载数据的过程我们可以对数据进行过滤:
1 2 3 4 5 6 7 8 9 10 |
LinkedList<String> collect = stupidList.stream().map(String::trim).collect( LinkedList::new, (list, element) -> { if (element.trim().equals("hello")){ list.add(element); } }, (listOne, listTwo) ->{ listOne.addAll(listTwo); }); |
实际上,如果单纯从限制目标类型的角度来看,Collectors接口已经提供了方法简化我们的工作:
1 |
stupidList.stream().map(String::trim).collect(Collectors.toCollection(LinkedList::new)); |
转载请注明:Pure nonsense » java 8中的Stream API