Java8新特性-函数式编程

文章介绍了函数式编程的概念及其优点,如代码简洁和易于并发编程。Lambda表达式是Java8引入的特性,用于简化单方法接口的实现,通过示例展示了Lambda的使用和语法糖,包括参数和返回类型推断。此外,还讲解了方法引用,包括静态方法、实例方法和构造方法引用,作为传递给FunctionalInterface的替代方式。
摘要由CSDN通过智能技术生成

1. 概述

1.1 为什么需要

● 大数量下处理集合效率高
● 代码可读性高
● 消灭嵌套地狱
在这里插入图片描述
在这里插入图片描述

1.2 函数式编程思想

1.2.1 概念

面向对象思想需要关注用什么对象完成什么事情。
而函数式编程思想就类似于我么数学中的函数,它主要关注的是对数据进行了什么操作。

函数式编程(Functional Programming)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。历史上研究函数式编程的理论是Lambda演算,所以我们经常把支持函数式编程的编码风格称为Lambda表达式。

1.2.2 优点

● 代码简洁,开发快速
● 接近自然语言,易于理解
● 易于并发编程

2. Lambda基础

2.1 前言

在了解Lambda之前,我们先回顾一下Java的方法。
Java的方法分为实例方法,以及静态方法。

public final class Integer {
    boolean equals(Object o) {
        ...
    }
    public static int parseInt(String s) {
        ...
    }
}

无论是实例方法,还是静态方法,本质上都相当于过程式语言的函数。例如C函数:

char* strcpy(char* dest, char* src)

只不过Java的实例方法隐含地传入了一个this变量,即实例方法总是有一个隐含参数this

函数式编程(Functional Programming)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。历史上研究函数式编程的理论是Lambda演算,所以我们经常把支持函数式编程的编码风格称为Lambda表达式。

2.2 Lambda表达式

在Java程序中,我们经常遇到一大堆单方法接口,即一个接口只定义了一个方法:
● Comparator
● Runnable
● Callable

以Comparator为例,我们想要调用Arrays.sort()时,可以传入一个Comparator实例,以匿名类方式编写如下:

String[] array = ...
Arrays.sort(array, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

上述写法非常繁琐。从Java 8开始,我们可以用Lambda表达式替换单方法接口。改写上述代码如下:

Arrays.sort(array, (s1, s2) -> {
            return s1.compareTo(s2);
            });

观察Lambda表达式的写法,它只需要写出方法定义:

(s1, s2) -> {
    return s1.compareTo(s2);
}

其中,参数是(s1, s2),参数类型可以省略,因为编译器可以自动推断出String类型。-> { ... }表示方法体,所有代码写在内部即可。Lambda表达式没有class定义,因此写法非常简洁。

如果只有一行return xxx的代码,完全可以用更简单的写法:

Arrays.sort(array, (s1, s2) -> s1.compareTo(s2));

返回值的类型也是由编译器自动推断的,这里推断出的返回值是int,因此,只要返回int,编译器就不会报错。

2.3 FunctionalInterface

我们把只定义了单方法的接口称之为FunctionalInterface,用注解@FunctionalInterface标记。例如,Callable接口:

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

再来看Comparator接口:

@FunctionalInterface
public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);

    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }

    default Comparator<T> thenComparing(Comparator<? super T> other) {
        ...
    }
    ...
}

虽然Comparator接口有很多方法,但只有一个抽象方法int compare(T o1, T o2),其他的方法都是default方法或static方法。另外注意到boolean equals(Object obj)Object定义的方法,不算在接口方法内。因此,Comparator也是一个FunctionalInterface

2.4 省略格式

可推导即可省略
Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以根据上下文推导得知的信息,都可以省略。

Lambda表达式:是可推导,可以省略,凡是根据上下文推导出来的内容,都可以省略书写。
● 小括号内参数的类型可以省略;
● 如果小括号内有且仅有一个参,那么类型和()都可以省略;
● 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。大括号、return关键字及语句分号要省一起省,要不省就都写上,不能只省其中一个。

