Java面向对象之函数式编程

时间:2023-02-13 11:38:32

1 函数式编程

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“用什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式来做。

1.1 做什么,而不是怎么做

  • 例如:

new Thread(new Runnable(){
@Override
public void run(){
//TODO things
}
}).start();

对于 ​​Runnable​​ 的匿名内部类用法,可以分析出几点内容:

  • ​Thread​​​ 类需要 ​​Runnable​​ 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心;
  • 为了指定 run 的方法体,我们必须要创建 ​​Runnable​​ 接口的实现类;
  • 为了省去定义一个 ​​Runnable​​ 实现类的麻烦,我们必须要使用匿名内部类;
  • 在匿名内部类中,我们必须覆盖重写抽象的 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能有错;
  • 而实际上,大概仅有 run() 方法体才是程序中最需要的关键所在。
  • 我们真正希望做的事情是,将 run 方法体内的代码传递给 Thread 类并去执行。

思考:

我们真的希望创建一个匿名内部类对象吗?不!

我们只是为了做这件事情而不得不创建一个对象。

1.2 函数式编程的本质是什么?

传递一段代码——这才是我们真正目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。那么,有没有更加简单的方法呢?

如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。

匿名内部类存在的一个问题是,即使匿名内部类的实现非常简单,例如只包含一个抽象方法的接口,那么匿名内部类的语法仍然显得比较冗余。

解决方法:可以使用JDK8开始支持的 lambda 表达式,这种表达式只针对有一个抽象方法的接口实现,以简洁的表达式形式实现接口功能来作为方法参数。

new Thread(() -> System.out.println("多线程任务执行!")).start();

这段代码和刚才的执行效果是完全一样的,可以在1.8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。

不再有“不得不创建接口对象”的束缚,不再有“抽象方法覆盖重写”的负担,就是这么简单!

1.3 Lambda表达式

lambda 表达式的基本语法格式

语句中通过箭头来区分开参数列表和方法体

Java面向对象之函数式编程

2 函数式接口

2.1 是否可以使用 lambda 代替所有匿名内部类?

接口中有且只有一个抽象方法时,才能使用 lambda 表达式代替匿名内部类。这是因为 lambda 表达式是基于函数式接口实现的。

所谓函数式接口是指有且只有一个抽象方法的接口,lambda 表达式就是java中函数式编程的体现,只有确保接口中有且只有一个抽象方法,lambda 表达式才能顺利地推导出所实现的这个接口中的方法。

2.2 定义

在JDK8中,接口上标注有 @FunctionalInterface 注解的即为函数式接口,在函数式接口内部有且只有一个抽象方法。

来看下 Runnable 接口中的源码:

Java面向对象之函数式编程

说明:@FunctionalInterface 注解只是显式的标注了接口是一个函数式接口,并强制编辑器进行更严格的检查,确保该接口是函数式接口。

JDK8之前已有的部分函数式接口:

  • java.lang.Runnable
  • java.util.Comparater
  • java.io.FileFilter
  • java.lang.Reflect.InvocationHandler

JDK8 增加的函数式接口

java.util.function 包下包含了很多类,用来支持java的函数式编程。

Java面向对象之函数式编程

2.3 Lambda 表达式的简化

lambda 表达式的省略写法(进一步在lambda表达式的基础上继续简化)

1、如果lambda表达式的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号!

2、如果lambda表达式的方法体代码只有一行代码,如果这行代码是return 语句,必须省略return 不写,同时省略分号。

3、参数类型可以省略不写。

4、如果只有一个参数,参数外的()也可以省略。

除了以上简化规则,还可以使用“方法引用”进一步简化 lambda 表达式。

public class TestOutter {
public static void main(String[] args) {
//匿名函数写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1启动了!");

}
}).start();

//lambda写法
new Thread(() -> {System.out.println("线程2启动了!");}).start();
}
}

Java面向对象之函数式编程

3 方法引用

