新特性>lambda & stream

新特性lambda & stream

Lambda(简化函数式接口生成的匿名内部类)

Java的函数式编程是一种编程范式,它强调使用函数作为一等公民(first-class citizens),即函数可以像其他数据类型一样被赋值给变量、作为参数传递给其他函数或作为其他函数的返回值。

在Java中,函数式编程的特性主要通过Lambda表达式、方法引用和函数式接口来实现

lambda表达式其实就是实现SAM接口的语法糖,使得Java也算是支持函数式编程的语言。Lambda写的好可以极大的减少代码冗余,同时可读性也好过冗长的匿名内部类。

备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实
底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部
类的“语法糖”,但是二者在原理上是不同的。

@Test
public void test2(){
        String[] arr = {"hello","java","hi","chai","atguigu"};
        //这个需求中我们关心的是什么? 如何比较两个字符串的大小,至于对象不重要
        Arrays.sort(arr, (o1, o2) -> Integer.compare(o1.length(), o2.length()));
        System.out.println(Arrays.toString(arr));
}

函数接口的概念

Lambda表达式其实就是实现SAM接口的语法糖,所谓SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法。

其实只要满足“SAM”特征的接口都可以称为函数式接口,都可以使用Lambda表达式,但是如果要更明确一点,最好在声明接口时,加上@FunctionalInterface。一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

Lambda表达式语法

Lambda表达式是用来给“函数式接口”的变量或形参赋值用的。其实本质上,Lambda表达式是用于实现“函数式接口”的抽象方法的语法格式,或者是给函数式接口的变量传递一段实现抽象方法的方法体代码。

Lambda表达式语法格式

(形参列表) -> {Lambda体}

语法格式说明:

  • (形参列表)它就是你要赋值的函数式接口的抽象方法的(形参列表)
  • {Lambda体}就是实现这个抽象方法的方法体
  • ->称为Lambda操作符(减号和大于号中间不能有空格,而且必须是英文状态下半角输入方式)
public class TestLambdaGrammar {
    //匿名内部类
    @Test
    public void test1(){
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("我爱尚硅谷");
            }
        };
    }
    //lambda
    @Test
    public void test2(){
        Runnable r = ()-> { System.out.println("我爱尚硅谷"); };
    }
}

Lambda表达式的简化

  • 当{Lambda体}中只有一句语句时,可以省略{}和{;}
  • 当{Lambda体}中只有一句语句时,并且这个语句还是一个return语句,那么return和{}和{;}三者可以省略。它们三要么一起省略,要么都不省略。
  • 当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
  • 当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,则形参的数据类型和()可以一起省略,但是形参名不能省略。
  • 当Lambda表达式(形参列表)是空参时,()不能省略
@Test
    public void test3(){
        Runnable r = ()-> System.out.println("我爱尚硅谷");
    }


    @Test
    public void test6(){
        String[] arr = {"hello","java","hi","chai","atguigu"};
        //把上面的字符串按照长短排序,从短到长
        Arrays.sort(arr, (o1, o2)-> Integer.compare(o1.length(), o2.length()));
        System.out.println(Arrays.toString(arr));
    }
    
    @Test
    public void test9(){
        File dir = new File("d:\\atguigu");
        File[] subFiles = dir.listFiles(sub->sub.isFile());
        for (File subFile : subFiles) {
            System.out.println(subFile);
        }
    }
}

Java8之后引入的函数式接口

Java8在java.util.function新增了很多函数式接口:主要分为四大类,消费型、供给型、判断型、功能型。基本可以满足我们的开发需求。当然你也可以定义自己的函数式接口。

1、消费型接口

消费型接口的抽象方法特点:有形参,但是返回值类型是void

序号 接口名 抽象方法 描述
1 Consumer void accept(T t) 接收一个对象用于完成功能
2 BiConsumer<T,U> void accept(T t, U u) 接收两个对象用于完成功能
3 DoubleConsumer void accept(double value) 接收一个double值
4 IntConsumer void accept(int value) 接收一个int值
5 LongConsumer void accept(long value) 接收一个long值
6 ObjDoubleConsumer void accept(T t, double value) 接收一个对象和一个double值
7 ObjIntConsumer void accept(T t, int value) 接收一个对象和一个int值
8 ObjLongConsumer void accept(T t, long value) 接收一个对象和一个long值

已知在JDK1.8中java.lang.Iterable接口中增加了一个默认方法:

  • public default void forEach(Consumer<? super T> action) 该方法功能是遍历Collection集合,并将传递给action参数的操作代码应用在每一个元素上。

因为Collection接口继承了Iterable接口,这就意味着所有Collection系列的接口都包含该方法。

package com.atguigu.four;

import java.util.Arrays;
import java.util.List;

public class TestConsumer {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("java","c","python","c++","VB","C#");
        list.forEach(s -> System.out.println(s));
    }
}

2、供给型接口

这类接口的抽象方法特点:无参,但是有返回值

