Bootstrap

学习笔记:JAVA新特性(lambda表达式,函数式接口,Stream API,Optional)

一、函数式接口和Lambda表达式

简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例**(可以理解为:函数式接口的实现类对象)**。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。

1.定义函数式接口

// TODO: 2021/8/18 判断一个接口是否为函数式接口,可以在接口之前添加注解@FunctionalInterface 
@FunctionalInterface
public interface MyNumber<T> {
     // TODO: 2021/8/18 接口可以定义为一个泛型,在函数式接口实现类对象中,可以通过类型推断来确定数据类型 
     T getValue(T t);
}

2.函数式接口实现类对象做为参数应用

作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收****Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

public class DreamOne {
    //作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda
    //表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。
    public String toUpperString(MyNumber<String> myNumber, String str) {
        return myNumber.getValue(str);
    }
    
    // TODO: 2021/8/18 测试:能过lambda表达式生成一个函数式接口实现类对象,将其做为参数传传递进去
    public void toUpperStringTest(){
        MyNumber<String> myNumber = (x)->x;
        String s = toUpperString(myNumber, "hellojavac!");
        System.out.println(s);
    }
    
}

3.JAVA内置四大核心函数式接口

函数式接口参数类型返回类型用途
Consumer消费型接口Tvoid对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier供给型接口T返回类型为T的对象,包含方法:T get()
Function<T, R>消费生产型TR对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)
Predicate断定型接口Tboolean确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法:boolean test(T t)
  • 其他接口

在这里插入图片描述

以上四大接口和其他接口都为函数式接口,采取泛型,可以通过类型推断,确定函数式接口实现类对象的数据类型。

二、函数式接口的方法引用与构造器引用

1.方法引用

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
  • 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
  • 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
  • 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。

个人理解:方法用引,其核心思想是函数式接口不需要自已写lambda表达式来创建函数式接口实现类对象,而是直接使用系统或之前已经写好的,这样也可能通过调用函数式接口实现类对象中方式,产生与之前写好代码的效果

具体代码示例如下:

@Test
public void test56() {
    // TODO: 2021/8/18 两层意思:一是方法引用得到的也是一个对象,是函数式接口实现类对象,
    // 二是:函数式接口的实现类抽象方法的实现也引用的方法的方法体相同,
    // 三是:可以使用系统自带的方法去引用,也可以自已写方法去引用,
    Consumer<String> consumer = System.out::println;
    consumer.accept("hellothis");

    Comparator<Integer> comparator = Integer::compareTo;
    Comparator<String> comparator1 = String::compareTo;
    int compare = comparator.compare(52, 34);
    Consumer<Integer> consumer1 = System.out::println;
    consumer1.accept(compare);
}

@Test
public void test71() {
    //采用方法引用
    BiPredicate<String,String> biPredicate = String::equals;
    boolean test = biPredicate.test("java", "hello");
    System.out.println(test);

    //采用lambda表达式
    BiPredicate<String,String> biPredicate1 = (x,y)->{return x.equals(y);};
    System.out.println(biPredicate1.test("this", "this"));
}

2.构造引用

格式: ClassName::new

  • 与函数式接口相结合,自动与函数式接口中方法兼容。

  • 可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。

@Test
public void test82() {
    // TODO: 2021/8/18 构造引用
    //构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象
    DreamConstractor dreamConstractor = DreamOne::new;
    DreamOne dreamOne = dreamConstractor.getDreamOne();
    dreamOne.setName("admin");
    dreamOne.setAge(123);
    //方法引用
    Consumer<Object> consumer = System.out::println;
    consumer.accept(dreamOne);
}

三、Stream API

  • Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API

  • Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

  • Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

1. 为什么要使用Stream API

  • 实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理。

  • Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。

2. 什么是Stream

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

注意点:

  • ① Stream 自己不会存储元素。

  • ② Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。

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

3. Stream的操作三个步骤

  • 创建Stream

一个数据源(如:集合、数组),获取一个流

  • 中间操作

一个中间操作链,对数据源的数据进行处理

  • 终止操作

一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用

4. 通过集合创建Stream

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

default Stream stream() : 返回一个顺序流

default Stream parallelStream() : 返回一个并行流

5. 通过数组创建Stream

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

static Stream stream(T[] array): 返回一个流

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

  • public static IntStream stream(int[] array)

  • public static LongStream stream(long[] array)

  • public static DoubleStream stream(double[] array)

6. 通过Stream的of()方法创建Stream

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

  • public static Stream of(T… values) : 返回一个流

7. 创建无限流

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

  • 迭代

public static Stream iterate(final T seed, final UnaryOperator f)

  • 生成

public static Stream generate(Supplier s)

@Test
public void test92() {
    // TODO: 2021/8/18 迭代的方式
    Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);
    iterate.limit(10).forEach(System.out::println);

    // TODO: 2021/8/18 生成方式
    // TODO: 2021/8/18 采用了lambda表达式
    Stream<Double> generate1 = Stream.generate(() -> {
        return Math.random();
    });
    // TODO: 2021/8/18 采用了方法引用
    // TODO: 2021/8/18 Stream本身不存储数据,而是在使用的时候将数据源中的
    // 数据按指定的生成方法产生数据和Stream一起将数据存储到内存当中
    Stream<Double> generate = Stream.generate(Math::random);
    generate.limit(100).forEach(System.out::println);
}

