Java是一门不断更新迭代的语言。以下是一些重要更新点:

  • JDK1.5:增强for循环、可变参数列表、静态导入、枚举类型、包装类的自动装箱和拆箱、泛型
  • JDK1.7:二进制字面量、数字常量下划线、Switch运算中String类型和枚举类型的引入、try-with-resource资源自动释放

image.png

  • JDK1.8:接口的默认方法和静态方法、Lambda表达式、方法引用、Optional、Stream,大部分新增的内容都是围绕函数式编程而生的

    接口的默认方法和静态方法

    接口中的方法一般都是抽象方法。而有些方法十分常用,且方法体固定,如果每次实现类都要重写这个方法,就十分麻烦。Java1.8为接口提供了默认方法和静态方法,使得接口中也可以进行方法的具体实现。
    默认方法由default修饰符修饰,可以为接口添加默认方法,并实现方法体。实现接口的实现类既不必重写默认方法,也能够“继承”默认方法的实现。默认方法可以让接口更优雅地升级,不必随着接口方法增加,每次都需要修改实现类的代码,也可以让实现类中省略很多不必要方法的空实现。
    接口的静态方法与类的静态方法完全相同,不能被继承,也不能被重写。因为接口没有具体的对象,故也只能通过接口名调用。

    1. interface A {
    2. static void show() {
    3. System.out.println("SSS");
    4. }
    5. default void defaultMethod() {
    6. System.out.println("DDD");
    7. }
    8. }
    9. class B implements A {
    10. /** A没有合适的方法必须重写 **/
    11. public static void main(String[] args) {
    12. A.show(); // 打印SSS
    13. new B().defaultMethod(); // 打印DDD
    14. }
    15. }

    如果实现类重写了接口的默认方法,则调用重写以后的。如果一个类实现了多个接口,且接口之间没有继承关系,但是默认方法名相同,则只能重写该默认方法后才能调用。

    Lambda表达式

    Lambda表达式就是对匿名内部类的一种简化。但在此之前,仍然需要对实现的接口/抽象类,以及抽象方法进行约定。因为进行了约定,故方法名称、参数列表和返回值的类型都是确定的,在Lambda表达式使用过程中可以被省略。
    Lambda表达式的格式为接口的引用 = () -> {}。其中:

  • ()内为要实现方法的参数列表。参数列表的参数类型可以省略。当参数只有一个时,括号也可以省略。没有参数时,需要保留括号。当接口的引用作为一个重载方法的参数时,类型需要强转以符合其中一个方法。

  • {}内为方法体和返回值。当方法体只有一行时,大括号可以省略。当只有一句返回语句时,return也可以省略。
    1. FunInt f = () -> {
    2. System.out.println();
    3. System.out.println("ABCBoy");
    4. }
    5. FunInt1 f1 = () -> System.out.println("ABCBoy"); // 只有一句可以省略{}
    6. FunInt2 f2 = () -> System.out::println; // 这里是方法的引用
    7. FunInt3 f3 = (int a, int b) -> { return a+b;};
    8. FunInt4 f4 = (a, b) -> a+b; // 可以省略参数类型和return
    9. int sum = f4.sum(2, 7); // 假设该方法名为sum
    Lambda表达式依旧是一个匿名内部类,其中的局部变量依旧需要是一个final常量或未被改变值的变量。

    函数式编程

    函数式编程,和面向对象编程、面向过程编程一样,都是编程的一种方式。函数式编程是面向数学的抽象,将计算过程描述为一种表达式求值的过程,且具有明确的返回值。
    行为参数化是函数式编程的一种形式。将核心代码(方法/函数)通过传参的形式传入到方法中,从而实现不同的功能。在此过程中,核心代码的方法名并不是需要关心的。

    函数式接口

    因为Lambda表达式省略了方法名,故要实现的接口中有且只能有一个抽象方法,当然包括可能继承和实现的方法,但静态方法和默认方法不在此列。这样的接口被称为函数式接口。
    1. @FunctionalInterface // 可以为接口添加该注解,用编译器检查该接口是否是函数式接口
    2. interface FunInt {
    3. void show();
    4. }
    当然,实现了方法并不意味着调用了方法。依旧需要通过对象对方法使用方法名进行调用。
    当一个函数式接口的引用作为一个方法的参数时,可以在传参的同时,实现该接口,即接口的参数化。例如之前的Runnable和Comparator接口: ```java ExecutorService pool = Executors.newCachedThreadPool(); pool.execute(() -> { / 要执行的代码 / }); // 这里的参数是Runnable实现类对象

Set set = new TreeSet<>((o1, o2) -> 1); // 这里的参数是Comparator实现类对象

  1. <a name="DAX83"></a>
  2. ### JDK提供的接口
  3. JDK1.8中,定义了一些常用的函数式接口,这些接口都定义在java.lang.function包中。
  4. ![](https://cdn.nlark.com/yuque/0/2021/jpeg/2437684/1621220612338-a0e3ea59-fc20-4ac6-a5f2-7aba3b445230.jpeg)
  5. - Predicate(判断条件):传入一个对象,返回一个布尔值,函数名称为test。通常用于对传入的对象进行判断。常用在if表达式中,以影响方法流程的执行。拥有and()、or()、negate()三个默认方法,用于连接Predicate条件,形成复合条件。
  6. ```java
  7. public static List<Integer> filter(
  8. List<Integer> list, Predicate<Integer> p) {
  9. // 该方法传入一个整形列表,根据Predicate表达式的条件
  10. // 返回由特定数字组成的列表
  11. List<Integer> data = new ArrayList<>();
  12. for (Integer integer : list) { // 遍历
  13. if(p.test(integer)) { // 判断当前数字对象,判断条件见实现类
  14. data.add(integer); // 满足条件则放入列表中
  15. }
  16. }
  17. return data; // 返回列表
  18. }
  19. public static void main(String[] args) {
  20. Predicate<Integer> p = t -> { // 实现类,实现其中的test方法
  21. if(t > 4) { // 判断条件是大于4的整数
  22. return true;
  23. }
  24. return false;
  25. };
  26. Predicate<Integer> p2 = t -> {
  27. if(t < 8) { // 判断条件是小于8的整数
  28. return true;
  29. }
  30. return false;
  31. };
  32. Predicate<Integer> p3 = p.and(p2);
  33. List<Integer> list = filter(alist, p3);
  34. // 筛选条件是p1&&p2的p3
  35. // 传入不同的筛选条件获得不同的列表
  36. }
  • Consumer(处理器):传入一个对象,没有返回值,函数名称为accept。通常用于对传入的对象进行一些操作,如给属性赋值、遍历等。拥有andThen()默认方法,用于连接两个Consumer操作。

    1. Consumer<String> s = t -> System.out.println(t);
    2. // 传入一个字符串,对字符串进行打印
    3. List<String> list = new ArrayList<>(); // 定义一个集合
    4. list.add("A");
    5. list.add("B"); // 添加两个元素
    6. list.forEach(s);
    7. // Collection接口提供的forEach默认方法,传入一个Consumer的实现类对象
    8. // 对集合进行遍历,对集合的每个对象进行Consumer的操作,这里是打印
  • Function(函数):传入一个对象,返回一个对象(这两个对象类型可以不同),函数名称为apply。通常用于对传入的对象进行一些操作,并获取一个返回值,如获取对象属性等。拥有compose()和andThen()默认方法,用于连接两个Function操作。拥有identity()静态方法,用于直接返回传入的Function的参数,不做任何操作。 ```java public static int jisuan(String info, Function function) { // 传入一个字符串和一个Function操作,返回一个Integer return function.apply(info); // 对传入的字符串执行Function操作 }

public static void main(String[] args) { String info = “蒙娜丽莎,15 小李,16 小红,18”;

  1. int result = jisuan(info, t -> { // 传入上面的字符串,并对字符串进行操作
  2. String[] split = t.split(" ");
  3. int sum = 0;
  4. for (String string : split) {
  5. String[] split2 = string.split(",");
  6. sum += Integer.parseInt(split2[1]);
  7. }
  8. return sum / 3; // 这一串操作是对字符串中的数字进行加和并求平均值
  9. });
  10. System.out.println(result);

}

  1. - Supplier(提供器):不传入任何参数,返回一个对象,函数名称为get。通常执行一段代码,并返回一个值。没有任何默认方法。
  2. - UnaryOperator
  3. - BinaryOperator
  4. 因为上面的一些方法的返回的都是对象,对于基本数据类型需要频繁的进行拆箱和装箱。为了提供运行速度,也对基本数据类型提供了特化的接口:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/2437684/1599374546191-82857499-92fa-4467-a5a2-2574b3fb9c01.png#height=242&id=eQVG8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=323&originWidth=913&originalType=binary&ratio=1&size=43587&status=done&style=none&width=685)
  5. <a name="KcnOr"></a>
  6. ### 集合中的默认方法
  7. CollectionMap接口中,在JDK1.8新增了一些默认方法。这些方法都需要传入一个上面的函数式接口的实现类对象,对集合或集合的元素进行一些操作。<br />Collection列表:
  8. - removeIf(Predicate<? super T> filter):对元素进行遍历,对满足filter条件的元素进行移除
  9. - forEach(Consumer<? super T> action):对元素进行遍历,执行action操作
  10. Map集合:
  11. - computeIfAbsent(K key, Function<K, V> mappingFunction):若key不存在,则执行mappingFunction的操作获取一个value,并添加到Map
  12. <a name="aPaML"></a>
  13. ## 方法引用
  14. 方法引用是对Lambda表达式的再次简化。对于Lambda表达式中只使用了仅一个函数(方法)的情况,将参数列表和传入参数这部分代码进行省略。<br />使用方式:

Clazz::new // 构造器引用 Clazz::StaticMethodName // 静态方法引用 Clazz::InstenceMethodName // 当前对象的方法引用 object::InstenceMethodName // 特定对象的方法引用

  1. 例如:
  2. ```java
  3. // 一接口(Stream)中某方法的定义为
  4. Stream<T> filter(Predicate<? super T> predicate);
  5. // 使用时,既可以实现Predicate(判断条件),传入一个匿名内部类或Lambda表达式;
  6. // 也可以使用其他类中的具体方法作为方法的引用,作为该Predicate的实现:
  7. stream.filter(Objects::nonNull);
  8. // Objects::nonNull的定义为:
  9. public static boolean nonNull(Object obj) {
  10. return obj != null;
  11. }