序号 接口名 抽象方法 描述
1 Supplier T get() 返回一个对象
2 BooleanSupplier boolean getAsBoolean() 返回一个boolean值
3 DoubleSupplier double getAsDouble() 返回一个double值
4 IntSupplier int getAsInt() 返回一个int值
5 LongSupplier long getAsLong() 返回一个long值
package com.atguigu.four;

import java.util.function.Supplier;

public class TestSupplier {
    public static void main(String[] args) {
        Supplier<String> supplier = () -> "尚硅谷";
        System.out.println(supplier.get());
    }
}

3、判断型接口

这类接口的抽象方法特点:有参,但是返回值类型是boolean结果。

序号 接口名 抽象方法 描述
1 Predicate boolean test(T t) 接收一个对象
2 BiPredicate<T,U> boolean test(T t, U u) 接收两个对象
3 DoublePredicate boolean test(double value) 接收一个double值
4 IntPredicate boolean test(int value) 接收一个int值
5 LongPredicate boolean test(long value) 接收一个long值

已知:JDK1.8时,Collecton接口增加了一下方法,其中一个如下:

  • public default boolean removeIf(Predicate<? super E> filter) 用于删除集合中满足filter指定的条件判断的。
package com.atguigu.four;

import java.util.ArrayList;

public class TestPredicate {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("atguigu");
        list.add("ok");
        list.add("yes");

        System.out.println("删除之前:");
        list.forEach(t-> System.out.println(t));

        //删除包含o字母的元素
        list.removeIf(s -> s.contains("o"));

        System.out.println("删除包含o字母的元素之后:");
        list.forEach(t-> System.out.println(t));
    }
}

4、功能型接口

这类接口的抽象方法特点:既有参数又有返回值

序号 接口名 抽象方法 描述
1 Function<T,R> R apply(T t) 接收一个T类型对象,返回一个R类型对象结果
2 UnaryOperator T apply(T t) 接收一个T类型对象,返回一个T类型对象结果
3 DoubleFunction R apply(double value) 接收一个double值,返回一个R类型对象
4 IntFunction R apply(int value) 接收一个int值,返回一个R类型对象
5 LongFunction R apply(long value) 接收一个long值,返回一个R类型对象
6 ToDoubleFunction double applyAsDouble(T value) 接收一个T类型对象,返回一个double
7 ToIntFunction int applyAsInt(T value) 接收一个T类型对象,返回一个int
8 ToLongFunction long applyAsLong(T value) 接收一个T类型对象,返回一个long
9 DoubleToIntFunction int applyAsInt(double value) 接收一个double值,返回一个int结果
10 DoubleToLongFunction long applyAsLong(double value) 接收一个double值,返回一个long结果
11 IntToDoubleFunction double applyAsDouble(int value) 接收一个int值,返回一个double结果
12 IntToLongFunction long applyAsLong(int value) 接收一个int值,返回一个long结果
13 LongToDoubleFunction double applyAsDouble(long value) 接收一个long值,返回一个double结果
14 LongToIntFunction int applyAsInt(long value) 接收一个long值,返回一个int结果
15 DoubleUnaryOperator double applyAsDouble(double operand) 接收一个double值,返回一个double
16 IntUnaryOperator int applyAsInt(int operand) 接收一个int值,返回一个int结果
17 LongUnaryOperator long applyAsLong(long operand) 接收一个long值,返回一个long结果
18 BiFunction<T,U,R> R apply(T t, U u) 接收一个T类型和一个U类型对象,返回一个R类型对象结果
19 BinaryOperator T apply(T t, T u) 接收两个T类型对象,返回一个T类型对象结果
20 ToDoubleBiFunction<T,U> double applyAsDouble(T t, U u) 接收一个T类型和一个U类型对象,返回一个double
21 ToIntBiFunction<T,U> int applyAsInt(T t, U u) 接收一个T类型和一个U类型对象,返回一个int
22 ToLongBiFunction<T,U> long applyAsLong(T t, U u) 接收一个T类型和一个U类型对象,返回一个long
15 DoubleBinaryOperator double applyAsDouble(double left, double right) 接收两个double值,返回一个double结果
24 IntBinaryOperator int applyAsInt(int left, int right) 接收两个int值,返回一个int结果
25 LongBinaryOperator long applyAsLong(long left, long right) 接收两个long值,返回一个long结果

已知,java.util.List接口在Java8版本中新增了一个方法:

  • default void replaceAll(UnaryOperator operator)将该列表的每个元素替换为将该运算符应用于该元素的结果。
    @Test
    public void test(){
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("haha");

        //使用Lambda表达式实现Function<T,R>接口的子接口UnaryOperator<T>,
        // 可以实现将一个字符串首字母转为大写的功能。
        list.replaceAll(s -> s.substring(0,1).toUpperCase() + s.substring(1));
        System.out.println(list);
    }

自定义函数式接口

只要确保接口中有且仅有一个抽象方法即可:

修饰符 interface 接口名称 {
    public abstract 返回值类型 方法名称(可选参数信息);
    // 其他非抽象方法内容
}

