Skip to content

JDK8 新特性

1. 介绍

在 JDK8 版本中 引入了很多新的内容 分为

新的语法

新的功能

新的底层实现

等等

2. 集合相关

ArrayList 被设计为了懒加载的模式 初始化无参构造 只维护一个空列表 当我们第一次添加元素 才将数组初始化为 10

HashMap 加入了红黑树数据结构

3. 接口相关

接口中可以书写普通方法 使用 default 关键字修饰 加在返回值类型之前 访问修饰符之后

接口中可以书写静态方法

java
package com.atguigu.test1;

public interface A {
    public default void m1(){

    }

    public static void m2(){

    }

}

4. 函数式接口

函数式接口:即一个接口中只有一个抽象方法 这样的接口称之为 SAM 接口 Single Abstract Method 这样的接口可以使用注解@FunctionalInterface 修饰 称之为函数式接口

只要一个接口中只有一个抽象方法,即可称之为函数式接口

函数式编程属于一种编程思想 就像面向过程 面向对象 等等 都属于编程思想

函数式编程的代表语言是 Haskell 更强调函数(方法)可以实现什么操作,执行了什么功能,不注重是哪个角色调用了这个函数(方法)

使用函数式编程 即表示前提必须为函数式接口

5.lambda 表达式

lambda 表达式即函数式编程的最终实现

回顾匿名内部类 :即我们可以”直接 new“接口 或者抽象类 相当于创建一个匿名内部类

使用了 lambda 表达式以后 之前匿名内部类书写格式混乱的问题 可以得到解决

前提:lambda 表达式只能用于函数式接口

写法越简洁 前期越难理解 后期使用越方便

lambda 表达式格式:()->

java
package com.atguigu.test1;

/**
 *  回顾匿名内部类  :即我们可以”直接new“接口 或者抽象类 相当于创建一个匿名内部类
 *  使用了lambda表达式以后 之前匿名内部类书写个数混乱的问题 可以得到解决
 *  前提:lambda表达式只能用于函数式接口
 *
 *  写法越简洁 前期越难理解 后期使用越方便
 */
public class B {
    public static void main(String[] args) {
        C c1 = new C() {
            @Override
            public void m1() {
                System.out.println("匿名内部类的方式重写m1方法");
            }
        };

        c1.m1();

        // 无参 无返回值 只有一条语句
        C c2 = ()-> System.out.println("lambda表达式的方式重写m1方法");


        c2.m1();

        // 有一个参数 无返回值 只有一条语句
        D d1 = (a)-> System.out.println("lambda表达式方式重写D接口m1方法" + a);


        d1.m1(100);
        // 有两个个参数 无返回值 只有一条语句
        E e1 = (a,b)-> System.out.println("lambda表达式方式重写E接口m1方法" + a + b);
        e1.m1(123, "abc");

        // 有两个参数 有返回值 只有一条语句
        F f1 = (a,b)-> a + b;
        System.out.println(f1.m1(10, 20));

        // 有一个参数 有返回值 有多条语句
        F f2 = (a,b)->{
            System.out.println(a + b);
            return a + b;
        };

    }
}

interface F{
    int m1(int a,int b);
}
interface E{
    void m1(int a,String b);
}
interface D{
    void m1(int a);
}
interface C{
    void m1();
}

6.方法引用

方法引用 :在 lambda 表达式的基础上 使用其他的方法的方法体 来作为 lambda 表达式(函数式接口中)

抽象方法的方法体

具体细节:被引用的方法体 原本的方法 返回值 形参列表 必须和 函数式接口中抽象方法的返回值 形参列表 完全匹配

否则 将无法引用

方法引用格式 ::

构造方法引用 类名 :: new;

静态方法引用 类名 :: 方法名;

实例方法引用 对象名 :: 方法名;

java
package com.atguigu.test2;

/**
 *  方法引用  :在lambda表达式的基础上 使用其他的方法的方法体 来作为 lambda表达式(函数式接口中)
 *  抽象方法的方法体
 *
 *  具体细节:被引用的方法体 原本的方法 返回值  形参列表 必须和 函数式接口中抽象方法的返回值 形参列表 完全匹配
 *  否则 将无法引用
 *
 *  方法引用格式 ::
 *  构造方法引用 类名 :: new;
 *  静态方法引用 类名 :: 方法名;
 *  实例方法引用 对象名 :: 方法名;
 *
 *
 */
