Java 1.8 函数式编程详解

文章目录

  • Java 1.8 函数式编程详解
    • 一. 概述
      • 1.1 java 8 新特性:
    • 二. 函数式接口
      • 2.1 函数式接口概述
      • 2.2 Lambda表达式概述
      • 2.3 java.util.function介绍
      • 2.4 实战演示函数式编程
      • 2.5 函数式接口之 Consumer
      • 2.6 函数式接口之 Function
      • 2.7 Function包结构介绍
    • 三. Stream流处理
      • 3.1 概述
      • 3.2 Stream流对象的创建
      • 3.3 Stream流对象的使用
      • 3.4 对象流的使用--详解
      • 3.5 补充

一. 概述

1.1 java 8 新特性:

  • 概述:

    • Java 8 正式版是一个有重大改变的版本,该版本对 Java 做了重大改进。本文章主要讲述java 1.8 函数式编程,主要涉及:函数式接口Lambda 表达式集合的流式操作;
  • 新特性如下:
    • 函数式接口
    • Lambda 表达式
    • 集合的流式操作
    • 注解
    • 安全性
    • IO/NIO
    • 全球化功能

二. 函数式接口

2.1 函数式接口概述

  • java 1.8 引入的一个核心概念是函数式接口(Functional Interfaces)。通过在接口里面添加一个抽象方法,这些方法可以直接从接口中运行。如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了 @FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错。

    • java.lang.Runnable就是一个函数式接口

      • 代码如下:

            @FunctionalInterfacepublic interface Runnable {public abstract void run();}
        
  • 函数式接口,总的来说它是指有且只有一个未实现的方法的接口,一般通过Functionallterface这个注解来表明某个接口是一个函数式接口。函数式接口是java支持函数式编程的基础;

    函数式编程语法,能够精简代码;