接口当中抽象方法的 public abstract 是可以省略的

例如:声明一个计算器Calculator<T,R>接口,内含抽象方法calculate可以对两个参数进行计算,并返回结果。其中T是参数类型,R是返回值类型。

package com.atguigu.fi;

@FunctionalInterface
public interface Calculator<T,R> {
    R calculate(T a, T b);
}

例如:声明一个转换器Convertor<T,R>,包含抽象方法change,可以将参数转换为另一个值,并返回结果。其中T是参数类型,R是返回值类型。

package com.atguigu.fi;

@FunctionalInterface
public interface Convertor<T,R> {
    R change(T t);
}

Optional类(解决空指针异常)

序号 构造器或方法 描述
1 static Optional empty() 用来创建一个空的Optional
2 static Optional of(T value) 用来创建一个非空的Optional
3 static Optional ofNullable(T value) 用来创建一个可能是空,也可能非空的Optional
4 T get() 返回Optional容器中的对象。要求Optional容器必须非空。T get()与of(T value)使用是安全的
5 T orElse(T other) 如果Optional容器中非空,就返回所包装值,如果为空,就用orElse(T other)other指定的默认值(备胎)代替。一般orElse(T other) 与ofNullable(T value)配合使用
6 T orElseGet(Supplier<? extends T> other) 如果Optional容器中非空,就返回所包装值,如果为空,就用Supplier接口的Lambda表达式提供的值代替
7 T orElseThrow(Supplier<? extends X> exceptionSupplier) 如果Optional容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException
8 boolean isPresent() 判断Optional容器中的值是否存在
9 void ifPresent(Consumer<? super T> consumer) 判断Optional容器中的值是否存在,如果存在,就对它进行Consumer指定的操作,如果不存在就不做
10 Optional map(Function<? super T,? extends U> mapper) 判断Optional容器中的值是否存在,如果存在,就对它进行Function接口指定的操作,如果不存在就不做
import java.util.Optional;

import org.junit.Test;

public class TestOptional {
 
    @Test
    public void test8(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);
        String string = opt.orElseThrow(()->new RuntimeException("值不存在"));
        System.out.println(string);
    }


    @Test
    public void test7(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);
        String string = opt.orElseGet(()->new String("atguigu"));
        System.out.println(string);
    }

    @Test
    public void test6(){
        String str = "hello";
        Optional<String> opt = Optional.ofNullable(str);
        String string = opt.orElse("atguigu");
        System.out.println(string);
    }

    @Test
    public void test5(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);
//		System.out.println(opt.get());//java.util.NoSuchElementException: No value present
        String string = opt.orElse("atguigu");
        System.out.println(string);
    }

 


    @Test
    public void test3(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);
        System.out.println(opt);
    }

  
}

引用(简化Lambda表达式)

语法格式与使用要求

当Lambda表达式满足一些特殊的情况时,还可以再简化:

(1)Lambda体只有一句语句,并且是通过调用一个对象的/类现有的方法来完成的

(2)并且Lambda表达式的形参正好全部用上,Lambda体中没有额外的数据参与

方法引用:

类名::静态方法名: 重写方法的形参与方法体内 类名调用方法的实参 一一匹配

 //todo 类名::静态方法名:  重写方法的形参与方法体内 类名调用方法的实参一一匹配
        
/*Supplier<Double> s = new Supplier<Double>() {
     @Override
     public Double get() {
       return Math.random();
    }
};*/

//lambda
// Supplier<Double> s = ()-> Math.random();

//方法引用
Supplier<Double> s = Math::random;

System.out.println("s.get() = " + s.get());

对象名::实例方法名: 重写方法的形参 与方法体内 对象调用方法的实参 一一匹配

//todo  对象名::实例方法名: 重写方法的形参与方法体内 对象调用方法的实参一一匹配
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list,"安琪拉","吕布","妲己","项羽");

/* list.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
            System.out.println(s);
     	}
	});*/
//lambda
//list.forEach(a-> System.out.println(a));
//方法引用
list.forEach(System.out::println);

类名::实例方法名: 重写方法的形参只有一个 且是方法体内调用方法的对象

​ 重写方法的形参有多个 第一个是调用方法的对象 ”后续是调用方法的实参”

interface C{
    int size(String s);
}
@Test
public void test05(){
     //todo  类名::实例方法名:  重写方法的形参只有一个 且是方法体内调用方法的对象
       /* C c = new C() {
      @Override
      public int size(String s) {
          return s.length();
       }
   };*/
   //lambda
   // C c = s->s.length();
   //方法引用
   C c = String::length;


   int size = c.size("Hello");
   System.out.println("size = " + size);
}
interface D{
    String d(String a,String b);
}

@Test
public void test06(){
 //todo 类名::实例方法名 : 重写方法的形参有多个 第一个是调用方法的对象后续是调用方法的实参
   /* D d = new D() {
      @Override
       public String d(String a, String b) {
            return a.concat(b);
         }
     } ;*/

//lambda
//  D d = (a,b)-> a.concat(b);

//方法引用
D d = String::concat;

String result = d.d("安琪拉-", "李白");
System.out.println("result = " + result);
}

