常用函数式接口与Stream API简单讲解

Stream简直不要太好使啊!!!

常用函数式接口

  • Supplier<T>,主要方法:T get(),这是一个生产者,可以提供一个T对象。
  • Consumer<T>,主要方法:void accept(T),这是一个消费者,默认方法:andthen(),稍后执行。
  • Predicate<T>,主要方法:boolean test(T t),这是一个判断者,默认方法:and():且,or():或,negate():非。
  • Function<T,R>,主要方法:R apply(T t),这是一个修改者,默认方法:compose():优先执行,andThen(),稍后执行,identity():直接传自身。
  1. Function f1 = e -> e + 1;
  2. Function f2 = e -> e * 1;
  3. f1.andthen(f2).apply(t)//即f2.apply(f1.apply(t))
  4. f1.compose(f2).apply(t)//即f1.apply(f2.apply(t))
  5. identify()相当于 s -> s

常用Stream方法:

中间操作是惰性求值,中间操作过后返回的还是Stream,因此可以继续操作;结束操作是立即求值,返回具体的数据。

流不能被重用,因此推荐链式写法。

上边仅仅是Stream的方法,经常使用的还有Stream的静态方法,比如:Stream.concat(),用来合成两个流;Stream.of()方法,直接获取流。

  • 使用流处理int数组时,可以使用Arrars.stream(arr)或者IntStream.of(arr)进行初始的流转化,而Stream.of(arr)会把arr当做数组对象传入,而非直接操作数组。(Arrays.stream 是为了弥补Stream.of无法使用数组参数而做的补偿方案.比如a[]传入Stream.of会被当成一个对象而传入Arrays.stream可以当成一个数组)

具体区别可以由下面几个栗子看出来:

  1. int[] arr = {1,2,3,4};
  2. System.out.println(
  3. Arrays.stream(arr).count());//4,因为数组里有4个数
  4. System.out.println(
  5. Stream.of(arr).count());//1,因为只有一个数组,并且这里的流不能调用sum()方法求和,因为引用对象没法相加
  6. Stream.of(1,2,3,4).count();//4,因为有四个Integer,注意这里Stream.of()方法会自动装箱,把int变为Integer,这里也不能调用sum()方法
  7. Arrays.stream(new int[]{1,2,3,4}).count();//4,等价于第一种情况,并且此时还有个box()方法可以装箱,因此使用流处理拆装箱非常方便
  8. //但是!如果Stream.of()参数是一个引用类型的数组(注意是一个),比如String[]:
  9. System.out.println(
  10. Stream.of(new String[]{"My", "Java", "My", "Life!"}).count());//4
  11. //它又可以正常使用,
  12. //但是!!如果里面是两个String[]数组,它又不好使了:
  13. String[] s1 = {"My", "Java", "My", "Life!"};
  14. String[] s2 = {""};
  15. System.out.println(
  16. Stream.of(s1,s2).count());//2!!!