2.5 小结

单方法接口被称为FunctionalInterface
接收FunctionalInterface作为参数的时候,可以把实例化的匿名类改写Lambda表达式,能大大简化代码。

Lambda表达式的参数和返回值均可由编译器自动推断。

3. 方法引用

3.1 前言

使用Lambda表达式,我们就可以不必编写FunctionalInterface接口的实现类,从而简化代码:

Arrays.sort(array, (s1, s2) -> {
    return s1.compareTo(s2);
});

3.2 静态方法引用

实际上,除了Lambda表达式,我们还可以直接传入方法引用。例如:

public class Main {
    public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, Main::cmp);
        System.out.println(String.join(", ", array));
    }

    static int cmp(String s1, String s2) {
        return s1.compareTo(s2);
    }
}

上述代码在Arrays.sort()中直接传入了静态方法cmp的引用,用Main::cmp表示。因此,所谓方法引用,是指如果某个方法签名和接口恰好一致,就可以直接传入方法引用。

因为Comparator<String>接口定义的方法是int compare(String, String),和静态方法int cmp(String, String)相比,除了方法名外,方法参数一致,返回类型相同,我们说两者的方法签名一致,可以直接把方法名作为Lambda表达式传入:

Arrays.sort(array, Main::cmp);

注意:在这里,方法签名只看参数类型和返回类型,不看方法名称,也不看类的继承关系。

3.3 实例方法引用

我们再看看如何引用实例方法。如果我们把代码改写如下:

public class Main {
    public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, String::compareTo);
        System.out.println(String.join(", ", array));
    }
}

不但可以编译通过,而且运行结果也是一样的,这说明String.compareTo()方法也符合Lambda定义。
观察String.compareTo()的方法定义:

public final class String {
    public int compareTo(String o) {
        ...
    }
}

思考: 这个方法的签名只有一个参数,为什么
int Comparator<String>.compare(String, String)能匹配呢?

因为实例方法有一个隐含的this参数String类的compareTo()方法在实际调用的时候,第一个隐含参数总是传入this,相当于静态方法:

public static int compareTo(this, String o);

所以,String.compareTo()方法也可作为方法引用传入。

3.4 构造方法引用

除了可以引用静态方法和实例方法,我们还可以引用构造方法。
我们来看一个例子:如果要把一个List<String>转换为List<Person>,应该怎么办?

class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
}

List<String> names = List.of("Bob", "Alice", "Tim");
List<Person> persons = ???

传统的做法是先定义一个ArrayList<Person>,然后用for循环填充这个List.

要更简单地实现StringPerson的转换,我们可以引用Person的构造方法:

public class Main {
    public static void main(String[] args) {
        List<String> names = List.of("Bob", "Alice", "Tim");
        List<Person> persons = names.stream().map(Person::new).collect(Collectors.toList());
        System.out.println(persons);
    }
}

class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
    public String toString() {
        return "Person:" + this.name;
    }
}

现在我们看到,这里的map()需要传入的FunctionalInterface的定义是:

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

把泛型对应上就是方法签名Person apply(String),即传入参数String,返回类型Person。而Person类的构造方法恰好满足这个条件,因为构造方法的参数是String而构造方法虽然没有return语句,但它会隐式地返回this实例,类型就是Person,因此,此处可以引用构造方法。构造方法的引用写法是类名::new,因此,此处传入Person::new

3.5 小结

FunctionalInterface允许传入:
● 接口的实现类(传统写法,代码较繁琐);
● Lambda表达式(只需列出参数名,由编译器推断类型);
● 符合方法签名的静态方法;
● 符合方法签名的实例方法(实例类型被看做第一个参数类型);
● 符合方法签名的构造方法(实例类型被看做返回类型)。

FunctionalInterface不强制继承关系,不需要方法名称相同,只要求方法参数(类型和数量)与方法返回类型相同,即认为方法签名相同

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值