说明:lambda 表达式的主体只有一条语句时,程序不仅可以省略包含主体的大括号,还可以通过英文双冒号“::”的语法格式来引用方法和构造器(即构造方法)。

作用:可以进一步简化lambda表达式的书写,其本质都是对 lambda 表达式的主体部分已存在的方法进行直接引用,主要区别就是对普通方法与构造方法的引用而已。

3.1 类名引用静态方法

定义:类名引用静态方法也就是通过类名对静态方法的引用,该类可以是java自带的特殊类,也可以是自定义的普通类。

引用示例如下:

//定义函数式接口
@FunctionalInterface
interface Calcable{
int calc(int num);
}

//定义一个类,类中定义一个静态方法
class Math{
public static int abs(int num){
return num > 0 ? num : -num;
}
}

public class Example {
private static void printAbs(int num, Calcable calcable){
System.out.println(calcable.calc(num));
}

public static void main(String[] args) {

//1、使用匿名内部类
printAbs(-8, new Calcable() {
@Override
public int calc(int num) {
return Math.abs(num);
}
});

//2、使用 lambda 表达式
printAbs(-8, (num) -> {return Math.abs(num);});

//3、简化 lambda 表达式
printAbs(-9, num -> Math.abs(num));

//4、使用方法引用,简化 lambda 表达式
printAbs(-7, Math::abs);
}
}

Java面向对象之函数式编程

3.2 对象名引用方法

定义:对象名引用方法是指通过实例化对象的名称,来对其方法进行的引用。

引用示例如下:

//定义函数式接口
@FunctionalInterface
interface Utils{
String calc(String str);
}

//定义一个类,类中定义一个普通方法
class UpperUtil{
public String toUpper(String str){
return str.toUpperCase();
}
}

public class Example2 {
private static void printUpper(String ori, Utils u){
System.out.println(u.calc(ori));
}

public static void main(String[] args) {
UpperUtil upper = new UpperUtil();

//1、使用匿名内部类
printUpper("abc", new Utils() {
@Override
public String calc(String ori) {
return upper.toUpper(ori);
}
});

//2、使用 lambda 表达式
printUpper("bcd", (ori) -> {return upper.toUpper(ori);});

//3、简化 lambda 表达式
printUpper("cde", ori -> upper.toUpper(ori));

//4、使用方法引用,简化 lambda 表达式
printUpper("def", upper::toUpper);
}
}

3.3 构造器引用方法

定义:是指对类自带的构造器的引用。

引用示例如下:

//定义函数式接口
@FunctionalInterface
interface PersonBuilder{
Person buildPerson(String name);
}

//定义一个类,类中定义一个构造器及普通方法
class Person{
private String name;

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

public String getName(){
return name;
}
}

public class Example3 {
private static void printName(String name, PersonBuilder buildPerson){
System.out.println(buildPerson.buildPerson(name).getName());
}

public static void main(String[] args) {

//1、使用 lambda 表达式
printName("李四", (String name) -> {return new Person(name);});

//2、使用构造器引用的方式
printName("张三", Person::new);
}
}

3.4 类名引用普通方法

定义:是指通过一个普通类的类名,来对其普通方法进行的引用。

引用示例如下:

//定义函数式接口
@FunctionalInterface
interface Printable{
void print(StringUtils su, String str);
}

//定义一个类及方法
class StringUtils{
public void printUppercase(String str){
System.out.println(str.toUpperCase());
}
}

public class Example4 {
private static void printUpper(StringUtils su, String str, Printable printable){
printable.print(su, str);
}

public static void main(String[] args) {
//1、使用匿名内部类
printUpper(new StringUtils(), "abc", new Printable() {
@Override
public void print(StringUtils su, String str) {
su.printUppercase(str);
}
});

//2、使用 lambda 表达式
printUpper(new StringUtils(), "bcd", (Object, t) -> Object.printUppercase(t));

//3、使用方法引用的方式
printUpper(new StringUtils(), "def", StringUtils::printUppercase);
}
}