Lambda表达式

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了提升。

语法格式

Lambda 无参,无返回值

@Test
public void test1() {
    Runnable run1 = new Runnable() {
        @Override
        public void run() {
            System.out.println("庆祝深圳40周年");
        }
    };
    run1.run();

    System.out.println("---------Lambda表达式---------");

    Runnable run2 = () -> System.out.println("庆祝深圳40周年");
    run2.run();
}
庆祝深圳40周年
---------Lambda表达式---------
庆祝深圳40周年

Lambda 需要一个参数,但是没有返回值。且 Lambda 若只需要一个参数时,参数的小括号可以省略。数据类型也可以省略,因为可由编译器推断得出,称为“类型推断”。

类型推断例如:

ArrayList list = new ArrayList<>(); //类型推断

int[] arr = {1,2,3}; //类型推断

@Test
public void test2() {
    Consumer<String> consumer1 = new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println("我爱" + s);
        }
    };
    consumer1.accept("中国");

    System.out.println("---------Lambda表达式---------");

    Consumer<String> consumer2 = s -> System.out.println("我爱" + s);
    consumer2.accept("中国");
}
我爱中国
---------Lambda表达式---------
我爱中国

Lambda 体只有一条语句时,return 与大括号若有,都可以省略。

@Test
public void test3() {
    Comparator<Integer> comparator1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1.compareTo(o2);
        }
    };
    System.out.println(comparator1.compare(10, 20));

    System.out.println("---------Lambda表达式---------");

    Comparator<Integer> comparator2 = (o1, o2) -> o1.compareTo(o2);
    System.out.println(comparator2.compare(10, 20));
}
-1
---------Lambda表达式---------
-1

Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值。

@Test
public void test4() {
    Comparator<Integer> comparator1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        }
    };
    System.out.println(comparator1.compare(30, 20));

    System.out.println("---------Lambda表达式---------");

    Comparator<Integer> comparator2 = (o1, o2) -> {
        System.out.println(o1);
        System.out.println(o2);
        return o1.compareTo(o2);
    };
    System.out.println(comparator2.compare(30, 20));
}
30
20
1
---------Lambda表达式---------
30
20
1

函数式(Functional)接口

Lambda 表达式的本质:作为函数式接口的实例

如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。

如何理解函数式接口?

Java 从诞生日起就是一直倡导“一切皆对象”, 在 Java 里面面向对象(OOP)编程是一切。但是随着 pythonscala 等语言的兴起和新技术的挑战, Java 不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持 OOP 还可以支持 OOF(面向函数编程)。

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

所以以前用匿名实现类表示的现在都可以用 Lambda 表达式来写。

Java 内置四大核心函数式接口

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

测试使用

Consumer(消费型接口)

public void smallToBig(int age, Consumer<Integer> con) {
    con.accept(age);
}

@Test
public void test1() {
    smallToBig(20, new Consumer<Integer>() {
        @Override
        public void accept(Integer age) {
            System.out.println("小不忍则乱大谋 " + age);
        }
    });

    System.out.println("---------Lambda表达式---------");

    smallToBig(20, age -> System.out.println("小不忍则乱大谋 " + age));
}
小不忍则乱大谋 20
---------Lambda表达式---------
小不忍则乱大谋 20

Predicate(断定性接口)

//根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定
public List<String> filterString(List<String> list, Predicate<String> pre){
    ArrayList<String> filterList = new ArrayList<>();
    for (String s : list) {
        if (pre.test(s)) {
            filterList.add(s);
        }
    }
    return filterList;
}

@Test
public void test2(){
    List<String> array = Arrays.asList("北京", "南京", "天津", "普京", "东京");
    List<String> filterString1 = filterString(array, new Predicate<String>() {
        @Override
        public boolean test(String s) {
            return s.contains("京");
        }
    });
    System.out.println(filterString1);

    System.out.println("---------Lambda表达式---------");

    List<String> filterString2 = filterString(array, s -> s.contains("京"));
    System.out.println(filterString2);
}
[北京, 南京, 普京, 东京]
---------Lambda表达式---------
[北京, 南京, 普京, 东京]

方法引用构造器引用

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

方法引用可以看做是 Lambda 表达式深层次的表达。换句话说,方法引用就是 Lambda 表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是 Lambda 表达式的一个"语法糖"。

要求: 实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!

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

如下三种主要使用情况:

  • 对象::实例方法名
  • 类::静态方法名
  • 类::实例方法名

方法引用

情况一:对象 :: 实例方法

  • Consumer 中的 void accept(T t)
  • PrintStream 中的 void println(T t)
@Test
public void test1(){
    Consumer<String> con1 = str -> System.out.println(str);
    con1.accept("钟小湖");
    System.out.println("*******************");
    Consumer<String> con2 = System.out::println;
    con2.accept("康小穷");
}
钟小湖
-------------方法引用-------------
康小穷
  • Supplier中的 T get()
  • Employee中的 String getName()
@Data
@NoArgsConstructor
@AllArgsConstructor
class Employee {
    String name;
}
@Test
public void test2() {
    Employee emp = new Employee( "Tom");
    Supplier<String> sup1 = () -> emp.getName();
    System.out.println(sup1.get());

    System.out.println("-------------方法引用-------------");

    Supplier<String> sup2 = emp::getName;
    System.out.println(sup2.get());
}
Tom
-------------方法引用-------------
Tom

情况二:类 :: 静态方法

例1:

  • Comparator 中的 int compare(T t1,T t2)
  • Integer 中的 int compare(T t1,T t2)
