Java 1.8 函数式编程详解

本文详细介绍了Java1.8中的函数式编程特性,包括函数式接口、Lambda表达式和Stream流处理等内容。深入探讨了如何利用这些新特性简化代码,提升程序的可读性和并行处理能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暗余

码字来之不易,您的鼓励我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值