8. Stream的中间操作:筛选与切片

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

  • 筛选与切片
方 法描 述
filter(Predicate p)接收 Lambda , 从流中排除某些元素
distinct()筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize)截断流,使其元素不超过给定数量
skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
@Test
public void test92() {
    // TODO: 2021/8/18 迭代的方式产生数据到Stream中
    Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);
    iterate.limit(10).forEach(System.out::println);

    // TODO: 2021/8/18 生成方式
    // TODO: 2021/8/18 采用了lambda表达式
    Stream<Double> generate1 = Stream.generate(() -> {
        return Math.random();
    });
    // TODO: 2021/8/18 采用了方法引用
    // TODO: 2021/8/18 Stream本身不存储数据,而是在使用的时候将数据源中的
    // 数据按指定的生成方法产生数据和Stream一起将数据存储generate到内存当中
    // TODO: 2021/8/18 明确产生数据的方式, 
    Stream<Double> generate = Stream.generate(Math::random);
    // TODO: 2021/8/18 只选前100的数据到stream中,Stream存储在内存当中
    Stream<Double> limit = generate.limit(100);
    // TODO: 2021/8/18 过滤,只选取大gf 0.5的数据在Stream中
    Stream<Double> doubleStream = limit.filter(x -> x > 0.5);
    // TODO: 2021/8/18 将Stream中的数据去重
    Stream<Double> distinct = doubleStream.distinct();
    // TODO: 2021/8/18 跳过前10个元素,将后面的元素继续存储在Stream中 
    Stream<Double> skip = distinct.skip(10);
    // TODO: 2021/8/18 终止操作,将stream中的数据打印出来
    doubleStream.forEach(System.out::println);
}9

9. Stream的中间操作:映射

方法描述
map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
@Test
public void test123() {
    // TODO: 2021/8/18 通过迭代的方式道理成数据
    Stream<Integer> iterate = Stream.iterate(1, x -> x + 1);
    // TODO: 2021/8/18 截取前100个数据保存到Stream中
    Stream<Integer> limit = iterate.limit(100);
    // TODO: 2021/8/18 映射:接收一个函数,这个函数可以通过lambda表达式或方法引用确定的,
    // 传递的是一个函数式接口实现类对象,再调用这个对象中的方法
    Stream<Integer> integerStream = limit.map(x -> x * 2);
    // TODO: 2021/8/18 将int型数据转换成double型,由于是数据从小转大,所以会自动类型转换,在lambda方法体中不需要进行任何操作
    DoubleStream doubleStream = integerStream.mapToDouble(x -> x*12);
    // TODO: 2021/8/18 将double数据转换成int数据,由于是数据从大到小,所以在表达式方法体中需要强转
    IntStream intStream = doubleStream.mapToInt(x -> (int) x);
    // TODO: 2021/8/18 将int数据转换从long数据,数据从小到大,会自动类型转换
    LongStream longStream = intStream.mapToLong(x -> x);
    // TODO: 2021/8/18 将Stream中的一个数据映射成为包含这个数据的一个Stream
    LongStream stream = longStream.flatMap(x -> LongStream.of(x));
}

10. Stream的中间操作:排序

方法描述
sorted()产生一个新流,其中按自然顺序排序
sorted(Comparator com)产生一个新流,其中按比较器顺序排序
@Test
public void test143() {
    Stream<Integer> iterate = Stream.iterate(1, x -> x + 1);
    iterate.limit(100).sorted((x, y) -> {
        return x.compareTo(y);
    }
                             ).forEach(System.out::println);
}

11. Stream终止操作

  • 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

  • 流进行了终止操作后,不能再次使用。

11.1 匹配与查找
方法描述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)
@Test
public void test152() {
    Stream<Integer> generate = Stream.generate(() -> {
        Integer integer = local.get();
        if (integer == null) {
            integer = 1;
            local.set(integer);
        } else {
            integer += 1;
            local.set(integer);
        }
        return integer;
    });

    Stream<Integer> limit = generate.limit(100);
    // TODO: 2021/8/18 检查是否匹配所有元素
    boolean b = limit.allMatch(x -> x > 0);
    // TODO: 2021/8/18 检查是否匹配其中的一个元素
    System.out.println(limit.anyMatch(x -> x == 900));
    // TODO: 2021/8/18 检查是否没有匹配所有元素
    System.out.println(limit.noneMatch(x -> x < 0));
    // TODO: 2021/8/18 返回第一个元素 ,产生的是一个Optional对象
    Optional<Integer> first = limit.findFirst();
    // TODO: 2021/8/18 返回流中任意的一个元素
    Optional<Integer> any = limit.findAny();
    Integer integer = first.get();
    System.out.println(integer);
}