public class TestMethodReference {
    public static void main(String[] args) {
        A a1 = ()-> System.out.println("");

        A a2 = Student :: new;
        a2.m1();


        B b1 = Student :: new;

        b1.m1("赵四");

        C c1 = Student :: new;
        c1.m1("a", 20);

        // 思考:哪个方法首先为静态方法
        // 并且参数为布尔类型 返回值为String类型的
        D<String,Boolean> d1 = String :: valueOf;
        System.out.println(d1.m1(true).length());

        D<Double,Double> d2 = Math :: abs;
        System.out.println(d2.m1(20.0));

        D<Integer,Float> d3 = Math :: round;

        System.out.println(d3.m1(3.5F));

        System.out.println("-----------------------------------------------------");

        String str = "abc";
        D<Boolean,String> d4 = str :: startsWith;
        System.out.println(d4.m1("def"));

        D<Boolean,String> d5 = str :: endsWith;
        System.out.println(d5.m1("c"));

        E<String> e1 = System.out :: println;
        e1.m1("hello world");


    }
}

interface E<P>{
    void m1(P p);
}
interface D<R,P>{
    R m1(P p);
}
interface C{
    void m1(String str,int num);
}
interface B{
    void m1(String str);
}
class Student{
    private String name;
    private int age;

    public Student(String name) {
        this.name = name;
        System.out.println("单个参数name属性的构造方法");
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("两个参数 name age属性的构造方法");
    }

    public Student() {
        System.out.println("无参构造方法");
    }
}
interface A{
    void m1();
}

JDK 提供的函数式接口位于 java.util.function 这个包中

这些函数式接口可以大致分为四类

消费型接口:Consumer<T> accept(T t) 只接受参数没有返回值

功能型接口:Function<T,R> R apply(T t) 有参数 有返回值

供给型接口:Supplier<T> T get() 没有参数 但是有返回值

断言型接口:Predicate<T> boolean test(T t) 有参数有返回值 但是返回值固定为布尔类型

java
package com.atguigu.test3;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 *  JDK提供的函数式接口位于 java.util.function 这个包中
 *  这些函数式接口可以大致分为四类
 *  消费型接口:Consumer<T> accept(T t)  只接受参数没有返回值
 *  功能型接口:Function<T,R> R apply(T t) 有参数 有返回值
 *  供给型接口:Supplier<T>  T get() 没有参数 但是有返回值
 *  断言型接口:Predicate<T> boolean test(T t) 有参数有返回值 但是返回值固定为布尔类型
 */
public class TestFunctional {
    public static void main(String[] args) {
        Consumer<Integer> consumer = System.out::println;
        consumer.accept(100);


        Function<String,Integer> function = Integer :: parseInt;
        System.out.println(function.apply("123"));

        Supplier<Double> supplier = Math ::random;
        System.out.println(supplier.get());

        Predicate<String> predicate = String :: isEmpty;

        System.out.println(predicate.test("abc"));


    }
}

Stream 流式编程

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

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

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

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

注意:

①Stream 自己不会存储元素。

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

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

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

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

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

创建方式

创建 Stream方式一:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法: default Stream<E> stream() : 返回一个顺序流

创建 Stream方式二:通过数组

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

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

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

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

public static<T> Stream<T> of(T... values) : 返回一个流

创建 Stream方式四:创建无限流(了解)

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

public static<T> Stream<T> generate(Supplier<T> s)

中间操作

方 法描 述
filter(Predicate p)保存符合指定条件的元素
distinct()筛选,通过流所生成元素的 equals() 去除重复元素
limit(long maxSize)保留指定个数的前
skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
sorted()产生一个新流,其中按自然顺序排序
map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

终止操作

方法描述
boolean allMatch(Predicate p)检查是否匹配所有元素
boolean anyMatch(Predicate p)检查是否至少匹配一个元素
boolean noneMatch(Predicate p)检查是否没有匹配所有元素
Optional<T> findFirst()返回第一个元素
long count()返回流中元素总数
Optional<T> max()返回流中最大值
Optional<T> min()返回流中最小值
void forEach(Consumer c)迭代

7. Optional 类

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

Optional 实际上是个容器:它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。

方法描述
of(Object obj)根据传入对象获取一个 Optional 对象,此对象不能为 null
empty()o包装一个保存有 null 的 Optional 对象
ofNullable(Object obj)根据传入对象获取一个 Optional 对象,此对象可以为 null
get()获取 Optional 中保存的对象,如果为 null,则报空指针异常
isPresent()表示判断 Optional 是否为 null,为 null 结果为 false,不为 null 结果为 true
ifPresent(Consumer<? super T> consumer)如果 Optional 对象中的对象不为 null,则消费此对象,否则不消费
orElse(T other)如果当前 Optional 对象中保存对象为 null,则使用传入对象
orElseGet(Supplier<? extends T> other)如果当前 Optional 对象中为 null 则获取到另外一个对象,否则不获取
orElseThrow(Supplier<? extends X> exceptionSupplier)如果当前 Optional 对象中为 null 则抛出异常,否则不抛出

Keep Reading, Keep Writing, Keep Coding