构造器引用:

类名::new 重写方法的形参与方法体内调用构造器需要的实参匹配

interface E{
    Person e(String s,int a);
}
public void test02(){
 //todo 构造器引用: 类名::new 重写方法的形参与方法体内调用构造器需要的实参一一匹配
   /* E e = new E() {
        @Override
         public Person e(String s, int a) {
            return new Person(s,a);
         }
     };*/
     
//lambda
// E e = (s,a)->new Person(s,a);

//构造器引用
E e = Person::new;

Person person = e.e("张三", 200);
System.out.println("person = " + person);
}

数组引用:

数组类型::new 重写方法的形参与方法体内动态创建数组的实参匹配

   @Test
    public void test03(){
        //todo 数组引用:  数组类型::new 重写方法的形参与方法体内动态创建数组的实参匹配
      /*  Function<Integer,String[]> f = new Function<Integer, String[]>() {
            @Override
            public String[] apply(Integer i) {
                return new String[i];
            }
        };*/
        //lambda
      //  Function<Integer,String[]> f = i-> new String[i];
        //数组引用
        Function<Integer,String[]> f = String[]::new;

        String[] array = f.apply(15);

        System.out.println("array.length = " + array.length);

        System.out.println("Arrays.toString(array) = " + Arrays.toString(array));

}

Function:功能型接口

StreamAPI

Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,负责存储数据,Stream流讲的是计算,负责处理数据!”

注意:

①Stream 自己不会存储元素。

②Stream 不会改变源对象。每次处理都会返回一个持有结果的新Stream。

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream操作的步骤

1- 创建 Stream:通过一个数据源(如:集合、数组),获取一个流

2- 中间操作:每次处理都会返回一个持有结果的新Stream,即中间操作的方法返回值仍然是Stream类型的对象,因此中间操作可以是个操作链,可对数据源的数据进行n次处理,但是在终结操作前,并不会真正执行。

3- 终止操作:终止操作的方法返回值类型就不再是Stream了,因此一旦执行终止操作,就结束整个Stream操作了。一旦执行终止操作,就执行中间操作链,最终产生结果并结束Stream。

1560769396655

创建StreamAPI

1、创建 Stream方式一:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • public default Stream stream() : 返回一个顺序流
  • public default Stream parallelStream() : 返回一个并行流
 @Test
 public void test1(){
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("hi");
        list.add("heihei");
        Stream<String> stream = list.stream();
}

2、创建 Stream方式二:通过数组

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • public static Stream stream(T[] array): 返回一个流
@Test
public void test2(){
    String[] arr = {"hello","world","java"};
    Stream<String> stream = Arrays.stream(arr);
}

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array):返回一个整型数据流
  • public static LongStream stream(long[] array):返回一个长整型数据流
  • public static DoubleStream stream(double[] array):返回一个浮点型数据流

3、创建 Stream方式三:通过Stream的of()

可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。

  • public static Stream of(T... values) : 返回一个顺序流
@Test
public void test3(){
  Stream<String> stringStream = Stream.of("hello", "world", "java");
}

4、创建 Stream方式四:创建无限流

可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。

  • public static Stream iterate(final T seed, final UnaryOperator f):返回一个无限流
  • public static Stream generate(Supplier s) :返回一个无限流
@Test
    public void test4(){
        //Supplier<T> 的抽象方法  T get()
        Stream<Double> stream = Stream.generate(() -> Math.random());

        //结束Stream
        //Consumer<T> 的抽象方法  void accept(T t)
        stream.forEach(t-> System.out.println(t));
}

中间操作API

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。