@Test
public void test3() {
    Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1,t2);
    System.out.println(com1.compare(12,21));

    System.out.println("-------------方法引用-------------");

    Comparator<Integer> com2 = Integer::compare;
    System.out.println(com2.compare(12,3));

}
-1
-------------方法引用-------------
1
  • Function中的 R apply(T t)
  • Math中的 Long round(Double d)
@Test
public void test4() {
    Function<Double,Long> func1 = new Function<Double, Long>() {
        @Override
        public Long apply(Double d) {
            return Math.round(d);
        }
    };
    System.out.println(func1.apply(16.888));

    System.out.println("-------------lambda-------------");
    Function<Double,Long> func2 = d -> Math.round(d);
    System.out.println(func2.apply(12.3));

    System.out.println("-------------方法引用-------------");
    Function<Double,Long> func3 = Math::round;
    System.out.println(func3.apply(12.6));
}
17
-------------方法引用-------------
12
-------------方法引用-------------
13

情况三:类 :: 实例方法 (有难度)

例1:

  • Comparator 中的 int comapre(T t1,T t2)
  • String中的 int t1.compareTo(t2)
@Test
public void test5() {
    Comparator<Integer> com1 = (s1, s2) -> s1.compareTo(s2);
    System.out.println(com1.compare(18, 19));

    System.out.println("-------------方法引用-------------");

    Comparator<Integer> com2 = Integer::compareTo;
    System.out.println(com2.compare(20, 19));
}
-1
-------------方法引用-------------
1

例2:

  • BiPredicate 中的 boolean test(T t1, T t2);
  • String 中的 boolean t1.equals(t2)
@Test
public void test6() {
    BiPredicate<String, String> pre1 = (s1, s2) -> s1.equals(s2);
    System.out.println(pre1.test("abc", "abc"));

    System.out.println("-------------方法引用-------------");

    BiPredicate<String, String> pre2 = String::equals;
    System.out.println(pre2.test("abc", "abd"));
}
true
-------------方法引用-------------
false

总结:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同!(针对于情况1和情况2)

构造器引用

和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。

抽象方法的返回值类型即为构造器所属的类的类型。

例1:

  • Supplier中的T get()
  • Employee的空参构造器:Employee()
@Test
public void test7(){

    Supplier<Employee> sup = new Supplier<Employee>() {
        @Override
        public Employee get() {
            return new Employee();
        }
    };

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

    Supplier<Employee>  sup1 = () -> new Employee();
    System.out.println(sup1.get());

    System.out.println("-------------方法引用-------------");

    Supplier<Employee>  sup2 = Employee :: new;
    System.out.println(sup2.get());
}
-------------lambda-------------
Employee(name=null)
-------------方法引用-------------
Employee(name=null)

例2:Function中的 R apply(T t)

@Test
public void test8() {
    Function<String, Employee> function1 = d -> new Employee(d);
    System.out.println(function1.apply("钟小湖"));
    Function<String, Employee> function2 = Employee::new;
    System.out.println(function2.apply("乐心湖"));
}
Employee(name=钟小湖)
Employee(name=乐心湖)

例3:BiFunction中的 R apply(T t,U u)

@Test
public void test9() {
    BiFunction<Integer, String, Employee> biFunction1 = (d, s) -> new Employee(d, s);
    System.out.println(biFunction1.apply(1, "钟小湖"));
    BiFunction<Integer, String, Employee> biFunction2 = Employee::new;
    System.out.println(biFunction2.apply(1, "钟小湖"));
}
Employee(id=1, name=钟小湖)
Employee(id=1, name=钟小湖)

数组引用

可以把数组看做是一个特殊的类,则写法与构造器引用一致。

@Test
public void test10() {
    Function<Integer, String[]> func1 = length -> new String[length];
    String[] arr1 = func1.apply(5);
    System.out.println(Arrays.toString(arr1));

    Function<Integer, String[]> func2 = String[]::new;
    String[] arr2 = func2.apply(5);
    System.out.println(Arrays.toString(arr2));
}
[null, null, null, null, null]
[null, null, null, null, null]

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 提供了一种高效且易于使用的处理数据的方式。
为什么要使用Stream API ?

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

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

什么是 Stream ?

是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

“集合讲的是数据, Stream讲的是计算!”

注意:

  • Stream 自己不会存储元素。
  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream 的操作三个步骤

1、创建 Stream

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

2、中间操作

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

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

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

如下图所示

常用方法:https://www.xn2001.com/archives/466.html

Optional

概述

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

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

总结:Optional类是为了解决空指针问题而生。

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

  • Optional.of(T t) :

    创建一个 Optional 实例, t必须非空;

  • Optional.empty() :

    创建一个空的 Optional 实例

  • Optional.ofNullable(T t):

    t可以为null判断Optional容器中是否包含对象:

  • boolean isPresent() :

    判断是否包含对象

  • void ifPresent(Consumer<? super T> consumer) :

    如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。

获取Optional容器的对象:

  • T get():

    如果调用对象包含值,返回该值,否则抛异常

  • T orElse(T other) :

    如果有值则将其返回,否则返回指定的other对象。

  • T orElseGet(Supplier<? extends T> other) :

    如果有值则将其返回,否则返回由Supplier接口实现提供的对象。

  • T orElseThrow(Supplier<? extends X> exceptionSupplier) :

    如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。

参考资料

https://codeyee.com/archives/java-jdk8.html

https://www.bilibili.com/video/BV1Kb411W75N


Last modification:October 15, 2020
如果觉得我的文章对你有用,请随意赞赏