小结:说得好,数组我选择Arrays.Stream()! (`へ´*)ノ

主要方法介绍:

forEach

forEach():void forEach(Consumer<? super E> action),

即对每个元素执行action,也就是最常见的遍历

  1. Stream.of("My","Java","My","Life!")
  2. .forEach(System.out::println);//打印每个元素

filter

filter():Stream<T> filter(Predicate<? super T> predicate),筛选,即挑选出符合predicate的元素

  1. Stream.of("My", "Java", "My", "Life!")
  2. .filter(s -> s.length() > 4);//筛选出长度大于4的元素,此时流里的元素为:"Java","Life!"

distinct

distinct():Stream<T> distinct(),去重

  1. Stream.of("My", "Java", "My", "Life!")
  2. .distinct();//"My","Java","Life!"

sorted

sorted():Stream<T> sorted()Stream<T> sorted(Comparator<? super T> comparator),排序,无参为自然排序,有参为自定义比较器排序

  1. Stream.of("My", "Java", "My", "Life!")
  2. .sorted()//"Java","Life!","My","My"无参,也就是按照默认排序(这里就是ASCII码)
  3. .forEach(System.out::println);
  4. Stream.of("My", "Java", "My", "Life!")
  5. .sorted((s1, s2) -> s2.length() - s1.length())//"Life!","Java","My","My",按照长度进行降序排序
  6. .forEach(System.out::print);

map

map():<R> Stream<R> map(Function<? super T,? extends R> mapper),转换,也就是执行Function的功能

  1. class Student{
  2. private String name;
  3. public Student(String name){
  4. this.name = name;
  5. }
  6. }
  7. Stream.of("My", "Java", "My", "Life!")
  8. .map(String::toLowerCase)//转化为小写
  9. .map(Student::new);//接着以名字为参数转化Student流

flatMap

flatMap():<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper),扁平化转换流,与map()不同的是,map()返回一个结果,而当返回结果数目未知时不太方便,使用flatMap()会将多个数目不一的流合为一个流

  1. Stream.of(Arrays.asList("My"), Arrays.asList("Java", "My", "Life!"))//此时,这是List流,里面是两个List对象,List里面才是字符串数据
  2. .flatMap(list -> list.stream())//扁平化,即把List流转化为字符串流,这里也可以使用方法引用:.flatMap(Collection::stream)
  3. .forEach(System.out::println);

count

count():long count(),统计流中元素的数量,注意返回值为long

  1. Stream.of("My", "Java", "My", "Life!")
  2. .count();//4L

limit

limit():Stream<T> limit(long maxSize),限制长度,也可以理解为取前maxSize个元素,maxSize可以大于元素数目

  1. Stream.of("My", "Java", "My", "Life!")
  2. .limit(2);//"My","Java"

skip

skip():Stream<T> skip(long n),跳过前n个元素,和limit差不多

  1. Stream.of("My", "Java", "My", "Life!")
  2. .skip(2);//"My","Life!"

toArray

toArray:Object[] toArray()<A> A[] toArray(IntFunction<A[]> generator),无参即返回Object[]数组,而有参时可以返回特定类型数组

  1. Stream.of("My", "Java", "My", "Life!")
  2. .toArray(String[]::new);//String[]数组,内容为:["My", "Java", "My", "Life!"]

parallel

parallel:S parallel()转化为并行流,但是单核甚至双核的情况下转化并行流反而会降低效率。

max

max():Optional<T> max(Comparator<? super T> comparator),以自定义比较方法返回一个最大对象,注意这里返回的是Optional对象。

  1. Stream.of("My", "Java", "My", "Life!")
  2. .max((o1, o2) -> o2.length()-o1.length())
  3. .get();//"My"
  4. Stream.of("My", "Java", "My", "Life!")
  5. .max(Comparator.comparingInt(String::length))//"Life!",这里返回的是Optional,里面盛的是"Life!"
  6. .stream()//这里调用的是Optional的方法,其实这里没有必要,直接调用get()方法就可以拿到"Life!"字符串了
  7. .forEach(System.out::println);

说到了max()就不得不提一下Optional是什么,简单来说Optional就是一个容器,用来盛放数据,并且它是容许为null的,这样就不用反复判断值是否为空了。可以看一下Optional的方法:

  1. public final class Optional<T> {
  2. //静态方法,返回空Optional对象
  3. public static<T> Optional<T> empty() {
  4. }
  5. //静态方法,返回Optional对象,value不能为空
  6. public static <T> Optional<T> of(T value) {
  7. return new Optional<>(value);
  8. }
  9. //静态方法,返回Optional对象,值允许为null
  10. public static <T> Optional<T> ofNullable(T value) {
  11. return value == null ? empty() : of(value);
  12. }
  13. //虽然值允许为null,但还是在调用get时还是会抛出NoSuchElementException异常
  14. public T get() {
  15. }
  16. //所以此方法可以判断值是否存在
  17. public boolean isPresent() {
  18. }
  19. //如果存在,则执行action
  20. public void ifPresent(Consumer<? super T> action) {
  21. }
  22. //JDK9方法↓
  23. //如果值非空,执行action,值为null则执行emptyAction
  24. public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
  25. }
  26. //Optional对象也可以执行与Stream相同的部分方法
  27. public Optional<T> filter(Predicate<? super T> predicate) {
  28. }
  29. public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
  30. }
  31. public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
  32. }
  33. //JDK9方法↓
  34. //如果为null,则返回一个supplier提供的对象,与orElse和orElseGet方法类似。
  35. public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
  36. }
  37. //JDK9方法↓
  38. //可以把Optional对象继续变成流
  39. public Stream<T> stream() {
  40. }
  41. public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
  42. if (value != null) {
  43. return value;
  44. } else {
  45. throw exceptionSupplier.get();
  46. }
  47. }
  48. //重写equals方法
  49. @Override
  50. public boolean equals(Object obj) {
  51. }
  52. //重写hashCode方法
  53. @Override
  54. public int hashCode() {
  55. }
  56. //重写toString方法
  57. @Override
  58. public String toString() {
  59. return value != null
  60. ? String.format("Optional[%s]", value)
  61. : "Optional.empty";
  62. }
  63. }

orElse和orElseGet:

orElseorElseGet这两个方法放上源码比较好理解:

首先,在值为null时,两个方法并无区别,都能获取到T;

但是,在非空时,如果orElse()的参数是一个方法,该方法也会被执行,而orElseGet()的参数supplier并不会执行,这得益于Lambda的延迟执行。因此,相同情况下orElseGet()性能更优。或者直接使用or,如果JDK允许的话

  1. public T orElse(T other) {
  2. return value != null ? value : other;
  3. }
  4. public T orElseGet(Supplier<? extends T> supplier) {
  5. return value != null ? value : supplier.get();
  6. }

举个小栗子就能明白了:

  1. static String B() {
  2. System.out.println("B()执行了");
  3. return "B";
  4. }
  5. public static void main(final String... args) {
  6. System.out.println(Optional.ofNullable(null).orElse(B()));
  7. System.out.println(Optional.ofNullable(null).orElseGet(() -> B()));
  8. }

上面这种情况执行结果为:

  1. B()执行了
  2. B
  3. B()执行了
  4. B

并没有什么问题,但是当值非空的时候:

  1. static String B() {
  2. System.out.println("B()执行了");
  3. return "B";
  4. }
  5. public static void main(final String... args) {
  6. System.out.println(Optional.ofNullable("A").orElse(B()));
  7. System.out.println(Optional.ofNullable("A").orElseGet(() -> B()));
  8. }

执行结果为:

  1. B()执行了
  2. A
  3. A

很明显orElseGet更节约性能,Lambda的延迟执行特性还是比较好用的。


咳咳,扯远了,继续说Stream的常用方法:

min

min():Optional<T> min(Comparator<? super T> comparator),与max类似,注意max()min()最好使用引用方法,而不是自己定义Comparator,举个栗子:

  1. Stream.of("My", "Java", "My", "Life!")
  2. .min((o1, o2) -> o2.length()-o1.length());//"Life!"

这里使用的是min()方法,自定义Comparator为常见的降序,但是最后得到的反而是长度最长的,完全违背了函数式见文知意的思想。

所以想要比较长度的话这样写:

  1. Stream.of("My", "Java", "My", "Life!")
  2. .max(Comparator.comparingInt(String::length));
  3. Stream.of("My", "Java", "My", "Life!")
  4. .min(Comparator.comparingInt(String::length));

或者,你觉得max(),min(),count(),sum()这些不好用,还有下面两个多能工供你使用

reduce

reduce():Optional<T> reduce(BinaryOperator<T> accumulator);T reduce(T identity, BinaryOperator<T> accumulator);<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);

一个reduce操作(也称为折叠)接受一系列的输入元素,并通过重复应用操作将它们组合成一个简单的结果,也就是持续迭代

理解reduce的含义重点就在于理解"累 加 器" 的概念,并且每次运算的结果会作为下一次运算的一个参数

Stream的一个参数和两个参数的方法的基本逻辑都是如此,差别仅仅在于一个参数的是result R = T1 ,然后再继续与剩下的元素参与运算

分别为一个参数、两个参数、三个参数,这里需要先介绍一下这几个新接口:

  • BiFunction

    • public interface BiFunction<T, U, R>
    • R apply(T t, U u)
    • 默认方法andthen
    • 它与Function不同点在于它接收两个输入返回一个输出; 而Function接收一个输入返回一个输出。注意它的两个输入、一个输出的类型可以是不一样的。
  • BinaryOperator
    • public interface BinaryOperator<T> extends BiFunction<T,T,T>
    • 有两个静态方法:minBymaxBy
    • BiFunction的三个参数可以是一样的也可以不一样;而BinaryOperator就直接限定了其三个参数必须是一样的;因此BinaryOperator与BiFunction的区别就在这。它表示的就是两个相同类型的输入经过计算后产生一个同类型的输出。
  • BiConsumer
    • void accept(T t, U u)
    • Consumer变种,接收两个参数,不返回

reduce一个接口参数可以很方便的进行求最大值、最小值、和,代码如下:

  1. Integer sum = s.reduce((a, b) -> a + b).get();
  2. Integer max = s.reduce((a, b) -> a >= b ? a : b).get();
  3. Integer min = s.reduce((a, b) -> a <= b ? a : b).get();

reduce两个参数的对比一个参数的多了一个初始化的值,

  1. T result = identity;
  2. for (T element : this stream){
  3. result = accumulator.apply(result, element)
  4. }
  5. return result;

所以reduce(0, (a, b) -> a + b)reduce((a, b) -> a + b)是完全等价的。

reduice三个参数较为复杂,前两个参数与二参基本相同,第三个参数用于在并行计算下,合并各个线程的计算结果。需要注意的是,并行情况下初始值(也就是第一个参数)会被多次计算,具体可以参看此博客

collect

collect

collect():<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)<R, A> R collect(Collector<? super T, A, R> collector)

收集装置,可以将元素以特定方式收集起来。需要了解的是收集器的参数决定了收集器的行为

这里需要说一下Collector接口,这里先上几个重要的静态方法:

生成Collection

  • toCollection()将流转化为集合,参数为集合实现方式,比如:
  1. Stream.of("My", "Java", "My", "Life!")
  2. .collect(Collectors.toCollection(ArrayList::new));//以ArrayList的形式转化为集合
  • toList()toset()这两个可以视为上面的简化版本,无参,默认为ArrayList和HashSet实现:
  1. Stream.of("My", "Java", "My", "Life!")
  2. .collect(Collectors.toList());

生成Map

  • toMap与上面类似,只不过需要指定map的键和值:
  1. Stream.of("My", "Java", "my", "Life!")
  2. .collect(Collectors.toMap(Function.identity(),//指定Key
  3. s -> s.length()))//指定Value
  4. //注意,这里键是不能重复的,否则会抛出异常,所以我把第二个"My"改成了"my",想要键可重复请继续往下看。
  5. //运行结果
  6. {Java=4, Life!=5, My=2, my=2}
  • partitioningBy()适用于将Stream中的元素依据某个二值逻辑(满足条件,或不满足)分成互补相交的两部分,一参为一个Predicate接口,二参时可以将第二个参数指定为再一个Collector。
  1. Map<Boolean, List<String>> map = Stream.of("My", "Java", "my", "Life!")
  2. .collect(Collectors.partitioningBy(s -> s.length() > 3));//以长度是否大于3分组
  3. //运行结果
  4. {false=[My, my], true=[Java, Life!]}
  5. //二参可以指定一个下游收集器
  6. Map<Boolean, Long> map = Stream.of("My", "Java", "my", "Life!")
  7. .collect(Collectors.partitioningBy(s -> s.length() > 4, Collectors.counting()));//以长度是否大于4分组,并计数
  8. //运行结果
  9. {false=3, true=1}
  • groupingBy():上面的partitioningBy()只能将数据分成两类,根本不够用是不是,groupingBy()则可以做到更多。它分别有一参、二参和三参:
  1. //一参可以当做partitioningBy用
  2. Map<Boolean, List<String>> map = Stream.of("My", "Java", "My", "Life!")
  3. .collect(Collectors.groupingBy(s -> s.length() > 4));//以长度是否大于4分组
  4. //运行结果
  5. {false=[My, Java, My], true=[Life!]}
  6. //也可以当做增强型
  7. Map<Integer, List<String>> map = Stream.of("My", "Java", "My", "Life!")
  8. .collect(Collectors.groupingBy(String::length));//以长度分组
  9. //运行结果
  10. {2=[My, My], 4=[Java], 5=[Life!]}
  11. //二参就更好用了
  12. Map<String, Long> map = Stream.of("My", "Java", "My", "Life!")
  13. .collect(Collectors.groupingBy(
  14. Function.identity(),Collectors.counting()));//指定键为元素,值为出现次数,这种情况下键是可以重复的
  15. //运行结果
  16. {Java=1, Life!=1, My=2}
  17. //或者
  18. Map<Integer, Long> map = Stream.of("My", "Java", "My", "Life!")
  19. .collect(Collectors.groupingBy(String::length,Collectors.counting()));//以长度为键,出现次数为值
  20. //运行结果
  21. {2=2, 4=1, 5=1}
  22. //三参就更厉害了
  23. Map<Integer, List<String>> map = Stream.of("My", "Java", "My", "Life!")
  24. .collect(Collectors.groupingBy(
  25. String::length,Collectors.mapping(s->s.toUpperCase(),Collectors.toList())));//注意这里下游收集齐还包含着更下游的收集齐
  26. //运行结果
  27. {2=[MY, MY], 4=[JAVA], 5=[LIFE!]}

你可能觉得三参的collect用不到,但是请看这里:

下游收集器还可以包含更下游的收集器,这绝不是为了炫技而增加的把戏,而是实际场景需要。考虑将员工按照部门分组的场景,如果我们想得到每个员工的名字(字符串),而不是一个个Employee对象,可通过如下方式做到:

  1. // 按照部门对员工分布组,并只保留员工的名字
  2. Map<Department, List<String>> byDept = employees.stream()
  3. .collect(Collectors.groupingBy(Employee::getDepartment,
  4. Collectors.mapping(Employee::getName,// 下游收集器
  5. Collectors.toList())));// 更下游的收集器

超强!参见这篇博客

  • joining()这是用来连接字符串的,无参为直接连接,一参指定连接符,三参可以指定连接符和首位字符:
  1. Stream.of("My", "Java", "My", "Life!")
  2. .collect(Collectors.joining());//MyJavaMyLife!
  3. Stream.of("My", "Java", "My", "Life!")
  4. .collect(Collectors.joining("-")//My-Java-My-Life!
  5. Stream.of("My", "Java", "My", "Life!")
  6. .collect(Collectors.joining("-","[","]"))//[My-Java-My-Life!]

小结:用了Stream之后再也不想用普通的for循环了有木有啊!!!

常用函数式接口与Stream API简单讲解的更多相关文章

  1. 函数式接口与Stream流

    lambda表达式是jdk8的特性.lambda表达式的准则是:可推断,可省略. 常规代码写一个多线程 public class Main { public static void main(Stri ...

  2. Java常用函数式接口--Consumer接口andThen()方法使用案例(二)

    Java常用函数式接口--Consumer接口使用案例

  3. Java常用函数式接口--Predicate接口使用案例

    Java常用函数式接口--Predicate接口使用案例 该方法可以使用and来优化: 调用:

  4. 8000字长文让你彻底了解 Java 8 的 Lambda、函数式接口、Stream 用法和原理

    我是风筝,公众号「古时的风筝」.一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农! 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在 ...

  5. Java 之 常用函数式接口

    JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供.下面是最简单的几个接口及使用示例. 一.Supplier 接口 java ...

  6. java8 常用函数式接口

    public static void main(String[] args) { // TODO Auto-generated method stub //函数式接口 Function<Inte ...

  7. 黑马函数式接口学习 Stream流 函数式接口 Lambda表达式 方法引用

  8. Java常用函数式接口--Consumer接口使用案例

    第一种方式: 第二种方式:

  9. Java常用函数式接口--Supplier接口使用案例

    使用案例:

随机推荐

  1. 空中网4k/5k月薪挑选大四实习生的线程题

    空中网4k/5k月薪挑选大四实习生的线程题 两年前,我们一个大四的学员去应聘空中网的实习生职位,空中网只给他出了三道线程题,拿回家做两天后再去给经理讲解,如果前两题做好了给4k月薪,第三道题也做出来的 ...

  2. 剑指offer 链表中环的入口位置

    题目描述 一个链表中包含环,请找出该链表的环的入口结点.   思路:这题需要知道a = c,然后head和slow每次走一步,相遇的时候就是第一个入口交点, 注意:for循环或者while循环之后,一 ...

  3. 8个最常用的Python内置函数,小白必备!

    题图:Photo by Markus Spiske on Unsplash Python给我们内置了大量功能函数,官方文档上列出了69个,有些是我们是平时开发中经常遇到的,也有一些函数很少被用到,这里 ...

  4. 吴裕雄--天生自然java开发常用类库学习笔记:Math与Random类

    public class MathDemo01{ public static void main(String args[]){ // Math类中的方法都是静态方法,直接使用“类.方法名称()”的形 ...

  5. Ubuntu1804 双网卡的设置

    在WIFI模式下,既可以使用ping开发板,又可上网.     目标:ubuntu1804下使用两个网卡 网卡(eth0):用于桥接主机的物理网卡 网卡(eth1):用于NAT模式下共享主机IP,用于 ...

  6. writeObiect与序列化反序列化

    在写仿QQ聊天工具中,客户端与服务端之间的通信一开始是采用的是InputStream和OutputStream,这导致在数据传输过程中,登录信息,注册信息等难以区分,这时我给传输的数据加了标识来分辨信 ...

  7. local feature和global feature的理解

    在计算机视觉方面,global feature是基于整张图像提取的特征,也就是说基于all pixels,常见的有颜色直方图.形状描述子.GIST等:local feature相对来说就是基于局部图像 ...

  8. MAC设置允许任何来源

    在升级了macOS Sierra (10.12)版本后在“安全性与隐私”中不再有“任何来源”选项 接下来,我们就打开终端,然后输入以下命令: sudo spctl --master-disable 输 ...

  9. c++输出哈夫曼编码

    #include<iostream> #include<string> using namespace std; struct huffTree { int parent; i ...

  10. maven启动报错No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?

    [INFO] Scanning for projects... [WARNING] [WARNING] Some problems were encountered while building th ...