Stream流
笔记出自自学视频:05.Lambda练习一_哔哩哔哩_bilibili
Java8的Stream使用的是函数式编程模式,他可以用来对集合或数组进行链状流式的操作。
常用操作 创建流
1 2 List<Integer> list = new ArrayList<>(); list.stream();
数组:Arraysstream(数组)或者用Stream.of来创建
1 2 3 Integer[] arr = {1 ,2 ,3 ,4 ,5 }; Stream<Integer> stream = Arrays.stream(arr); Stream<Integer> stream2 = Stream.of(arr);
双列集合:转换成单列集合后再创建
1 2 Map<String,Integer> map = new HashMap <>(); Stream<Map.Entry<String,Integer>> stream = map.entrySet().stream();
中间操作 filter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static void main (String[] args) { Computer c1 = new Computer ("1" ,12 ,12 ); Computer c2 = new Computer ("2" ,12 ,12 ); Computer c3 = new Computer ("ko" ,12 ,12 ); Computer c4 = new Computer ("ko" ,21 ,12 ); Computer c5 = new Computer ("ok" ,12 ,12 ); List<Computer> list = new ArrayList <>(); list.add(c1); list.add(c2); list.add(c3); list.add(c4); list.add(c5); System.out.println("去重前" ); for (Computer com : list) { System.out.println(com); } System.out.println("去重后" ); list.stream() .distinct() .filter(computer -> computer.getLevel()>=12 ) .forEach(computer -> System.out.println(computer)); }
map
这里的map不是单纯的map结构而是映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public static void main (String[] args) { Computer c1 = new Computer ("1" ,12 ,12 ); Computer c2 = new Computer ("2" ,12 ,12 ); Computer c3 = new Computer ("ko" ,12 ,12 ); Computer c4 = new Computer ("ko" ,21 ,12 ); Computer c5 = new Computer ("ok" ,12 ,12 ); List<Computer> list = new ArrayList <>(); list.add(c1); list.add(c2); list.add(c3); list.add(c4); list.add(c5); list.stream() .map(computer -> computer.getName().toString()) .forEach(name-> System.out.println(name)); }
1 2 3 4 5 6 7 8 9 System.out.println("将电脑类转为手机" ); list.stream() .map(computer -> { Phone p = new Phone (); p.setName(computer.getName()); p.setPrice(computer.getPrice()); p.setLevel(computer.getLevel()); return p; }).forEach(phone -> phone.show());
distinct
自定义类需要重写equals方法。
1 2 3 list.stream() .distinct() .forEach(computer -> System.out.println(computer));
sorted
对流中的元素进行排序。
1 2 3 list.stream() .sorted((o1, o2) -> o1.getPrice() - o2.getPrice()) .forEach(computer -> System.out.println(computer));
limit
设置流的最大长度,超出部分丢弃
1 2 list.stream().limit(3 ) .forEach(computer -> System.out.println(computer));
skip
跳过流的前n个元素,返回剩下的元素
1 2 list.stream().skip(5 ) .forEach(computer -> System.out.println(computer));
flatMap
map只能把一个对象装换成另一个对象来作为流的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
终结操作
forEach
对流中的元素进行遍历操作。
count可以用来获取当前流中元素的个数。
max&min
获取当前流的最值。
1 2 3 4 5 6 Optional<Computer> max = list.stream().max(new Comparator <Computer>() { @Override public int compare (Computer o1, Computer o2) { return o1.getPrice() - o2.getPrice(); } });
collect
把当前流转换成一个集合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 List<Computer> collect = list.stream() .sorted(new Comparator <Computer>() { @Override public int compare (Computer o1, Computer o2) { return o1.getPrice() - o2.getPrice(); } }) .collect(Collectors.toList()); Set<Computer> getSet = list.stream() .sorted(new Comparator <Computer>() { @Override public int compare (Computer o1, Computer o2) { return o1.getPrice() - o2.getPrice(); } }) .collect(Collectors.toSet());
查找匹配
anyMatch
用来判断是否有任意符合匹配条件的元素,结果为boolean类型
1 2 3 4 5 6 7 boolean flag = list.stream() .anyMatch(new Predicate <Computer>() { @Override public boolean test (Computer computer) { return computer.getName().equals("华硕" ); } });
allMatch
用来判断是否都符合匹配条件,结果为boolean类型
1 2 3 4 5 6 7 boolean lowPrice = list.stream() .allMatch(new Predicate <Computer>() { @Override public boolean test (Computer computer) { return computer.getPrice() < 2000 ; } });
nonoMatch
判断是否元素都不符合匹配条件。
1 2 3 4 5 6 7 boolean allNone = list.stream() .noneMatch(new Predicate <Computer>() { @Override public boolean test (Computer computer) { return computer.getName().equals("联想" ); } });
获取流中元素
findAny
获取流中任意的一个元素。
1 2 Optional<Computer> any = list.stream() .findAny();
findFirst
获取流中的第一个元素
1 2 3 4 5 6 7 8 9 Optional<Computer> first = list.stream() .findFirst(); first.ifPresent(new Consumer <Computer>() { @Override public void accept (Computer computer) { System.out.println(computer); } });
reduce
对流中的数据按照指定的计算方法计算出一个结果。(缩减操作)
reduce的作用是把stream中元素组合起来,我们可以给定一个初始值。
1 2 3 4 5 6 7 T result = identity; for (T element : this stream) result = accumulator.apply (result,element) return result;
1 2 3 4 5 6 7 8 9 10 11 Integer reduce1 = list.stream() .map(computer -> computer.getPrice()) .reduce(200 , (result, element) -> result + element); Integer reduce2 = list.stream() .map(computer -> computer.getPrice()) .reduce(200 , new BinaryOperator <Integer>() { @Override public Integer apply (Integer integer, Integer integer2) { return integer + integer2; } });
总结
Optiona 可以精简判断对象不为空时的操作
创建对象 一般使用Optional的静态方法ofNullable把数据封装成一个Optional对象。无论传入的参数是不是null都不会出现问题。
1 2 Author authoe = getAuthor () Optional<Author> authorOptional = Optional.ofNUllable (author);
Mybatis从5.3版本已经支持Optional。可以直接把到dao方法的返回值类型定义成Optional类型,Mybatis会自己把数据封装成Optional对象返回。
也可以使用Optional的静态方法of把数据封装成Optional对象。但需要注意传入的参数不能为空。
1 2 Author author = new Author (); Optional<Author> authorOptional = Opthinal.of (author);
安全消费值 获取到一个Optional对象之后可以使用ifPresent方法消费其中的值。
该方法会判断期内封装的数据是否为空,不为空时才会执行具体的消费代码。
1 2 3 4 5 6 7 8 9 Phone phone = new Phone ();Optional<Phone> phone1 = Optional.ofNullable(phone); phone1.ifPresent(new Consumer <Phone>() { @Override public void accept (Phone phone) { phone.show(); System.out.println(phone.getName()); } });
安全获取值 可以使用get方法获取其中的值,但不推荐。当Optional内部的数据为空的时候会出现异常。推荐使用以下方法
orElseGet
获取数据,当数据为空时设置默认值
1 2 3 4 5 6 7 Phone phone2 = phone1.orElseGet(new Supplier <Phone>() { @Override public Phone get () { return new Phone (); } });
orElseThrow
过滤 使用filter方法可以对封装的数据进行判断,若不符合条件则会成为一个无数据的Optional对象。
数据装换
函数式接口 概述 只有一个抽象方法的接口
JDK的函数式接口都加上了@FunctionalInterface注解进行标识。
常见的函数式接口 Consumer 根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。
Function计算转换接口 根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回
Predicate判断接口 根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,把结果返回
Supplier生产型接口 根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把结果返回
常用的默认方法
and
在使用Predicate接口时候可能需要进行条件判断的拼接。而and方法相当于是使用&&来拼接两个判断条件
or
在使用Predicate接口时候可能需要进行条件判断的拼接。而or方法相当于是使用||来拼接两个判断条件
negate
在使用Predicate接口时候可能需要进行条件判断的拼接。而negate方法相当于取反
方法引用 使用lambada时,如果方法体中==只有一个方法==的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。
基本格式 类名或对象名::方法名
语法详解 引用类的静态方法 格式
类名::方法名 //当方法体中只有一行代码,且这行代码是调用了某个类的静态方法,并且我们把重写的抽象方法中的所有参数都按照顺序传入了这个静态方法中。
1 2 Arrays.stream(arr) .map(Math::abs);
引用对象的实例方法 格式
对象名::方法名 //当方法体中只有一行代码,且这行代码是调用了某个对象的成员方法,并且我们把重写的抽象方法中的所有参数都按照顺序传入了这个成员方法中。
1 2 3 4 5 6 7 8 9 10 11 12 13 list2.stream() .map(new Function <Phone, String>() { @Override public String apply (Phone phone) { return phone.getName(); } }) .forEach(new Consumer <String>() { @Override public void accept (String s) { System.out.println(s); } });
引用类的实例方法 格式
类名::方法名 //如果我们重写方法时,当方法体中只有一行代码,且这行代码是调用第一个参数的成员方法,并且我们把重写的抽象方法中的剩余参数都按照顺序传入了这个成员方法中。
构造器引用 格式
类名::new //如果我们重写方法时,当方法体中只有一行代码,且这行代码是调用了某个类的构造方法
Stream基本数据类型优化 我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。但是你一定要知道装箱和拆箱肯定是要消耗时间的。虽然这个时间消耗很下。但是在大量的数据不断的重复装箱拆箱的时候,你就不能无视这个时间损耗了。
所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了很多专门针对基本数据类型的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private static void test27 () { List<Author> authors = getAuthors(); authors.stream() .map(author -> author.getAge()) .map(age -> age + 10 ) .filter(age->age>18 ) .map(age->age+2 ) .forEach(System.out::println); authors.stream() .mapToInt(author -> author.getAge()) .map(age -> age + 10 ) .filter(age->age>18 ) .map(age->age+2 ) .forEach(System.out::println); }
并行流 当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完全。如果我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。
1 2 3 4 5 6 7 8 9 10 Optional<Integer> reduce1 = list.stream() .parallel() .map(Computer::getPrice) .reduce(new BinaryOperator <Integer>() { @Override public Integer apply (Integer integer, Integer integer2) { return integer + integer2; } }); System.out.println("电脑价格总和:" +reduce.orElseGet(() -> 3 ));