Java 1.8 函数式编程详解

Java 1.8 函数式编程详解

一. 概述

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就是一个函数式接口
      • 代码如下:
        	@FunctionalInterface
        	public 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() {
      		@Override
      		public 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>(){
      	@Override
      	public 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 的抽象方法 test
    			 res.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() {
                  @Override
                  public 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() {
              @Override
              public 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() {
            @Override
            public 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};    // 千万不要用int
      	Stream.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类似。
发布了127 篇原创文章 · 获赞 52 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_37128049/article/details/104748123