序号 方 法 描 述
1 Stream filter(Predicate p) 接收 Lambda , 从流中排除某些元素
2 Stream distinct() 筛选,通过流所生成元素的equals() 去除重复元素
3 Stream limit(long maxSize) 截断流,使其元素不超过给定数量
4 Stream skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
5 Stream peek(Consumer action) 接收Lambda,对流中的每个数据执行Lambda体操作
6 Stream sorted() 产生一个新流,其中按自然顺序排序
7 Stream sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
8 Stream map(Function f) 接收一个函数作为参数,该函数会《被应用到每个元素上》,并将其映射成一个新的元素。(对每个参数进行选择性处理)
9 Stream mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
10 Stream mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
11 Stream mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
12 Stream flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
import org.junit.Test;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class TestMiddle {
    @Test
    public void test1(){
        //(1)创建Stream,指定数据源来创建Stream(必须有)
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

        //没有中间处理操作

        //(3)结束处理,获取结果,称为“终结操作”(必须有)
        stream.forEach(t-> System.out.println(t));
    }

    @Test
    public void test2(){
        //(1)创建Stream,指定数据源来创建Stream(必须有)
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5,8,10,12,14);

        //中间处理操作:可以多步
        //filter(Predicate p)
        //Predicate<T> boolean test(T t)
        //找出偶数
        stream = stream.filter(t->t%2==0);
        //Stream 	sorted()  自然排序
//        Stream 	sorted(Comparator com) 定制排序
//       stream =  stream.sorted();//默认升序
        //Comparator<T> int compare(T t1, T t2)
       stream =  stream.sorted((t1,t2) -> t2-t1);//降序


        //(3)结束处理,获取结果,称为“终结操作”(必须有)
        stream.forEach(t-> System.out.println(t));
    }

    @Test
    public void test3(){
        //操作链
        Stream.of(1, 2, 3, 4, 5,8,10,12,14)
                .filter(t->t%2==0)
                .sorted((t1,t2) -> t2-t1)
                .forEach(t-> System.out.println(t));
    }

    @Test
    public void test4(){
        //把一组数字中,不同的数字筛选出来,打印这些数字,并且把这些不同的数字从小到大收集到List中
        List<Integer> list = Stream.of(1, 1, 3, 4, 5, 8, 1, 12, 5, 3, 2, 4, 12)
                .distinct() //去重复
                .sorted() //排序
                .peek(t -> System.out.println(t))
                .collect(Collectors.toList());

        System.out.println("list = " + list);
    }

    @Test
    public void test5(){
        //随机产生一些[0,100)的整数,取出前10个
        //Supplier<T> 的抽象方法  T get()
        Random random = new Random();
        List<Integer> list = Stream.generate(() -> random.nextInt(100))
                .limit(10)
                .collect(Collectors.toList());
        System.out.println("list = " + list);
    }

    @Test
    public void test6(){
        //取出第3个数字
        Optional<Integer> three = Stream.of(1, 2, 3, 4, 5)
                .skip(2)
                .findFirst();
        System.out.println(three);
    }

    @Test
    public void test7(){
        //取出第3个数字
         Stream.of(1, 2, 3, 4, 5)
                .skip(2)
                .limit(1)
                 .forEach(t-> System.out.println(t));
    }

    @Test
    public void test8(){
        //找出最老的3个员工,年龄最大的3个员工
        ArrayList<Employee> list = new ArrayList<>();
        list.add(new Employee(1,"张三",15,15000));
        list.add(new Employee(2,"李四",24,14000));
        list.add(new Employee(3,"王五",25,18000));
        list.add(new Employee(4,"赵六",22,12000));
        list.add(new Employee(5,"陈前",29,12000));
        list.add(new Employee(6,"林上清",27,12000));
        list.add(new Employee(7,"昆昆",27,12000));


        List<Employee> result = list.stream()
                .sorted((e1, e2) -> e2.getAge() - e1.getAge())
                .limit(3)
                .collect(Collectors.toList());

        System.out.println("result = " + result);
    }

    @Test
    public void test9(){
        //找出最老的3个员工,年龄最大的3个员工
        ArrayList<Employee> list = new ArrayList<>();
        list.add(new Employee(1,"张三",15,15000));
        list.add(new Employee(2,"李四",24,14000));
        list.add(new Employee(3,"王五",25,18000));
        list.add(new Employee(4,"赵六",22,12000));
        list.add(new Employee(5,"陈前",29,12000));
        list.add(new Employee(6,"林上清",27,12000));
        list.add(new Employee(7,"昆昆",27,12000));


        //年龄第3名的员工
        List<Employee> result = list.stream()
                .sorted((e1, e2) -> e2.getAge() - e1.getAge())
                .skip(2)
                .limit(1)
                .collect(Collectors.toList());

        System.out.println("result = " + result);
    }

    @Test
    public void test10(){
        //找出最老的3个员工,年龄最大的3个员工
        ArrayList<Employee> list = new ArrayList<>();
        list.add(new Employee(1,"张三",15,15000));
        list.add(new Employee(2,"李四",24,14000));
        list.add(new Employee(3,"王五",25,18000));
        list.add(new Employee(4,"赵六",22,12000));
        list.add(new Employee(5,"陈前",29,12000));
        list.add(new Employee(6,"林上清",27,12000));
        list.add(new Employee(7,"昆昆",27,12000));


        //年龄第3名的员工,年龄值不能重复
        //思路:先找出年龄值是第3的值,然后再找员工
        //Stream 	mapToInt(ToIntFunction f)
        //ToIntFunction<T> int applyAsInt(T value);
        OptionalInt ageOption = list.stream()
                .sorted((t1,t2)->t2.getAge()-t1.getAge())
                .mapToInt(emp -> emp.getAge())
                .distinct()
                .skip(2)
                .findFirst();
        System.out.println("age = " + ageOption);

        list.stream()
                .filter(emp -> emp.getAge() == ageOption.getAsInt())
                .forEach(t-> System.out.println(t));

    }

    @Test
    public void test11(){
        ArrayList<Employee> list = new ArrayList<>();
        list.add(new Employee(1,"张三",15,15000));
        list.add(new Employee(2,"李四",24,14000));
        list.add(new Employee(3,"王五",25,18000));
        list.add(new Employee(4,"赵六",22,12000));
        list.add(new Employee(5,"陈前",29,12000));
        list.add(new Employee(6,"林上清",27,12000));
        list.add(new Employee(7,"昆昆",27,12000));

        //把薪资低于15000,都涨工资为15000,然后统计总薪资值
        OptionalDouble sumSalary = list.stream()
                .map(emp -> {
                    if (emp.getSalary() < 15000) {
                        emp.setSalary(15000);
                    }
                    return emp;
                })  //Function<T,R> R apply(T t)
                .peek(t -> System.out.println(t))
                .mapToDouble(t -> t.getSalary())  //DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)  ToDoubleFunction<T> double apply(T t)
                .reduce((t1, t2) -> t1 + t2);//Optional<T> reduce(BinaryOperator<T> accumulator);   BinaryOperator<T,T> T apply(T t1, T t2)
        System.out.println("sumSalary = " + sumSalary);

    }

    @Test
    public void test12(){
        /*
         Stream 	map(Function f)
          Stream 	flatMap(Function f)
         */
        Stream.of("hello","java","world")  //首字母变大写
                .map(t->t.substring(0,1).toUpperCase().concat(t.substring(1)))  //Function<T,R> R apply(T t)
                .forEach(t-> System.out.println(t));
    }

    @Test
    public void test13(){
        /*
         Stream 	map(Function f)
          Stream 	flatMap(Function f)
         */
        Stream.of("hello","java","world")  //取出首字母
                .map(t->t.charAt(0))  //Function<T,R> R apply(T t)
                .forEach(t-> System.out.println(t));

        //map操作,元素的个数不会发生变化,类型可能变化
    }

    @Test
    public void test14(){
        /*
          <R> Stream<R> map(Function<? super T, ? extends R> mapper);
                T类型->R类型对象
         <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
                T类型->Stream<R>流
         */
        Stream.of("hello","java","world")  //把所有单词的每一个字母取出来
            .flatMap(s->Arrays.stream(s.split("|")))
                .forEach(t-> System.out.println(t));

       /* String s = "hello";
         String[] strings = s.split("|");
        System.out.println(Arrays.toString(strings));*/
    }

    @Test
    public void test15(){
        /*
         需求:给你一组单词,统计里面使用了几个字母
         */
        long count = Stream.of("hello", "java", "world", "atguigu")  //把所有单词的每一个字母取出来
                .flatMap(s -> Arrays.stream(s.split("|")))
                .distinct()
                .count();

        System.out.println("count = " + count);
    }

    @Test
    public void test16(){
        /*
         需求:给你一组单词,统计里面使用了几个字母,并找出这些字母
         */
        Set<String> set = Stream.of("hello", "java", "world", "atguigu")  //把所有单词的每一个字母取出来
                .flatMap(s -> Arrays.stream(s.split("|")))
                .collect(Collectors.toSet());
        System.out.println("字母有:"+set);
        System.out.println("个数:" + set.size());
        /*
        字母有:[a, d, e, g, h, i, j, l, o, r, t, u, v, w]
        个数:14
         */
    }

    @Test
    public void test17(){
        //"hello", "java", "world", "atguigu"
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list,"hello", "java", "world", "atguigu");

        HashSet<Character> set = new HashSet<>();
        for (String s : list) {//s依次是 "hello", "java", "world", "atguigu"
            char[] chars = s.toCharArray();
            /*
            当s是hello时,
            chars数组:{'h','e','l','l','o'}
             */
            for (int i = 0; i < chars.length; i++) {
                set.add(chars[i]);//把chars数组的一个一个的字符添加到set中
            }
        }
        System.out.println("字母有:"+ set);
        System.out.println("个数:" + set.size());
    }

}
public class Employee {
    private int id;
    private String name;
    private int age;
    private double salary;

