Java8如何使用Lambda表达式简化匿名内部类

函数式接口(FunctionalInterface)

函数式接口简介

只包含一个抽象方法的接口,称为函数式接口。

我们可以通过Lambda表达式来创建该接口的对象。如果Lambda表达式抛出一个非运行时异常,那么该异常需要在目标接口的抽象方法上进行声明。

Java8中用@FunctionalInterface来检查函数式接口,当然我们也可以在自己写的接口上使用这个注解来检查接口是否是函数式接口。
Java8如何使用Lambda表达式简化匿名内部类_第1张图片
在java.util.function包下定义了许多函数式接口。


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

函数式接口 参数类型 返回值类型 作用
Consumer消费型接口 T void 对传入的类型为T的对象进行处理
Supplier供给型接口 T 返回类型为T的对象
Function 函数型接口 T R 对T类型的对象进行处理并且返回类型为R的对象
Predicate 断定型接口 T boolean 判断类型为T的对象是否满足某种约束
@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);
    
}
@FunctionalInterface
public interface Supplier<T> {

    T get();
    
}
@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

}
@FunctionalInterface
public interface Predicate<T> {
    
    boolean test(T t);
    
}

其他部分接口

函数式接口 参数类型 返回值类型 作用
BiFunction T, U R 对类型为T,U的参数进行操作,返回R类型的结果。
UnaryOperator(Function子接口) T T 对类型为T的对象进行一元运算,并返回T类型的结果。
BinaryOperator(BiFunction子接口) T, T T 对类型为T的对象进行二元运算,并返回T类型的结果。
BiConsumer T,U void 对类型为T,U参数进行操作。
BiPredicate T,U boolean 判断参数T,U是否满足某种约束
ToIntFunction,ToLongFunction,ToDoubleFunction T int,long,double 分别计算int、long、double值的函数
IntFunction,LongFunction,DoubleFunction int,long,double R 参数分别为int、long、double类型的函数

看着貌似多了很多接口,其实并没有脱离接口原来的作用,只是jdk提供一个接口带着一个抽象方法供开发者自己去实现。函数式接口的定义也是为了提供一种新的语法:Lambda表达式。如果有需要,完全可以自己定义函数式接口。



Lambda表达式

Lambda表达式是啥?

众所周知,Java提倡的是”一切皆对象“。但是一些新的语言及技术的兴起,Java不得不做出调整以支持更加广泛的技术要求,也就是需要支持OOF(面向函数编程)。

在函数式编程语言中,Lambda表达式的类型是函数。但是在Java中,Lambda表达式的类型是对象,且必须依附于函数式接口。简单来说,Lambda表达式就是函数式接口的实例,只要一个对象是函数式接口的实例就可以用Lambda表达式来表示。所以,以前用匿名实现类表示的都可以用Lambda表达式来编写。

Lambda表达式是Java8引入的一种新的语法。这里又用到一种新的操作符,这个操作符就是”->”,称为Lambda表达式操作符或者箭头操作符。

该操作符将Lambda表达式分为两个部分:

左侧:指定了Lambda表达式需要的参数列表。

右侧:指定了Lambda表达式体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。

Lambda表达式基本的语法格式:

(参数列表) -> {Lambda表达式体}

为什么要用Lambda表达式?

Lambda表达式是一个匿名函数,是一段可以传递的代码。使用Lambda表达式能够写出更简洁、更灵活的代码。

既然Lambda表达式是一个函数式接口的实例,那么就找一个函数式接口来应用一下。
Java8如何使用Lambda表达式简化匿名内部类_第2张图片
在之前用匿名内部类创建一个Runnable接口对象是这样写的:

Runnable run = new Runnable() {
    @Override
    public void run() {
    	System.out.println("###");
    }
};

现在可以这样写:

Runnable run = () -> {System.out.println("lambda表达式实现");};

一行代码就实现了之前好多行代码的功能,确实很简洁。

Lambda表达式的使用语法

  1. 无参数,无返回值
Runnable run = () -> {System.out.println("lambda表达式实现");};

如果执行语句只有一行,可以省略”{}“:

Runnable run = () -> System.out.println("lambda表达式实现");
  1. 有一个参数,无返回值
Consumer<String> consumer = (String s) -> {System.out.println(s);};

参数类型可以省略,因为编译器会根据上下文环境推断出参数的类型,也叫做类型推断。这里指定了泛型为String。

而且只有一个参数时,参数列表的”()“也可以省略。所以可以这么写:

Consumer<String> consumer =  s -> System.out.println(s);
  1. 有多个参数,有返回值
Comparator<Integer> comparator1 = (o1,o2) -> {
    System.out.println(o2);
    return Integer.compare(o1,o2);
};
  1. 只有一条执行语句且有返回值
Comparator<Integer> comparator1 = (o1,o2) -> {
    return Integer.compare(o1,o2);
};

如果有返回值时只有一条执行语句,那么”return“可以省略。

Comparator<Integer> comparator1 = (o1,o2) -> Integer.compare(o1,o2);
  1. 将Lambda表达式作为参数传递
Thread thread = new Thread(() -> {
    System.out.println("lambda表达式实现");
});

为了将Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该Lambda 表达式兼容的函数式接口的类型。比如上面代码中的Lambda表达式的作用就是创建了一个Runnale对象,而new Thread()的参数可以是一个Runnable对象。



方法引用和构造器引用

方法引用

方法引用是Lambda表达式的另一种表现形式。简单来说,就是将Lambda表达式的内容封装成一个方法再进行调用或者已经有现成的方法可以直接使用。

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

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

方法引用中引入了一个新的操作符:“::”

使用格式:类(或对象):: 方法名

具体分为三种情况:

  1. 对象::非静态方法名
  2. 类::静态方法名
  3. 类::非静态方法名

举个简单的例子:

比如遍历一个集合,将每个对象打印出来,原来是这样写:

@Test
public void test() {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    for (Integer integer : list) {
        System.out.println(integer);
    }
}

Java8提供一个新方法forEach进行遍历:
Java8如何使用Lambda表达式简化匿名内部类_第3张图片
而该方法的参数是一个消费型接口,有一个入参,而返回值为void。
Java8如何使用Lambda表达式简化匿名内部类_第4张图片
而System.out.println方法的入参格式和返回值格式正好与之相同。也就是满足方法引用的要求。现在可以改造成这样:

 @Test
 public void test01() {
     List<Integer> list = new ArrayList<>();
     list.add(1);
     list.add(2);
     list.add(3);
     // list.forEach(integer -> System.out.println(integer));
     list.forEach(System.out::println);
 }

又或者这种情况:

BiPredicate<String, String> biPredicate = (s1,s2) -> s1.equals(s2);
// 等同于
BiPredicate<String, String> biPredicate1 = String::equals;

注意:当函数式接口方法的第一个参数是引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName

构造器引用

构造器引用无非就是方法引用的一种特殊类型。虽然构造器理论上没有返回值,但是变相地又有返回值(对象)。

// 创建一个对象
String s = new String();

那不是和供给型接口类似吗,这个时候就可以使用构造器引用。

格式:ClassName::new

要求:构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。

例如:

Supplier<String> stringSupplier = () -> new String();
// 等价于
Supplier<String> stringSupplier = String::new;

数组也可以:

Function<Integer, Integer[]> function = (i) -> new Integer[i];
// 等价于
Function<Integer, Integer[]> function = Integer[]::new;

你可能感兴趣的:(Java基础,java,开发语言)