一、流程
1.创建流
单列集合: List.stream()
数组: Arrays.stream(数组) \ Stream.of(数组)
双列集合: Map.entrySet().stream()
2.中间操作
对流进行处理
3.终结操作
对处理过的流进行最后的输出 如果没有终结操作的话,中间操作就不会被施行
二、中间操作
1.过滤filter
可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中
filter(Predicate<? super T> predicate) 参数为函数式接口可以使用lambda
如果过滤 == true则保留,false则过滤掉
2.map()
类型转换:对集合中的对象进行运算或者转化类型,比如可以将集合中的POJO转换成其一个属性的集合,然后输出这个POJO集合会发现输出的都是这个属性的信息
计算: 对一个对象集合,map可以对每一个对象的每个属性进行赋值计算等操作
顺序性: 可以再次对流进行map操作,意义就是如果这个流现在里面全都是某一属性的集合,这个map就可以再次对此进行类型转换或者计算,有点像SQL的语句嵌套的感觉
3.sorted()
对list集合进行排序
//示例1:对id进行升序排序 private static void printSortList(List<User> list) { List<User> userList=list.stream().sorted(Comparator.comparing(User::getId)).collect(Collectors.toList()); userList.forEach(u->System.out.println(u)); } //示例2:对id进行降序排序 private static void printSortList(List<User> list) { List<User> userList=list.stream().sorted(Comparator.comparing(User::getId).reversed()).collect(Collectors.toList()); userList.forEach(u->System.out.println(u)); } //示例3:age升序 id=降序=thenComparing private static void printSortList(List<User> list) { List<User> userList=list.stream() .sorted(Comparator.comparing(User::getAge).thenComparing(User::getId).reversed()) .collect(Collectors.toList()); userList.forEach(u->System.out.println(u)); }
4.flatMap
map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
//如果一个元素的一个属性为List类型的话 authors.stream() .flatMap(new Function<Author, Stream<Book>>(){ @Override public Stream<Book> apply(Author author){ return author.getBooks().stream(); } })
flatMap另一种方式 = 对流进行操作并返回流
5.limit()
对流中的前几个元素进行操作,如果设置参数超过集合的长度对返回的长度没有影响
6.skip(int a)
跳过前a个元素开始操作
skip索引起始于1,如果传入负值会抛出异常。
7.去重
distinct()
注意: distinct方法是依赖Obiet的equals方法来判断是否是相同对象的。所以需要注意重新equals方法。
Object的equals是 == 比较的是地址,所以要注意equals的重写
list.stream().distinct().forEach(employee -> System.out.println(employee.getName()));
8.排序
sorted()
空参: 实现排序的POJO类中要实现Comparable接口,定义抽象方法compareTo 返回正整数为升序,负整数为降序
this.xxx - 参数.xxx = ? this在左,参数在右
public class Cat implements Comparable<T>{ //实现方法 public int compareTo(Cat cat){ return this.age - cat.age; } }
实参: 不必纠结前后顺序,如果不对更换一下顺序即可
authors.stream() .disitinct() .sorted(new Comparator<Author>(){ @Override public int compare(Author o1,Author o2){ return o1.getAge() - o2.getAge(); } }) .forEach()
三、终结操作
终结操作之后这个流就报废了,如果再使用的话就会报错
1.forEach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体操作
2.count
可以用来获取当前流中的元素个数 返回值为long
3.max&min
求流中的最大值或者最小值,也许是比较int型或者String所以要重写匿名方法来设置比较规则,正数为大,负数为小
返回值为Optional
4.collect
将流中的元素转换成一个集合
转换为
List:
collect(Collectors.toList())
Set
collect(Collectors.toSet())
Map
key也可能重复,所以先将流中的元素去重也可以
5.anyMatch(重写判断条件)
判断是否有任意符合匹配条件的元素,结果为Boolean类型
6.allMatch(重写判断条件)
判断是否都符合匹配条件,return Boolean类型
7.noneMatch(重写判断条件)
判断是否都不符合匹配条件,return Boolean类型
8.findAny 随机性
获取流中的任意一个元素.该方法没有办法保证获取的一定是流中的第一个元素
返回值 Optional
ifPresent()判断这个Optional对象中是否有值,有则执行
9.findFirst
获取流中的第一个元素
和上面的返回值及操作相似
10.reduce归并
对流中的数据按照你指定的计算方式计算出一个结果。 (缩减操作)
reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和在初始化值的基础上进行计算,计算结果再和后面的元素计算。T result = identity; // 初始累加值 forI(T element : this stream) result = accumulator .apply(result, element) return result;
其中identitv就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的。
//类似于 int[] arr = {}; int result = 1; for(int i : arr){ result = result * 1; } return result;
reduce传入两个参数
第一种
//求所有年龄的和 authors.stream(). map(author -> author.getAge()). reduce(0,new BinaryQperator<Integer>(){ @Override public Integer apply(Integer result,Integer element){ //计算过程 return result + element; } }) //其中,第一个参数是我们设置的默认初始值 0 ,第二个参数是流中的每一个元素
第二种
//求最大值 //求最小值就可以将Integer.MIN改为 MAX这样计算 Integer reduce = list.stream(). reduce(Integer.MIN_VALUE, new BinaryOperator<Integer>() { @Override public Integer apply(Integer result, Integer element) { return result < element ? element : result; } });
reduce一个参数重载形式
将流中的第一个元素作为初始化值
boolean foundAny = false ;T result = nu11; for (T element : this stream) { if (!foundAny) { foundAny = true; result = element; }e1se result = accumulator .apply(result, element); } return foundAny ? optional .of(result) : optional.empty() ;
11.Optional
Optional就好像是包装类,可以把我们的具体数据封装在Optional对象内部。然后我们去使用Optional中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。
12.创建Optional
使用静态方法Optional.ofNullable(POJO)
public static void main(String[] args) { Optional<Person> personOptional = getPersonOptional(); personOptional.ifPresent(person2 -> System.out.println(person2.getName())); } public static Optional<Person> getPersonOptional() { Person person = new Person("朱大夫", 34, "一个伟大的人"); return Optional.ofNullable(person); }
Optional.of() 确定对象不为空使用
Optional.empty() 返回null值的Optional对象
13.安全消费值
Optional.ifPresent()
14.获取值.
get方法,不推荐,如果包装的是一个null值会抛出异常
orElseGet
获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建对象作为默认值返回。optional<Author> authoroptional = optional.ofNu1able(getAuthor ());Author authorl = authoroptional.orE1seGet(() -> new Author());
orElseThrow
获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出。public static void main(String[] args) { try { Optional<Person> personOptional = getPersonOptional(); Person person = personOptional.orElseThrow(() -> new RuntimeException("没有值会抛出这个异常")); System.out.println(person); } catch (RuntimeException e) { throw new RuntimeException(e); } } public static Optional<Person> getPersonOptional() { Person person = new Person("朱大夫", 34, "一个伟大的人"); return Optional.ofNullable(person); }
15.过滤
filter() 和stream.map类似
如果此Optional本来是有数据的,但是经过filter过滤之后,没有符合条件的数据,此Optional也会变成一个无数据的Optional对象
personOptional.filter(person1 -> person1.getAge() > 34).ifPresent(person1 -> System.out.println(person1.getName()));
16.判断
isPresent()判断是否存在
17.数据转换
和stream中的map方法一样,返回的也还是一个Optional对象
四、函数式接口
1.只有一个抽象方法的接口我们称之为函数接口
JDK的函数式接口都加上了@FunctionalInterface注解进行标识.但是无论是否加上该注解只要接口重质油一个抽象方法,都是函数式接口
consumer消费接口 void accept(T t)
function计算转换接口 R apply(T t)
predicate判断接口 boolean test(T t)
Supplier 生产型接口 T get()
spanjava11自带的函数式接口可以传入多个参数 : package java.util.function; BiFunction\BiConsumer等////
2.函数式接口常用默认方法
意义在于定义方法的时候: 当我们将多个函数式接口 a b 当做一个方法的参数时,可以在方法内使用这个函数式接口的默认方法,比如a.and(b).
and拼接条件
or或者条件
negate取反的操作 !
3.方法引用
重点是,参数顺序->知道参数是什么,参数准确传入->简化代码 == 方法引用
我们在使用lambda时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。我们只需要在写完lambda方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。当我们方法引用使用的多了慢慢的也可以直接写出方法引用。
4.基本格式
类名或者对象名::方法名
5.引用类的静态方法
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。
6.引用类的实例方法
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。
public class OptionalTest {
interface UseString {
String use(String str, int start, int length);
}
public static String subAuthorName(String str, UseString useString) {
int start = 0;
int length = 1;
return useString.use(str, start, length);
}
public static void main(String[] args) {
subAuthorName("小牧", String::substring);
}
}
7.构造器引用
类名::new
五、高级用法
1.基本数据类型优化
我们之前用到的很多Stream方法由于都使用了泛型,所以涉及到的参数和返回值都是引用数据类型即使我们操作的都是整数小数,但是实际用的都是他们的包装类.JDK提供的自动装箱和自动拆箱可以让我们在使用对应的包装类时就好像使用基本数据类型一样.但是这样在大数据量下是会消耗时间的.
Stream还提供了很多专门针对基本数据类型的方法
mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToLong,等
2.并行流
Stream.parallel()将串行流转换成并行流
或者一个集合直接list.parallelStream()