    public Employee(int id, String name, int age, double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }
}

终结操作API

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void。流进行了终止操作后,不能再次使用。

序号 方法的返回值类型 方法 描述
1 boolean allMatch(Predicate p) 检查是否匹配所有元素
2 boolean anyMatch(Predicate p) 检查是否至少匹配一个元素
3 boolean noneMatch(Predicate p) 检查是否没有匹配所有元素
4 Optional findFirst() 返回第一个元素
5 Optional findAny() 返回当前流中的任意元素
6 long count() 返回流中元素总数
7 Optional max(Comparator c) 返回流中最大值
8 Optional min(Comparator c) 返回流中最小值
9 void forEach(Consumer c) 迭代
10 T reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T
11 U reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional
12 R collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
import org.junit.Test;

import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class TestEndStream {
    @Test
    public void test1(){
        Stream<Integer> stream = Stream.of(1, 2, 3, 5, 7, 9);
        
        //终结操作
        //判断stream中的所有数据,是否都满足 偶数的要求
        //allMatch(Predicate<? super T> predicate)
        //Predicate<T> boolean test(T t)
        boolean result = stream.allMatch(num -> num % 2 == 0);
        System.out.println("result = " + result);
    }

    @Test
    public void test2(){
        Stream<Integer> stream = Stream.of(1, 2, 3, 5, 7, 9);

        //终结操作
        //判断stream中的所有数据,是否有数字满足 偶数的要求
        //anyMatch(Predicate<? super T> predicate)
        //Predicate<T> boolean test(T t)
        boolean result = stream.anyMatch(num -> num % 2 == 0);
        System.out.println("result = " + result);
    }


    @Test
    public void test3(){
        Stream<Integer> stream = Stream.of(1,  3, 5, 7, 9);

        //终结操作
        //判断stream中的所有数据,是否都不满足 偶数的要求
        //noneMatch(Predicate<? super T> predicate)
        //Predicate<T> boolean test(T t)
        boolean result = stream.noneMatch(num -> num % 2 == 0);
        System.out.println("result = " + result);
    }

    @Test
    public void test4(){
        Stream<Integer> stream = Stream.of(1,  3, 5, 7, 9);

        //获取流中第一个元素
        Optional<Integer> first = stream.findFirst();
        System.out.println(first);//Optional[1]
    }

    @Test
    public void test5(){
        Stream<Integer> stream = Stream.of(1,  3, 5, 7, 9);

        //加工处理一下,筛选出所有的偶数
        //Stream<T> filter(Predicate<? super T> predicate);
        //Predicate<T> boolean test(T t)
        stream = stream.filter(num -> num%2==0);

        //获取流中第一个元素
        Optional<Integer> first = stream.findFirst();
        System.out.println(first);//Optional.empty
    }

    @Test
    public void test6(){
        Stream<Integer> stream = Stream.of(1,  3, 5, 7, 9);
        //统计流中的元素个数
        long count = stream.count();
        System.out.println("count = " + count);
    }

    @Test
    public void test7(){
        Stream<Integer> stream = Stream.of(11,  93, 50, 75, 9);
        //找出流中的最大值和最小值
        //Comparator<T> int compare(T t1 ,T t2)
        Optional<Integer> max = stream.max((t1, t2) -> t1-t2);
        System.out.println("max = " + max);//max = Optional[93]
    }

    @Test
    public void test8(){
        Stream<Integer> stream = Stream.of(11,  93, 50, 75, 9);
        Optional<Integer> min = stream.min((t1, t2) -> t1 - t2);
        System.out.println("min = " + min);
    }

    @Test
    public void test9(){
        Stream<Integer> stream = Stream.of(11,  93, 50, 75, 9);
        //遍历流中的数据
        //  void forEach(Consumer<? super T> action);
        //Consumer<T> 的抽象方法  void accept(T t)
        stream.forEach(t-> System.out.println(t));
    }

    @Test
    public void test10(){
        Stream<Integer> stream = Stream.of(11,  93, 50, 75, 9);
        //使用reduce方法找出最大值,不用max方法
        //Optional<T> reduce(BinaryOperator<T> accumulator);
        //BinaryOperator<T,T> T apply(T t1, T t2)
        Optional<Integer> max = stream.reduce((t1, t2) -> t1 > t2 ? t1 : t2);
        System.out.println("max = " + max);
    }
    
    @Test
    public void test11(){
        Stream<Integer> stream = Stream.of(11,  93, 50, 75, 9);
        
        //把流中的元素值累加起来
        //Optional<T> reduce(BinaryOperator<T> accumulator);
        //BinaryOperator<T,T> T apply(T t1, T t2)
        final Optional<Integer> sum = stream.reduce((t1, t2) -> t1 + t2);
        System.out.println("sum = " + sum);
    }

    @Test
    public void test12(){
        Stream<Integer> stream = Stream.of(11,  93, 50, 75, 9, 52,24,96,52,24,96);

        //筛选出所有的偶数,放到一个List集合中
        //中间处理
        //Stream<T> filter(Predicate<? super T> predicate);
        //Predicate<T> boolean test(T t)
        stream = stream.filter(t->t%2==0);

        //收集这些元素到List中
        List<Integer> list = stream.collect(Collectors.toList());
        System.out.println("list = " + list);
    }
    
    @Test
    public void test13(){
        Stream<Integer> stream = Stream.of(11,  93, 50, 75, 9, 52,24,96,52,24,96);

        //筛选出所有的偶数,放到一个Set集合中
        //中间处理
        //Stream<T> filter(Predicate<? super T> predicate);
        //Predicate<T> boolean test(T t)
        stream = stream.filter(t->t%2==0);

        Set<Integer> set = stream.collect(Collectors.toSet());
        System.out.println("set = " + set);
    }
}