@Test
public void test183() {
    Stream<Integer> iterate = Stream.iterate(1, x -> x + 1);
    Stream<Integer> limit = iterate.limit(100);
    // TODO: 2021/8/18 返回流中值的总数 
    long count = limit.count();
    // TODO: 2021/8/18 返回流中的最小值
    Optional<Integer> min = limit.min((x,y)->x.compareTo(y));
    // TODO: 2021/8/18 返回流中的最大值
    Optional<Integer> max = limit.max(Integer::compareTo);
    // TODO: 2021/8/18 将流中的值逐一进行打印 
    limit.forEach(x-> System.out.println(x));
    System.out.println(max.get());
}
11.2 归约
方法描述
reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 Optional

备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

@Test
public void test198() {
    Stream<Integer> iterate = Stream.iterate(1, x -> x + 1);
    Stream<Integer> limit = iterate.limit(100);
    // TODO: 2021/8/18 采用方法引用
    Optional<Integer> reduce = limit.reduce(Integer::sum);
    // TODO: 2021/8/18 采用lambda表达式
    Optional<Integer> reduce1 = limit.reduce((x, y) -> x + y);
    System.out.println(reduce.get());
}

@Test
public void test209() {
    Stream<Integer> iterate = Stream.iterate(1, x -> x + 1);
    Stream<Integer> limit = iterate.limit(100);
    // TODO: 2021/8/18 归约,合并流中的值并加止额外的参数值100 
    Integer reduce = limit.reduce(100, Integer::sum);
    System.out.println(reduce);
}
11.2 收集
方法描述
collect(Collector c)将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
  • Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。

  • 另外, Collectors 实现类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

方法返回类型作用
toListList把流中元素收集到List
toSetSet把流中元素收集到Set
toCollectionCollection把流中元素收集到创建的集合
countingLong计算流中元素的个数
summingIntInteger对流中元素的整数属性求和
averagingIntDouble计算流中元素Integer属性的平均值
summarizingIntIntSummaryStatistics收集流中Integer属性的统计值。如:均值
joiningString连接流中每个字符串
maxByOptional根据比较器选择最大值
reducing归约产生的类型

在这里插入图片描述

在这里插入图片描述

四、Optional 类

  • 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。

  • Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。

  • Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

1. 创建Optional类对象的方法:

  • Optional.of(T t) : 创建一个 Optional 实例,t必须非空;

  • Optional.empty() : 创建一个空的 Optional 实例

  • **Optional.ofNullable(T t) 😗*t可以为null

2. 判断Optional容器中是否包含对象:

  • boolean isPresent() : 判断是否包含对象

  • void ifPresent(Consumer<? super T> consumer) **:**如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。

3. 获取Optional容器的对象:

  • T get(): 如果调用对象包含值,返回该值,否则抛异常

  • T orElse(T other) **:**如果有值则将其返回,否则返回指定的other对象。

  • T orElseGet(Supplier<? extends T> other) **:**如果有值则将其返回,否则返回由Supplier接口实现提供的对象。

  • T orElseThrow(Supplier<? extends X> exceptionSupplier) **:**如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。

  1. 代码示例一:
@Test
public void test225() {
    // TODO: 2021/8/18 创建Optional对象,有三种创建方法,这是其中之一
    Optional<Girl> girlFriend = Optional.ofNullable(new Boy().getGirlFriend());
    // TODO: 2021/8/18 判断Optional对象中是否有值,如果有值,就值传递给参数对象进行处理 ,参数数像可以是函数值接口实现类对象
    girlFriend.ifPresent(System.out::println);
    // TODO: 2021/8/18 如果optional 对象中有值,则返回对象中的值,如果没有,就返回参数中定义的对象
    Girl aThis = girlFriend.orElse(new Girl("this", 30));
    System.out.println(aThis);
    // TODO: 2021/8/18 如果Optional对象中有值,则返回值,如果没有,返回在生产型函数式接口实现类对象所产生的值
    Girl hello = girlFriend.orElseGet(() -> {
        return new Girl("hello", 25);
    });
    System.out.println(hello);
}
  1. 代码示例二:
@Test
public void test243() {
    Optional<Employee> admin = Optional.of(new Employee("admin", 5000));
    // TODO: 2021/8/18 判断 Optional对象中的数据是否符合参数中的条件,如果符合,则重新创建一个包含此数据的Opitonal对象
    //如果不符合,则创建一个空的Optional对象
    Optional<Employee> employee = admin.filter(e -> e.getSalary() > 7000);
    employee.ifPresent(System.out::println);
    // TODO: 2021/8/18 由于Optional对象中的数据为空,所以下面会返回参数中创建的对象 
    Employee thisgood = employee.orElse(new Employee("thisgood", 6000));
    System.out.println(thisgood);

    // TODO: 2021/8/18 由于Optional对象为空,所以会返回一个由生产型函数式接口实现类对象创建的对象 
    Employee hellojava = employee.orElseGet(() -> {
        return new Employee("hellojava", 20);
    });
    System.out.println(hellojava);
}
;