• Java8新特性


    Java8新特性

    1、Lambda表达式

    Lambda就是一个匿名函数,注意!只有函数式接口才可使用Lambda表达式

    Lambda表达式本质就是函数是接口的对象!

    @Test
    // 展示两个代码中最常见的写法
    void contextLoads() {
        // Lambda 表达式
        Comparator<Integer> comLam = (o1, o2) -> Integer.compare(o1, o2);
        // 方法引用
        Comparator<Integer> comRef = Integer::compare;
    
        System.out.println(comLam.compare(10, 11) == comRef.compare(10, 11));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Lambda格式:

    • 左边:Lambda形参列表(就是接口抽象方法的参数列表)
    • 右边:Lambda体(就是重写接口抽象方法的方法体)

    6种格式:

    ① 无参,无返回值
    Runnable runSim = new Runnable() {
        @Override
        public void run() {
            System.out.println("xxx");
        }
    };
    
    Runnable runLam = () -> System.out.println("xxx");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    ② 单参,无返回值
    Consumer<String> conSim = new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    };
    
    Consumer<String> conLam = (String s) -> System.out.println(s);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    ③ 数据类型可省略,由编译器推断得出,称为【类型推断】
    // 沿用 ② 代码
    Consumer<String> conLam = (s) -> System.out.println(s);
    
    // 类似的我们还接触过很多,such as:
    List<String> sList = new ArrayList<>();
    int[] iArr = {0, 1, 2};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    ④ 只有一个形参时可省略小括号
    // 沿用 ③ 代码
    Consumer<String> conLam = s -> System.out.println(s);
    
    • 1
    • 2
    ⑤ 参数>1,方法体执行语句>1
    Comparator<Integer> compSim = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            // ...假装这里还写了一大堆缺心眼逻辑
            return o1.compareTo(o2);
        }
    };
    
    Comparator<Integer> comLam = (o1, o2) -> {
        // ...假装这里还写了一大堆缺心眼逻辑
        return o1.compareTo(o2);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    ⑥ 方法体只有一行语句(有返回值的return也省略)
    // 沿用 ⑤ 代码
    Comparator<Integer> comLam = (o1, o2) -> Integer.compare(o1, o2);
    
    • 1
    • 2

    2、函数式(Functional)接口

    首先官方的接口上挂着@FunctionalInterface注解的就是函数式接口,其次凡是只有一个抽象方法的接口都是函数式接口

    我们可以用Lambad表达式创建函数式接口的对象,我们也可以使用@FunctionalInterface注解标注我们的函数是接口,这样可以检验它到底是不是一个函数式接口(就一个抽象方法,还是很明显的)

    java.util.function 包下定义了 Java 8 的丰富的函数式接口。

    Java 内置四大核心函数式接口:
    函数式接口参数类型返回类型用途
    Consumer
    消费型接口
    Tvoid对类型 T的对象应用操作,包含方法:
    void accept(T t);
    Supplier
    供给型接口
    T返回类型 T的对象,包含方法:
    T get();
    Function
    函数型接口
    TR对类型 T的对象操作,返回 R类型结果,包含方法:
    R apply(T t);
    Predicate
    断定型接口
    Tboolean确定类型 T的对象是否满足某约束,并返回布尔值,包含方法:
    boolean test(T t);
    其他接口:
    函数式接口参数类型返回类型用途
    BiFunctionT,UR对类型T、U的对象进行操作,返回R类型结果,包含方法:
    R apply(T t, U u);
    UnaryOprerator
    (Function子接口)
    TT对类型T进行一元运算,返回T类型结果,包含方法:
    T apply(T t);
    BinaryOperator
    (BiFunction子接口)
    T,TT对类型T进行二元运算,返回T类型结果,包含方法:
    T apply(T t1, T t2);
    BiConsumerT,Uvoid对类型T、U的对象进行操作,包含方法:
    void accept(T t, U u);
    BiPredicateT,Uboolean根据参数判断断言并返回布尔值,包含方法:
    boolean test(T t, U u);
    ToIntFunction
    ToLongFunction
    ToDoubleFunction
    Tint/long/double对T类型对象进行处理,返回对应类型的结果,包含方法依次为:
    int applyAsInt(T value);
    long applyAsLong(T value);
    double applyAsDouble(T value);
    IntFunction
    LongFunction
    DoubleFunction
    int/long/doubleR对对应类型对象进行处理,返回R类型的结果,包含方法依次为:
    R apply(int value);
    R apply(long value);
    R apply(double value);

    3、方法引用、构造器引用与数组引用

    3.1、方法引用(Method References)

    • 当要传给Lambda体的操作,已经有实现方法了,就可以使用方法引用!

    • 方法引用可以看作是Lambda表达式的深层次的表达,换句话说,方法引用就是Lambda表达式,也是函数式接口的一个实例,通过方法名来指向一个方法,学过C/C++的就更好理解了,就是传递指针(如有问题可联系小白,目前小白是这么理解的)。

    • 格式:使用操作符 :: 将类(或对象)与方法名分隔开

    • 如下三种主要使用情况:(前两种必须保证形参列表和返回值一致)

      • 对象::实例方法名

        • /*---------------------栗1----------------------*/
          // Lambda
          Consumer<String> conLam = s -> System.out.println(s);
          // MR
          Consumer<String> conMr = System.out::println;
          
          /*---------------------栗2----------------------*/
          // 假设Dog类有个属性是name(包含setter/getter)
          // Lambda
          Supplier<String> supLam = ()->dog.getName();
          // MR
          Supplier<String> supMr = dog::getName;
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
      • 类::静态方法名

        • /*---------------------栗1----------------------*/
          // Lambda
          Comparator<Integer> comLam = (o1, o2) -> Integer.compare(o1, o2);
          // MR
          Comparator<Integer> comMr = Integer::compare;
          
          /*---------------------栗2----------------------*/
          // Math:Long round(Double d)
          // Lambda
          Function<Double, Long> funcLam = d -> Math.round(d);
          // MR
          Function<Double, Long> funcMr = Math::round;
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
      • 类::实例方法名

        • /*---------------------栗1----------------------*/
          // Comparator: int compare(T t1, T t2)
          // String: int t1.compareTo(t2)
          // Lambda
          Comparator<String> comLam = (t1, t2) -> t1.compareTo(t2);
          // MR
          Comparator<String> comMr = String::compareTo;
          
          /*---------------------栗2----------------------*/
          // BiPredicate: boolean test(T t, U u)
          // String: boolean t1.equals(t2)
          // Lambda
          BiPredicate<String, String> comLam = (t1, t2) -> t1.equals(t2);
          // MR
          BiPredicate<String, String> comMr = String::equals;
          
          /*---------------------栗3----------------------*/
          // Function: R apply(T t)
          // 假设Dog类有个属性是name(包含setter/getter):String getName()
          // Lambda
          Function<Dog, String> funcLam = d -> d.getName();
          // MR
          Function<Dog, String> funcMr = Dog::getName;
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23

    3.2、构造器引用

    和方法引用类似:参数列表相同,返回值就是构造器所在类的类型。

    // Lambda
    Supplier<Dog> suppLam = ()->new Dog();
    // CR
    Supplier<Dog> suppCr = Dog::new;
    
    • 1
    • 2
    • 3
    • 4

    3.3、数组引用

    和构造器引用一致,把数组看成一个特殊的类即可。

    // Lambda
    Function<Integer, String[]> funcLam = length->new String[length];
    // AR
    Function<Integer, String[]> funcAr = String[]::new;
    
    • 1
    • 2
    • 3
    • 4

    4、强大的 Stream API

    Stream API (java.util.stream)把真正的函数式编程风格引入到 Java 中。这是目前为止对 Java 类库最好的补充!

    Stream API 是对集合数据进行操作,类似于SQL的查询。

    Collection 和 Stream 区别:Collection是数据的容器,Stream提供数据的计算。


    注意!!!

    • Stream 不会存储元素
    • Stream 不会改变源对象,只是返回含有处理结果的新 Stream
    • Stream 操作是延迟执行的,这意味着他们会等到需要结果的时候才执行

    Stream 操作的三步骤:

    1. 创建 Stream
      1. 一个数据源(集合、数组)获取一个流
    2. 中间操作
      1. 一个中间操作链,对数据源的数据进行处理
    3. 终止操作(终端操作)
      1. 一旦执行终止操作,就执行中间链操作a,并产生结果,之后不会再被调用

    4.1、创建 Stream 的四种方式:

    ① 通过集合

    Java 8 中 Collection 接口被扩展,提供两个获取 Stream 的方法:

    // 返回一个顺序流(按顺序把数据取出来)
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    
    // 返回一个并行流(并行取数据,取出来的数据和源数据不能保证顺序一致。。。)
    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    CODE:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Dog {
        private String name;
        private int age;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    List<Dog> dogs = new ArrayList<>();
    // 返回一个顺序流
    Stream<Dog> stream = dogs.stream();
    // 返回一个并行流
    Stream<Dog> dogStream = dogs.parallelStream();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    ② 通过数组

    Java 8 中 Arrays 的静态方法 stream(),可以获取 数据流:

    public static <T> Stream<T> stream(T[] var0) {
        return stream((Object[])var0, 0, var0.length);
    }
    
    public static <T> Stream<T> stream(T[] var0, int var1, int var2) {
        return StreamSupport.stream(spliterator(var0, var1, var2), false);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    CODE:

    int[] arr = {};
    IntStream stream = Arrays.stream(arr);
    
    • 1
    • 2
    ③ 通过 Stream 的 of()

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

    // 返回包含单个元素的序列{@code Stream}
    public static<T> Stream<T> of(T t) {
        return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
    }
    
    // 返回元素为指定值的顺序有序流
    @SafeVarargs
    @SuppressWarnings("varargs") // Creating a stream from an array is safe
    public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    CODE:

    Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
    
    • 1
    ④ 创建无限流

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

    // 返回由函数 f 迭代源数据 seed 生成无限顺序有序流
    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
        Objects.requireNonNull(f);
        final Iterator<T> iterator = new Iterator<T>() {
            @SuppressWarnings("unchecked")
            T t = (T) Streams.NONE;
    
            @Override
            public boolean hasNext() {
                return true;
            }
    
            @Override
            public T next() {
                return t = (t == Streams.NONE) ? seed : f.apply(t);
            }
        };
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
            iterator,
            Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
    }
    
    // 返回无限顺序无序流,其中每个元素由提供的 s 生成。这适用于生成恒定流、随机元素流等。
    public static<T> Stream<T> generate(Supplier<T> s) {
        Objects.requireNonNull(s);
        return StreamSupport.stream(
            new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    CODE:

    // 遍历前10个偶数
    Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
    
    // 生成10个随机数
    Stream.generate(Math::random).limit(10).forEach(System.out::println);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.2、筛选与切片

    方法描述
    filter(Predicate p)接收Lambda,从流中排除元素
    distinct()去重。老二式:通过hashCode()和equals()
    limit(long maxSize)截断(分页)。截取前maxSize个
    skip(long n)跳过前n个元素的流。长度不足n返回空流,与limit(n)互补

    CODE:

    Stream.iterate(0, t -> t + 2)
        .limit(10).
        .filter(t->t<10)
        .distinct()
        .skip(2)
        .forEach(System.out::println);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4.3、映射

    方法描述
    map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新元素
    mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream
    mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream
    mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream
    flatMap(Function f)接收一个函数作为参数,将流中每个元素都换成 f 返回的流,然后把所有流连接成一个流

    CODE:

    Arrays.asList("aa", "bb", "cc", "dd").stream()
        .map(String::toUpperCase)
        .flatMap(t->{
            List<Character> list = new ArrayList<>();
            for (char c: t.toCharArray()) {
                list.add(c);
            }
            return list.stream();
        })
        .forEach(System.out::println);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.4、排序

    方法描述
    sorted()按自然顺序排序产生一个新流
    sorted(Comparator c)按比较器顺序排序产生一个新流

    CODE:

    // sorted()
    Arrays.asList(22,13,9,0,13,-4,226,37).stream()
        .sorted()
        .forEach(System.out::println);
    
    
    List<Dog> dogs = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        dogs.add(new Dog("dog-" + i, (int)(Math.random() * 10)));
    }
    // sorted(Comparator c)
    dogs.stream()
        .sorted(Comparator.comparing(Dog::getAge).reversed()) // Comparator.reversed()倒序
        .forEach(System.out::println);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4.5、终止操作(终端操作)

    • 终端操作会从流的流水线生成结果。其结果可以是任意值,例如List、Integer,甚至是void
    • 流进行了终端操作后,不能再次使用
    4.5.1、匹配与查找
    方法描述
    boolean anyMatch(Predicate p)检查是否至少匹配一个元素,返回布尔值
    boolean allMatch(Predicate p)检查是否匹配所有元素,返回布尔值
    boolean noneMatch(Predicate p)检查是否没有匹配所有元素,返回布尔值
    Optional findFirst()返回第一个元素
    Optional findAny()返回当前流中任意元素
    long count()返回流中元素个数
    Optional min(Comparator c)返回最小元素
    Optional max(Comparator c)返回最大元素
    void forEach(Consumer c)内部迭代(使用Collection接口需要用户自己迭代,
    称为外部迭代。Stream API 使用内部迭代——它帮
    你把活干了)

    CODE:

    /* 本栗沿用上面的dogs */
    // allMatch:狗年龄都大于2吗
    System.out.println("狗年龄都大于2吗?__" + 
                       dogs.stream().allMatch(d->d.getAge() > 2));
    
    // anyMatch:狗年龄有大于2的吗
    System.out.println("狗年龄有大于2的吗?__" + 
                       dogs.stream().anyMatch(d->d.getAge() > 2));
    
    // noneMatch:狗年龄是否都不大于2
    System.out.println("狗年龄是否都不大于2?__" + 
                       dogs.stream().noneMatch(d->d.getAge() > 2));
    
    // findFirst:年龄逆序返回第一条狗
    System.out.println("年龄逆序返回第一条狗__" + dogs.stream()
                       .sorted(Comparator.comparing(Dog::getAge).reversed())
                       .findFirst());
    
    // findAny:随机返回一条狗
    for (int i = 0; i < 10; i++) {
        // 这里总会返回第一个元素,就很离谱。。。
        System.out.println(i + "__随机返回一条狗__" + 
                           dogs.stream().findAny());
    }
    
    // count:返回年龄大于2的狗的条数
    System.out.println("龄大于2的狗的条数__" + 
                       dogs.stream().filter(d->d.getAge() > 2).count());
    
    // max:返回这群狗中的最大年龄
    System.out.println("这群狗中的最大年龄__" + 
                       dogs.stream().map(d->d.getAge()).max(Integer::compareTo));
    
    // min:返回年龄最小的狗
    System.out.println("年龄最大的狗__" + 
                       dogs.stream().min(Comparator.comparingInt(Dog::getAge)));
    
    // forEach:这群狗年龄降序输出(此forEach是Stream内部迭代,非集合的forEach)
    dogs.stream()
        .sorted(Comparator.comparing(Dog::getAge).reversed())
        .forEach(System.out::println);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    4.5.2、归约
    方法描述
    T reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来得到一个值,返回T
    Optional reduce(BinaryOperator b)可以将流中元素反复结合起来得到一个值,返回Optional

    CODE:

    // reduce:1-10的和
    System.out.println("1-10的和__" + 
                       Arrays.asList(1,2,3,4,5,6,7,8,9,10).stream().reduce(0, Integer::sum));
    // reduce:狗窝狗年龄总和(继续沿用上面的dogs;另:用map的话更便捷可以省略new)
    System.out.println("狗窝狗年龄总和__" + 
                       dogs.stream()
                       .reduce((d1, d2)-> new Dog("", d1.getAge() + d2.getAge()))
                       .get()
                       .getAge());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    4.5.3、收集
    方法描述
    R collect(Collector c)将流转换为其他形式。接收一个Collector接口实现,
    用于给Stream中元素做汇总

    Collector 接口中方法的实现决定了如何对流执行汇总操作(如何收集到List、Set、Map)

    另外,Collectors 实用类提供了很多静态方法,可以方便的创建常见收集器实例,如下表所示

    在这里插入图片描述
    在这里插入图片描述

    CODE:

    // 继续沿用上面的dogs,返回年龄>3的狗列表
    dogs.stream()
        .filter(d -> d.getAge() > 3)
        .collect(Collectors.toList()) // 这里得到的就是List
        .forEach(System.out::println);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5、Optional 类

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

    起源:

    Google公司著名的 Guava项目引入了Optional类,Guava通过使用检査空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到 Google Guava的启发,Optional类已经成为 Java 8 类库的一部分。

    Optional 类提供了很多方法,可以不用再现实的进行空值检验。

    • 创建 Optional 类对象的方法
      • Optional.of(T t) : 创建一个 Optional 实例,t 必须非空
      • Optional.empty() : 创建一个空的 Optional 实例
      • Optional.ofNullable(T t)t 可以为 null
    • 判断Optional容器是否包含对象
      • boolean isPresent():判断是否包含对象
      • void ifPresent(Consumer consumer):如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给它
    • 获取 Optional 容器的对象
      • T get():如果调用对象包含值,返回该值,否则抛异常
      • T orElse(T other):如果有值则将其返回,否则返回指定的 other 对象
      • T orElseGet(Supplier other):如果有值则将其返回,否则返回由 Supplier 接口实现提供的对象
      • T orElseThrow(Supplier exceptionSupplier):如果有值则将其返回,否则抛出由 Supplier 接口实现提供的异常

    CODE:

    // 首先沿用上面的Dog
    // Person类包含Dog
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    class Person{
        private Dog dog;
    }
    
    // 封装一个通过Person获取其狗name的方法
    public String getPDName(Person p){
        return p.getDog().getName(); // 问题大大滴!很容易碰到空指针异常
    }
    
    // 我们做一下常规优化(可能的空指针异常被避免,代码更健壮)
    public String getPDNameSafely(Person p){
        if(Strings.notEmpty(p.getDog()) && Strings.notEmpty(p.getDog()){
            return p.getDog().getName();
        }
        return null;
    }
    
    // 使用 Optional...这特么何其麻烦啊...
    public String getPDNameSafelyPlus(Person p){
        Optional<Person> pOptional = Optional.ofNullable(p);
        Person pCp = pOptional.orElse(new Person(new Dog()));
        Dog dog = pCp.getDog();
        Optional<Dog> dogOptional = Optional.ofNullable(dog);
        Dog dog = dogOptional.orElse(new Dog());
        return dog.getName();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
  • 相关阅读:
    抖音小店:庞大用户基数与强大商业化能力的未来发展
    文件上传漏洞(CVE-2022-23043)
    翻译专业论文选题有哪些值得写的方向?
    人工智能--目标检测
    React Context源码是怎么实现的呢
    题目0049-N进制减法
    Python高级学习笔记(一)—— 多任务编程
    View Design 全系产品升级至 Vue 3,并发布 View UI Plus 正式版
    带头双向循环链表的增删查改(C语言实现)
    Spring Cache 带你飞(二)
  • 原文地址:https://blog.csdn.net/qq_30769437/article/details/126851820