收集(collect)

collect主要依赖java.util.stream.Collectors类内置的静态方法。

1归集(toList/toSet/toMap)

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toListtoSettoMap比较常用,另外还有toCollectiontoConcurrentMap等复杂一些的用法。

toList

List<Integer> list1 = Arrays.asList(1, 2, 8, 0, 9, 0, 1, 23, 32, 37, 49, 48);
List<Integer> collect1 = list1.stream().filter(value -> value > 5).collect(Collectors.toList());
log.info(String.format("list1中收集>5的结果为:%s", collect1));

toSet

Set<Integer> collect2 = list1.stream().filter(value -> value < 5).collect(Collectors.toSet());
log.info(String.format("list1中收集<5的结果为:%s", collect2));
collect2.forEach(value -> {System.out.print(value + " ");

toMap

List<User> userList1 = Arrays.asList(
        new User(1, "xww", "男", 22),
        new User(2, "zgx", "男", 22),
        new User(3, "whb", "男", 23),
        new User(4, "gg", "男", 30),
        new User(5, "yda", "男", 22),
        new User(6, "bhm", "女", 23),
        new User(7, "lsn", "女", 22),
        new User(8, "ksj", "女", 22)
);
 
Map<String, User> userMap = userList1.stream()
         .filter(user -> user.getSex().equals("女"))
         .collect(Collectors.toMap(User::getName, user -> user));
log.info(String.format("性别为女的用户转map:%s", userMap));

2 统计(count/averaging)

Collectors提供了一系列用于数据统计的静态方法:

  • 计数:count
  • 平均值:averagingIntaveragingLongaveragingDouble
  • 最值:maxByminBy
  • 求和:summingIntsummingLongsummingDouble
  • 统计以上所有:summarizingIntsummarizingLongsummarizingDouble
List<Integer> list = Arrays.asList(1, 2, 3, 2, 5, 3, 9, 8, 7, 6, 29, 10, 22);
 
// count
long count = list.stream().filter(value -> value > 10).count();
log.info(String.format("list中元素>10的个数为:%d", count));
 
// average
Double average = list.stream().filter(value -> value > 1).collect(Collectors.averagingInt(Integer::intValue));
log.info(String.format("list中元素>1的元素平均值为:%.2f", average));
 
List<User> userList1 = Arrays.asList(
        new User(1, "xww", "女", 22),
        new User(2, "zgx", "男", 22),
        new User(3, "whb", "男", 23),
        new User(4, "gg", "男", 30),
        new User(5, "yda", "男", 22),
        new User(6, "bhm", "女", 23),
        new User(7, "lsn", "女", 22),
        new User(8, "ksj", "女", 22)
);
 
Double averageAge = userList1.stream()
        .filter(user -> user.getSex().equals("男"))
        .map(User::getAge)
        .collect(Collectors.averagingInt(Integer::intValue));
log.info(String.format("男用户的平均年龄为:%d 岁", averageAge.intValue()));
 
// mapToInt
int ageSum = userList1.stream()
        .filter(user -> user.getSex().equals("女"))
        .mapToInt(User::getAge)
        .sum();
log.info(String.format("女用户的年龄之和为:%d", ageSum));
 
// summarizingInt 统计 计数、总和、最小值、平均值、最大值
IntSummaryStatistics recording = userList1.stream()
        .filter(user -> user.getSex().equals("男"))
        .collect(Collectors.summarizingInt(User::getAge));
log.info(String.format("记录所有男用户的年龄各项值,结果为:%s", recording));

3 分组(partitioningBy/groupingBy)

  • 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
  • 分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。

partitioningBy

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 20, 37, 49, 243, 30);
 
// partitioningBy
Map<Boolean, List<Integer>> collect1 = list.stream()
        .collect(Collectors.partitioningBy(value -> value > 20));
log.info(String.format("元素值是否大于20进行分组,结果为:%s", collect1));
 
collect1.forEach((key, value) -> {
    log.info(String.format("元素值是否大于20进行分组,结果为:%s:%s", key, value));
});

groupingBy

List<User> userList = Arrays.asList(
        new User(1, "xww", "女", 22),
        new User(2, "zgx", "男", 21),
        new User(3, "whb", "男", 23),
        new User(4, "gg", "男", 30),
        new User(5, "yda", "男", 22),
        new User(6, "bhm", "女", 23),
        new User(7, "lsn", "女", 22),
        new User(8, "ksj", "女", 22)
);
 
// groupingBy
Map<String, List<User>> collect2 = userList.stream()
        .collect(Collectors.groupingBy(User::getSex));
log.info(String.format("根据性别对用户进行分组,结果为:%s", collect2));
 
collect2.forEach((key, user) -> {
    log.info(String.format("根据性别对用户进行分组,结果为:%s:%s", key, user));
});

//统计男女各组的人数
Map<String, Long> collect = students.stream()
                .collect(Collectors.groupingBy(User::getSex, Collectors.counting()));

4 接合(joining)

joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 20, 37, 49, 243, 30);
String collect = list.stream()
        .map(Object::toString)
        .collect(Collectors.joining("——"));
log.info(String.format("joining测试结果为:%s", collect));
posted @ 2024-08-18 14:08  CH_song  阅读(11)  评论(0编辑  收藏  举报