使用方法引用,能够将其他类中的方法作为函数式接口中方法的实现,能够多次复用代码。缺点是太简略了,可读性不佳,除了能装逼,一点都不好用

流对象

这里的流对象不是输入输出流,也不是其中的对象流,而是一种集合的抽象。它能对集合进行过滤(Filter,移除或获取指定的元素)、排序(Sort,按特定顺序排序)、映射(Map,提取元素的某些属性或处理每个元素)、归约(reduce,对集合进行求和等操作)等加工。
以下是一个遍历课程列表(Course courses)获取学时(Long hours)并求学时总和的例子:

  1. courses.stream() // 获取流对象
  2. .map(Course::getHours) // 获取集合每个元素的hours属性,得到一个新的流对象
  3. .filter(Objects::nonNull) // 去除流对象中的空对象
  4. .reduce(Long::sum) // 将所有对象加和,得到一个Optional对象
  5. .orElse(0L); // 若Optional对象属性为null,则返回0L

Optional对象

Optional对象是对Object的封装,包含一个value属性,主要是避免空指针异常。

  1. Optional.of(value); // 赋值,value不能为null
  2. Optional.ofNullable(value); // 赋值,value可以为null
  3. Optional.empty(); // 获取一个value为null的optional
  4. optional.get(); // 取值,value为null会抛异常
  5. optional.isPresent(); // 返回boolean,value为null为false
  6. optional.ifPresent(value -> value.doSomething()); // value不为空就执行某方法
  7. optional.orElse(0); // value为空返回0否则返回value
  8. optional.orElseGet(() -> xxx.get()); // value为空返回Lambda方法返回值否则返回value
  9. optional.orElseThrow(() -> new Exception()); // value为null就抛异常