2.2 Lambda表达式概述

  • 函数式接口的重要属性是:我们能够使用 Lambda 实例化它们,Lambda 表达式让你能够将函数作为方法参数,或者将代码作为数据对待。Lambda 表达式的引入给开发者带来了不少优点:在 Java 8 之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差,Lambda 表达式的应用则使代码变得更加紧凑,可读性增强;Lambda 表达式使并行操作大集合变得很方便,可以充分发挥多核 CPU 的优势,更易于为多核处理器编写代码;
  • Lambda 表达式由三个部分组成:第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;第二部分为一个箭头符号:->;第三部分为方法体,可以是表达式和代码块。语法如下:
    • 方法体为表达式,该表达式的值作为返回值返回

      (parameters)-> expression
      
    • 方法体为代码块,必须用 {} 来包裹起来,且需要一个 return 返回值,但若函数式接口里面方法返回值是 void,则无需返回值。

      (parameters) -> {statements;}
      

      例如,下面是使用匿名内部类和Lambda表达式的代码比较。

    • 下面是使用匿名内部类的代码:

          button.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {System.out.print("Helllo Lambda in actionPerformed");}});下面是使用 Lambda 表达式后:button.addActionListener(\\actionPerformed 有一个参数 e 传入,所以用 (ActionEvent e)(ActionEvent e)-> System.out.print("Helllo Lambda in actionPerformed"));
      
    • 上面是方法体包含了参数传入 (ActionEvent e),如果没有参数则只需 ( ),例如 Thread 中的 run 方法就没有参数传入,当它使用 Lambda 表达式后:

          Thread t = new Thread(() -> { System.out.println("Hello from a thread in run");});   \\run 没有参数传入,所以用 (), 后面用 {} 包起方法体通过上面两个代码的比较可以发现使用 Lambda 表达式可以简化代码,并提高代码的可读性。为了进一步简化 Lambda 表达式,可以使用方法引用。例如,下面三种分别是使用内部类,使用 Lambda 表示式和使用方法引用方式的比较://1. 使用内部类Function<Integer, String> f = new Function<Integer,String>(){@Overridepublic String apply(Integer t) {return null;}};//2. 使用 Lambda 表达式Function<Integer, String> f2 = (t)->String.valueOf(t); //3. 使用方法引用的方式Function<Integer, String> f1 = String::valueOf;
      
  • 要使用 Lambda 表达式,需要定义一个函数式接口,这样往往会让程序充斥着过量的仅为 Lambda 表达式服务的函数式接口。为了减少这样过量的函数式接口,Java 8 在 java.util.function 中增加了不少新的函数式通用接口。例如:
    • Function<T, R>:将 T 作为输入,返回 R 作为输出,他还包含了和其他函数组合的默认方法。
    • Predicate :将 T 作为输入,返回一个布尔值作为输出,该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(与、或、非)。
    • Consumer :将 T 作为输入,不返回任何内容,表示在单个参数上的操作.
     例如,People 类中有一个方法 getMaleList 需要获取男性的列表,这里需要定义一个函数式接口           PersonInterface:interface PersonInterface {public boolean test(Person person);}public class People {private List<Person> persons= new ArrayList<Person>();public List<Person> getMaleList(PersonInterface filter) {List<Person> res = new ArrayList<Person>();persons.forEach((Person person) -> {if (filter.test(person)) {//调用 PersonInterface 的方法res.add(person);}});return res;}}// 为了去除 PersonInterface 这个函数式接口,可以用通用函数式接口 Predicate 替代如下:class People{private List<Person> persons= new ArrayList<Person>();public List<Person> getMaleList(Predicate<Person> predicate) {List<Person> res = new ArrayList<Person>();persons.forEach(person -> {if (predicate.test(person)) {//调用 Predicate 的抽象方法 testres.add(person);}});return res;}}
    

2.3 java.util.function介绍

  • 概述: java.util.function它包含了很多类,用来支持java的函数式编程,该包中的函数式接口有:

    序号 接口 描述
    1 BiConsumer<T,U> 代表了一个接受两个参数的操作,并且不返回任何结果
    2 BiFunction<T,U,R> 代表了一个接受两个输入参数的方法,并且返回一个结果
    3 BinaryOperator 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
    4 BiPredicate<T,U> 代表了一个两个参数的boolean值方法
    5 BooleanSupplier 代表了boolean值结果的提供方
    6 Consumer 代表了接受一个输入参数并且无返回的操作
    7 DoubleBinaryOperator 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
    8 DoubleConsumer 代表一个接受double值参数的操作,并且不返回结果
    9 DoubleFunction 代表接受一个double值参数的方法,并且返回结果
    10 DoublePredicate 代表一个拥有double值参数的boolean值方法
    11 DoubleSupplier 代表一个double值结构的提供方
    12 DoubleToIntFunction 接受一个double类型输入,返回一个int类型结果
    13 DoubleToLongFunction 接受一个double类型输入,返回一个long类型结果
    14 DoubleUnaryOperator 接受一个参数同为类型double,返回值类型也为double 。
    15 Function<T,R> 接受一个输入参数,返回一个结果。
    16 IntBinaryOperator 接受两个参数同为类型int,返回值类型也为int
    17 IntConsumer 接受一个int类型的输入参数,无返回值 。
    18 IntFunction 接受一个int类型输入参数,返回一个结果 。
    19 IntPredicate 接受一个int输入参数,返回一个布尔值的结果。
    20 IntSupplier 无参数,返回一个int类型结果。
    21 IntToDoubleFunction 接受一个int类型输入,返回一个double类型结果 。
    22 IntToLongFunction 接受一个int类型输入,返回一个long类型结果。
    23 IntUnaryOperator 接受一个参数同为类型int,返回值类型也为int 。
    24 LongBinaryOperator 接受两个参数同为类型long,返回值类型也为long。
    25 LongConsumer 接受一个long类型的输入参数,无返回值。
    26 LongFunction 接受一个long类型输入参数,返回一个结果。
    27 LongPredicate R接受一个long输入参数,返回一个布尔值类型结果。
    28 LongSupplier 无参数,返回一个结果long类型的值
    29 LongToDoubleFunction 接受一个long类型输入,返回一个double类型结果
    30 LongToIntFunction 接受一个long类型输入,返回一个int类型结果。
    31 LongUnaryOperator 接受一个参数同为类型long,返回值类型也为long。
    32 ObjDoubleConsumer 接受一个object类型和一个double类型的输入参数,无返回值。
    33 ObjIntConsumer 接受一个object类型和一个int类型的输入参数,无返回值。
    34 ObjLongConsumer 接受一个object类型和一个long类型的输入参数,无返回值。
    35 Predicate 接受一个输入参数,返回一个布尔值结果。
    36 Supplier 无参数,返回一个结果。
    37 ToDoubleBiFunction<T,U> 接受两个输入参数,返回一个double类型结果
    38 ToDoubleFunction 接受一个输入参数,返回一个double类型结果
    39 ToIntBiFunction<T,U> 接受两个输入参数,返回一个int类型结果。
    40 ToIntFunction 接受一个输入参数,返回一个int类型结果。
    41 ToLongBiFunction<T,U> 接受两个输入参数,返回一个long类型结果。
    42 ToLongFunction 接受一个输入参数,返回一个long类型结果。
    43 UnaryOperator 接受一个参数为类型T,返回值类型也为T。

2.4 实战演示函数式编程

  • Lambda初探:

    • 定义Student接口:

      public interface Student {void xuexi();}
      
    • 实现此接口:
          public static void main(String[] args) {Student student=new Student() {@Overridepublic void xuexi() {System.err.println("学生的每天学习!");}};}
      
      • 使用lambda表达式简略:
      public static void main(String[] args) {Student student= () -> System.err.println("学生的每天学习!");
      }
      
         > 能使用lambda表达式的条件: 1. 是一个接口   2. 只有一个抽象方法    这里因为没有参数所以括号内的内容为空;
      
  • Lambda表达式,有参数初探:

    • 定义Student接口:

      public interface Student {void xuexi(String content);
      }
      
    • 实现此接口:
          public static void main(String[] args) {Student student= new Student() {@Overridepublic void xuexi(String content) {System.err.println("学生的"+content+"每天学习!");}};student.xuexi("任务是");
      }
      
    • 使用Lambda表达式优化:
          public static void main(String[] args) {Student student= content -> System.err.println("学生的"+content+"每天学习!");student.xuexi("任务是");
      }
      

    控制台打印结果如下:

  • 什么是函数式接口:一个接口只有一个抽象方法,它就是函数式接口;

  • 什么是函数式编程:满足函数式接口,并且使用此接口用lambda表达式写法时,就用到了函数式编程了;

上面示例已经说明,函数式编程接口都只有一个抽象方法,因此在采用这种写法时,编译器会将这段函数编译后当做该抽象方法的实现。 如果接口与有多个抽象方法,编译器就不知道这段函数式应该实现哪个方法了。

  • 语法:

    • 输入-> 前面的部分,即被()包围的部分。此处只有一个输入参数,实际上输入可以有多个的,如两个参数时的写法:(a,b);当然也可以没有输入,此时直接就可以是();
    • 函数体: ->后面的部分,即被{}包围的部分;可以是一段代码。
    • 输出:函数式编程可以没有返回值,也可以有返回值。如果有返回值时,需要代码段的最后一句通过return的方式返回对应的值。
  • 简略语法:

    • 当函数体中只有一个语句时,可以去掉{}进一步简化:

      • 比如一段代码:

        Consumer c = new Consumer() {@Overridepublic void accept(Object o) {System.out.println(o);}
        };
        
      • 转为lambda后:
        Consumer c = (o) -> System.out.println(o);
        
    • 然而这还不是最简的,由于此处只是进行打印,调用了System.out中的println静态方法对输入参数直接进行打印,因此可以简化成以下写法:
      Consumer c = System.out::println;
      

      System.out 表示要调用的哪个类 :: 后面的表示调用这个类的某个方法; 我们不但可以使用println,还可以使用这个类的其他方法进行替换;

  • 总结:
    通过最后一段代码,我们可以简单的理解函数式编程,Consumer接口直接就可以当成一个函数了,这个函数接收一个输入参数,然后针对这个输入进行处理;当然其本质上仍旧是一个对象,但我们已经省去了诸如老方式中的对象定义过程,直接使用一段代码来给函数式接口对象赋值。
    而且最为关键的是,这个函数式对象因为本质上仍旧是一个对象,因此可以做为其它方法的参数或者返回值,可以与原有的代码实现无缝集成!

2.5 函数式接口之 Consumer

  • 概述: Consumer是一个函数式编程接口:顾名思义,Consumer的意思就是消费,即针对某个东西我们来使用它,因此它包含有一个有输入而无输出的accept接口方法;除accept方法,它还包含有andThen这个方法;
  • 其定义如下:
    default Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
    }
    
  • 可见这个方法就是指定在调用当前Consumer后是否还要调用其它的Consumer;使用示例:
    public static void consumerTest() {Consumer f = System.out::println;
    Consumer f2 = n -> System.out.println(n + "-F2");//执行完F后再执行F2的Accept方法
    f.andThen(f2).accept("test");//连续执行F的Accept方法
    f.andThen(f).andThen(f).andThen(f).accept("test1");
    }
    
  • 实战实例:
    • Student学生类

      /*** @program: * @description:* @author: anyu* @create: 2020-03-09*/
      @Data
      public class Student implements Serializable {private String name;private String age;
      }
      
    • 测试类:
          public static void main(String[] args) {Consumer consumer = x -> System.out.println(JSON.toJSONString(x));Student student = new Student();student.setAge("18");consumer.accept(student);
      }
      

      在不需要返回参数的情况下,我们可以使用Consumer接口来实现函数式编程;控制台打印结果如图:

2.6 函数式接口之 Function

  • 概述:Function也是一个函数式编程接口;它代表的含义是“函数”,而函数经常是有输入输出的,因此它含有一个apply方法,包含一个输入与一个输出;\
  • 除apply方法外,它还有compose与andThen及indentity三个方法,其使用见下述示例;
    /*** Function测试*/
    public static void functionTest() {Function<Integer, Integer> f = s -> s++;Function<Integer, Integer> g = s -> s * 2;/*** 下面表示在执行F时,先执行G,并且执行F时使用G的输出当作输入。* 相当于以下代码:* Integer a = g.apply(1);* System.out.println(f.apply(a));*/System.out.println(f.compose(g).apply(1));/*** 表示执行F的Apply后使用其返回的值当作输入再执行G的Apply;* 相当于以下代码* Integer a = f.apply(1);* System.out.println(g.apply(a));*/System.out.println(f.andThen(g).apply(1));/*** identity方法会返回一个不进行任何处理的Function,即输出与输入值相等; */System.out.println(Function.identity().apply("a"));
    }
    
  • 在使用函数式编程的时候,我们不但可以去操作对象,还可以操作数组,操作基本数据类型,操作集合等等;接下来给大家实战示例一下:
    • 使用对象

          public static void main(String[] args) {Function<Student, String> g = Student::getName;Student student = new Student();student.setName("张三");String apply = g.apply(student);System.err.println(apply);
      }
      

      g 是定义的一个方法;第二步创建了一个对象;当把这个对象作为参数调用apply方法执行时,就会获得这个对象的名称作为String返回;拿到这个String返回时并打印到控制台,如图所示:

    • 使用数组
          public static void main(String[] args) {Function<Student[], String> g = students->students[0].getName() ;           // 操作数组,打印数组中的Student对象的getName值Student student = new Student();       // 定义对象student.setName("暗余");Student[] arr=new Student[]{student};       // 定义数组并将此对象传入String apply = g.apply(arr);            // 执行Function方法并获取到返回值System.err.println(apply);              // 打印返回值
      }
      
    • 使用 基本数据类型:
          public static void main(String[] args) {Function<Integer,Integer> function= x-> x+1;System.out.println(function.apply(5));
      }
      

      结果控制台打印为 : 6

    • 使用 集合:
          public static void main(String[] args) {Function<List<Student>,List<String>> function= x-> {List<String> list= Lists.newArrayList();for (Student student : x) {list.add(student.getAge());}return list;};Student student1=new Student();student1.setAge("18");Student student2 =new Student();student2.setAge("19");List<Student> list=Lists.newArrayList();Collections.addAll(list, student1, student2);List<String> apply = function.apply(list);System.err.println(JSON.toJSONString(apply));
      }
      
      • 上面的Function方法,是将Student对象的List转为String集合,只提取每个对象中的Age内容;由于逻辑较多无法通过一行代码完成。但是我们使用Stream流,可以继续简化。如下图所示:
          public static void main(String[] args) {Function<List<Student>,List<String>> function= x->x.stream().map(y-> y.getAge()).collect(Collectors.toList());Student student1=new Student();student1.setAge("18");Student student2 =new Student();student2.setAge("19");List<Student> list=Lists.newArrayList();Collections.addAll(list, student1, student2);List<String> apply = function.apply(list);System.err.println(JSON.toJSONString(apply));}
      

      很酷很优雅有没有 >< 下面将会为大家介绍Stream流;

2.7 Function包结构介绍

  • 概述:当我们使用Function定义函数时,不但可以使用Function,还可以使用BiFunction以及BiConsumer等接口。

  • 常用函数式接口表如下:

    接口 描述
    Function<T,R> 接受一个输入参数,返回一个结果
    Supplier 无参数,返回一个结果
    Consumer 接受一个输入参数,并且不返回任何结果
    BiFunction<T,U,R> 接受两个输入参数的方法,并且返回一个结果
    BiConsumer<T,U> 接受两个输入参数的操作,并且不返回任何结果

三. Stream流处理

3.1 概述

  • 概述: 说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带 来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
  • 传统集合的多步遍历代码:几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元 素进行操作的时候,除了必需的添加、删除、获取外,典型的就是集合遍历,在遍历的过程中再对集合内的数据进行处理,代码往往会写很多。
  • 笔者概述:Stream流就是将集合放入一个工厂流水线中,进行一系列的如:过滤跳过映射转换等操作,最后拿到我们需要的数据,如统计长度,新集合新map等。就是对一个集合进行一系列处理,最后直接拿到我们想要的结果,并且代码量得到了大幅度的缩减,甚至一行代码完成了平时二十行代码达不到的效果!

3.2 Stream流对象的创建

  • Stream创建的方式有多种,分别如下:

    • 创建空的Stream对象

      Stream stream=Stream.empty();
      
    • 通过集合类中的stream或者parallelStream方法创建
      List<String> list = Arrays.asList("a", "b", "c", "d");
      Stream listStream = list.stream();                   //获取串行的Stream对象
      Stream parallelListStream = list.parallelStream();   //获取并行的Stream对象
      
    • 通过Stream 中的of方法(可变参数)创建
      Stream s= Stream.of("test");
      Stream s1 =Stream.of("a","b","c","d");
      

      of 内可以接受多种类型,比如数组也可以接收,代码如下:

                 int [] a=new int[]{10,11};Stream<int[]> a1 = Stream.of(a);
      
    • 通过Stream 中的iterate方法创建
      public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f);
      public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
      
    • 通过范围创建数值流
      IntStream.range(0,100);         //不包含最后一个数
      IntStream.rangeClosed(0,99);        //包含最后一个数
      

3.3 Stream流对象的使用

  • 概述:Stream对象提供多个非常有用的方法,这些方法可以分成两类:

    • 中间操作:将原始的Stream转换成另外一个Stream;如filter返回的是过滤后的Stream。
    • 终端操作:产生的是一个结果或者其它的复合操作;如count或者forEach操作。

    通过调用中间操作的方法,我们可以将Stream中的数据进行一系列的操作,如过滤跳过,求最大值,最小值排序转换等一系列的过滤或转换或聚类等;

  • 其清单如下所示,方法的具体说明及示例等请见后面:

  • 中间操作如表:

    方法 说明
    sequential 返回一个相等的串行的Stream对象,如果原Stream对象已经是串行就可能会返回原对象
    parallel 返回一个相等的并行的Stream对象,如果原Stream对象已经是并行的就会返回原对象
    unordered 返回一个不关心顺序的Stream对象,如果原对象已经是这类型的对象就会返回原对象
    onClose 返回一个相等的Steam对象,同时新的Stream对象在执行Close方法时会调用传入的Runnable对象
    close 关闭Stream对象
    filter 元素过滤:对Stream对象按指定的Predicate进行过滤,返回的Stream对象中仅包含未被过滤的元素
    map 元素一对一转换:使用传入的Function对象对Stream中的所有元素进行处理,返回的Stream对象中的元素为原元素处理后的
    mapToInt 元素一对一转换:将原Stream中的使用传入的IntFunction加工后返回一个IntStream对象
    flatMap 元素一对多转换:对原Stream中的所有元素进行操作,每个元素会有一个或者多个结果,然后将返回的所有元素组合成一个统一的Stream并返回;
    distinct 去重:返回一个去重后的Stream对象
    sorted 排序:返回排序后的Stream对象
    peek 使用传入的Consumer对象对所有元素进行消费后,返回一个新的包含所有原来元素的Stream对象
    limit 获取有限个元素组成新的Stream对象返回
    skip 抛弃前指定个元素后使用剩下的元素组成新的Stream返回
    takeWhile 如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。
    dropWhile 与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream。
  • 终端操作如表:

    方法 说明
    iterator 返回Stream中所有对象的迭代器;
    spliterator 返回对所有对象进行的spliterator对象
    forEach 对所有元素进行迭代处理,无返回值
    forEachOrdered 按Stream的Encounter所决定的序列进行迭代处理,无返回值
    toArray 返回所有元素的数组
    reduce 使用一个初始化的值,与Stream中的元素一一做传入的二合运算后返回最终的值。每与一个元素做运算后的结果,再与下一个元素做运算。它不保证会按序列执行整个过程。
    collect 根据传入参数做相关汇聚计算
    min 返回所有元素中最小值的Optional对象;如果Stream中无任何元素,那么返回的Optional对象为Empty
    max 与Min相反
    count 所有元素个数
    anyMatch 只要其中有一个元素满足传入的Predicate时返回True,否则返回False
    allMatch 所有元素均满足传入的Predicate时返回True,否则False
    noneMatch 所有元素均不满足传入的Predicate时返回True,否则False
    findFirst 返回第一个元素的Optioanl对象;如果无元素返回的是空的Optional; 如果Stream是无序的,那么任何元素都可能被返回。
    findAny 返回任意一个元素的Optional对象,如果无元素返回的是空的Optioanl。

|isParallel| 判断是否当前Stream对象是并行的|

3.4 对象流的使用–详解

  • Filter:

    • 概述: 用于对Stream中的元素进行过滤,返回一个过滤后的Stream;
    • 方法定义:
          Stream<T> filter(Predicate<? super T> predicate);
      
    • 使用示例:
      • 定义Student.java学生类:
      /*** @program:* @description:* @author: anyu* @create: 2020-03-09*/
      @Data
      public class Student implements Serializable {private String name;private String age;
      }
      
      • 定义测试类:
          public static void main(String[] args) {Student student1=new Student();student1.setAge("18");Student student2 =new Student();student2.setAge("19");List<Student> list=Lists.newArrayList();Collections.addAll(list, student1, student2);long count = list.stream().filter(x -> x.getAge().equals("18")).count();            // 打印出list集合内年龄等于18岁的数据总数System.err.println(count);
      }
      

      这里定义了一个有年龄对象的集合,使用filter能够过滤出年龄等于18岁的个数。实际上不使用.count,用其他的终结语句还能转换为另一个集合,或者找出等于18岁的这个数据的具体对象;

  • map:

    • 概述:它能对stream流内的元素进行转换,通过映射将元素依次进行转换处理;
    • 方法定义:
      <R> Stream<R> map(Function<? super T, ? extends R> mapper);
      
    • 使用示例:
          public static void main(String[] args) {Student student1=new Student();student1.setAge("18");Student student2 =new Student();student2.setAge("19");List<Student> list=Lists.newArrayList();Collections.addAll(list, student1, student2);List<String> collect = list.stream().map(x -> x.getAge() + "岁").collect(Collectors.toList());System.err.println(JSON.toJSONString(collect));
      }
      

      这里的 x-> x… 里面的x可以用其他字母代替,它只是一个替代名称,不具备实际意义,表示集合内的这一个元素;
      通过map集合,我们可以将每个值进行映射,带了一个岁的后缀,当使用collect终结stream时,就转换成了一个新的stream流了。代码运行后控制台打印结果如下:

  • flatMap:

    • 概述: 元素一对多转换:对原Stream中的所有元素使用传入的Function进行处理,每个元素经过处理后生成一个多个元素的Stream对象,然后将返回的所有Stream对象中的所有元素组合成一个统一的Stream并返回。
    • 方法定义:
      <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
      
    • 使用示例:
          public static void main(String[] args) {Student student1 = new Student();student1.setAge("18");student1.setName("张三啊");Student student2 = new Student();student2.setAge("19");student2.setName("李四啊");List<Student> list = Lists.newArrayList();Collections.addAll(list, student1, student2);List<String> collect = list.stream().flatMap(x->Stream.of(x.getName())).collect(Collectors.toList());System.err.println(JSON.toJSONString(collect));
      }
      
    > 这里的flatMap可以将每个元素再Stream流化,能够进行更细粒度的操作。比起Map的一对一,它的功能更为强大,具体的区别可百度。
    
  • takeWhile:

    • 概述:taskWhile用于Stream流的过滤操作,它筛选出符合条件的数据。当Stream流为有序Stream时,当遇到不符合条件的元素时,即进行终止。当Stream流为无序时,返回符合条件的所有元素;

      它与Filter有点类似,不同之处在于当Stream是有序的,它遇到不符合条件的就会停止,而Filter会继续筛选。

    • 方法定义:
      default Stream<T> takeWhile(Predicate<? super T> predicate)
      
    • 代码示例:
      Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
      //以下结果将打印: "test", "t1", "t2", "teeeee",最后的那个taaa不会进行打印
      s.takeWhile(n -> n.contains("t")).forEach(System.out::println);
      
  • dropWhile:

    • 概述:与takeWhile相反,返回takeWhile得不到的数据。它返回指定范围外的数据。当Stream是有序的,它遇到不符合条件的就会停止,然后拿取第一个不符合条件的后面的全部数据;
    • 方法定义:
      default Stream<T> dropWhile(Predicate<? super T> predicate)
      
  • forEach

    • 概述:forEach可以说是最常见的操作了,甚至对于List等实现了Collection接口的类可以不创建Stream而直接使用forEach。简单地说,forEach就是遍历并执行某个操作。
    • 代码示例:
          public static void main(String[] args) {Student student1 = new Student();student1.setAge("18");student1.setName("张三啊");Student student2 = new Student();student2.setAge("19");student2.setName("李四啊");Student student3=new Student();student3.setAge("20");student3.setName("王五啊");List<Student> list = Lists.newArrayList();Collections.addAll(list, student1, student2,student3);list.forEach(x-> System.err.println(x.getAge()));
      }
      

      使用Foreach能够对集合进行遍历,并进行一系列操作。集合可以直接使用foreach;控制台打印结果如下:

  • sorted和distinct:

    • 概述: sorted表示排序,distinct表示去重;
    • 代码示例:
         Integer[] arr = new Integer[]{5, 1, 2, 1, 3, 1, 2, 4};    // 千万不要用intStream.of(arr).sorted().forEach(System.out::println);Stream.of(arr).distinct().forEach(System.out::println);Stream.of(arr).distinct().sorted().forEach(System.out::println);
      
  • collect:

    • 概述:在流操作中,我们往往需求是从一个List得到另一个List,而不是直接通过forEach来打印。那么这个时候就需要使用到collect了。
    • 示例如下:
          public static void main(String[] args) {Student student1 = new Student();student1.setAge("18");student1.setName("张三啊");Student student2 = new Student();student2.setAge("19");student2.setName("李四啊");Student student3=new Student();student3.setAge("20");student3.setName("王五啊");List<Student> list = Lists.newArrayList();Collections.addAll(list, student1, student2,student3);List<Student> collect = list.stream().filter(x -> x.getAge().equals("19")).collect(Collectors.toList());System.err.println(JSON.toJSONString(collect));
      }
      

      通过过滤出年龄等于19岁的元素,然后转为新的list,打印时就可以得到新的list集合了。打印结果如图所示:

3.5 补充

  • 并行流:除了普通的stream之外还有parallelStream,区别比较直观,就是stream是单线程执行,parallelStream为多线程执行。parallelStream的创建及使用基本与Stream类似。

Java 1.8 函数式编程详解相关推荐

  1. Java8函数式编程详解

    Java8 函数式编程详解 Author:Dorae Date:2017年11月1日23:03:26 转载请注明出处 说起Java8,可能很多人都已经知道其最大的改进,就是引入了Lambda表达式与S ...

  2. java程序的界面编程详解

    java程序的界面编程详解 在Java中可以为程序自定义程序界面选择Windows.Unix.Java或Macintosh外观.甚至可以在程序运行时让用户自由的选择外观. UIManager和Swin ...

  3. JS中函数式编程详解版(FunctionalProgramming,FP)

    函数式编程详解 函数式编程的认识 函数式编程前置知识 函数是一等公民(First-class Function) 高阶函数 闭包 函数式编程基础 纯函数 lodash 模块 柯里化 函数组合 函子 函 ...

  4. 【转】Java8 函数式编程详解

    1.Java函数式编程的语法: 使用Consumer作为示例,它是一个函数式接口,包含一个抽象方法accept,这个方法只有输入而无输出也就是说这个方法无返回值. 现在我们要定义一个Consumer接 ...

  5. Java高并发编程详解系列-Java线程入门

    根据自己学的知识加上从各个网站上收集的资料分享一下关于java高并发编程的知识点.对于代码示例会以Maven工程的形式分享到个人的GitHub上面.   首先介绍一下这个系列的东西是什么,这个系列自己 ...

  6. java IO编程详解

    java IO编程详解 一.Socket 1. Sock概述 Socket,套接字就是两台主机之间逻辑连接的端点.TCP/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP协议是应用层协议 ...

  7. Java JUC并发编程详解

    Java JUC并发编程详解 1. JUC概述 1.1 JUC简介 1.2 进程与线程 1.2 并发与并行 1.3 用户线程和守护线程 2. Lock接口 2.1 Synchronized 2.2 什 ...

  8. 【java8新特性】——lambda表达式与函数式接口详解(一)

    一.简介 java8于2014年发布,相比于java7,java8新增了非常多的特性,如lambda表达式.函数式接口.方法引用.默认方法.新工具(编译工具).Stream API.Date Time ...

  9. 【Java基础】HashMap原理详解

    [Java基础]HashMap原理详解 HashMap的实现 1. 数组 2.线性链表 3.红黑树 3.1概述 3.2性质 4.HashMap扩容死锁 5. BATJ一线大厂技术栈 HashMap的实 ...

最新文章

  1. C++库文件和头文件编写教程
  2. 如何增加Eclipse输出控制台的容量?
  3. logback-spring.xml 文件路径 相对路径_小白学 Python(18):基础文件操作
  4. 精读linux源码,Linux基础入门的操作精读.doc
  5. 常用技巧 —— 离散化
  6. 【java笔记】线程(5):线程安全问题
  7. macOS下JetBrains配置修改错误导致无法启动解决方案
  8. 商务与经济统计 第三章案例
  9. 在c语言中作为字符串结束标志是什么,字符串的结束标志是什么?
  10. 移动端设计的基础尺寸单位与转化
  11. 什么时候需要消息队列
  12. 输入某年某日,判断这一天是这一年的第几天
  13. python图像几何变换_python 图像工具opencv3实例(对象识别和增强现实)1-图像几何转换...
  14. vue2 typescript 项目 如何引入antd -ui组件
  15. vue elementui 表格搜索筛选栏组件封装
  16. 备受瞩目的“2017全球云计算大会”有哪些亮点值得关注?
  17. 比较两个结构体是否相等
  18. linux学习笔记-rsync原理及使用
  19. 服务器测试之网卡bond测试
  20. Rsync 实现 Windows 与 CentOS 之间数据同步

热门文章

  1. 浅谈NAND FLASH的两种编程方式
  2. Evil.js源码解读
  3. 金佩姗 - 决 歌词
  4. mysql创建服务失败_mysql服务启动失败
  5. 神奇的脑波,关乎睡眠与创造力提升
  6. Android动画案例(二)补间动画
  7. 「分布式计算」什么是严格一致性和最终一致性?
  8. Android禁用分屏
  9. HTML5移动Web开发实战 PDF扫描版​
  10. 红米ac2100有ipv6吗_白里透着红,跑得相当快,红米AC2100路由器体验