int increase(int a, int b) {
return a + b;
}
不同的编程范式之间并不是截然不同的,总是有一些相同的编程规则。比如不管是面向过程、面向对象还是函数式编程,它们都有变量、函数的概念,最顶层都要有main函数执行入口,来组装编程单元(类、函数等)。只不过,面向对象的编程单元是类或对象,面向过程的编程单元是函数,函数式编程的编程单元是无状态函数。
Java对函数式编程的支持
实现面向对象编程不一定非得使用面向对象编程语言,同理,实现函数式编程也不一定非得使用函数式编程语言。现在,很多面向对象编程语言,也提供了相应的语法、类库来支持函数式编程。
Java这种面向对象编程语言,对函数式编程的支持可以通过一个例子来描述:
public class Demo {
public static void main(String[] args) {
Optional result = Stream.of(“a”, “be”, “hello”)
.map(s -> s.length())
.filter(l -> l <= 3)
.max((o1, o2) -> o1-o2);
System.out.println(result.get()); // 输出2
}
}
这段代码的作用是从一组字符串数组中,过滤出长度小于等于3的字符串,并且求得这其中的最大长度。
Java为函数式编程引入了三个新的语法概念:Stream类、Lambda表达式和函数接口(Functional Inteface)。Stream类用来支持通过“.”级联多个函数操作的代码编写方式;引入Lambda表达式的作用是简化代码编写;函数接口的作用是让我们可以把函数包裹成函数接口,来实现把函数当做参数一样来使用(Java 不像C那样支持函数指针,可以把函数直接当参数来使用)。
Stream类
假设我们要计算这样一个表达式:(3-1)*2+5。如果按照普通的函数调用的方式写出来,就是下面这个样子:
add(multiply(subtract(3,1),2),5);
不过,这样编写代码看起来会比较难理解,我们换个更易读的写法,如下所示:
subtract(3,1).multiply(2).add(5);
在Java中,“.”表示调用某个对象的方法。为了支持上面这种级联调用方式,我们让每个函数都返回一个通用的Stream类对象。在Stream类上的操作有两种:中间操作和终止操作。中间操作返回的仍然是Stream类对象,而终止操作返回的是确定的值结果。
再来看之前的例子,对代码做了注释解释。其中map、filter是中间操作,返回Stream类对象,可以继续级联其他操作;max是终止操作,返回的不是Stream类对象,无法再继续往下级联处理了。
public class Demo {
public static void main(String[] args) {
Optional result = Stream.of(“f”, “ba”, “hello”) // of返回Stream对象
.map(s -> s.length()) // map返回Stream对象
.filter(l -> l <= 3) // filter返回Stream对象
.max((o1, o2) -> o1-o2); // max终止操作:返回Optional
System.out.println(result.get()); // 输出2
}
}
Lambda表达式
前面提到Java引入Lambda表达式的主要作用是简化代码编写。实际上,我们也可以不用Lambda表达式来书写例子中的代码。我们拿其中的map函数来举例说明。
下面三段代码,第一段代码展示了map函数的定义,实际上,map函数接收的参数是一个Function接口,也就是函数接口。第二段代码展示了map函数的使用方式。第三段代码是针对第二段代码用Lambda表达式简化之后的写法。实际上,Lambda表达式在Java中只是一个语法糖而已,底层是基于函数接口来实现的,也就是第二段代码展示的写法。
// Stream类中map函数的定义:
public interface Stream extends BaseStream<T, Stream> {
Stream map(Function<? super T, ? extends R> mapper);
//…省略其他函数…
}
// Stream类中map的使用方法示例:
Stream.of(“fo”, “bar”, “hello”).map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return s.length();
}
});
// 用Lambda表达式简化后的写法:
Stream.of(“fo”, “bar”, “hello”).map(s -> s.length());
Lambda表达式包括三部分:输入、函数体、输出。表示出来的话就是下面这个样子:
(a, b) -> { 语句1;语句2;…; return 输出; } //a,b是输入参数
实际上,Lambda表达式的写法非常灵活。上面给出的是标准写法,还有很多简化写法。比如,如果输入参数只有一个,可以省略 (),直接写成 a->{…};如果没有入参,可以直接将输入和箭头都省略掉,只保留函数体;如果函数体只有一个语句,那可以将{}省略掉;如果函数没有返回值,return语句就可以不用写了。
Optional result = Stream.of(“f”, “ba”, “hello”)
.map(s -> s.length())
.filter(l -> l <= 3)
.max((o1, o2) -> o1-o2);
// 还原为函数接口的实现方式
Optional result2 = Stream.of(“fo”, “bar”, “hello”)
.map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return s.length();
}
})
.filter(new Predicate() {
@Override
public boolean test(Integer l) {
return l <= 3;
}
})
.max(new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
Lambda表达式与匿名类的异同集中体现在以下三点上:
-
Lambda就是为了优化匿名内部类而生,Lambda要比匿名类简洁的多得多。
-
Lambda仅适用于函数式接口,匿名类不受限。
-
即匿名类中的this是“匿名类对象”本身;Lambda表达式中的this是指“调用Lambda表达式的对象”。
函数接口
实际上,上面一段代码中的Function、Predicate、Comparator都是函数接口。我们知道,C语言支持函数指针,它可以把函数直接当变量来使用。
但是,Java没有函数指针这样的语法。所以它通过函数接口,将函数包裹在接口中,当作变量来使用。实际上,函数接口就是接口。不过,它也有自己特别的地方,那就是要求只包含一个未实现的方法。因为只有这样,Lambda表达式才能明确知道匹配的是哪个方法。如果有两个未实现的方法,并且接口入参、返回值都一样,那Java在翻译Lambda表达式的时候,就不知道表达式对应哪个方法了。
函数式接口也是Java interface的一种,但还需要满足:
-
一个函数式接口只有一个抽象方法(single abstract method);
-
Object类中的public abstract method不会被视为单一的抽象方法;
-
函数式接口可以有默认方法和静态方法;
-
函数式接口可以用@FunctionalInterface注解进行修饰。
满足这些条件的interface,就可以被视为函数式接口。例如Java 8中的Comparator接口:
@FunctionalInterface
public interface Comparator {
/**
* single abstract method
* @since 1.8
*/
int compare(T o1, T o2);
/**
* Object类中的public abstract method
* @since 1.8
*/
boolean equals(Object obj);
/**
* 默认方法
* @since 1.8
*/
default Comparator reversed() {
return Collections.reverseOrder(this);
}
/**
* 静态方法
* @since 1.8
*/
public static <T extends Comparable<? super T>> Comparator reverseOrder() {
return Collections.reverseOrder();
}
//省略…
}
函数式接口有什么用呢?一句话,函数式接口带给我们最大的好处就是:可以使用极简的lambda表达式实例化接口。为什么这么说呢?我们或多或少使用过一些只有一个抽象方法的接口,比如Runnable、ActionListener、Comparator等等,比如我们要用Comparator实现排序算法,我们的处理方式通常无外乎两种:
-
规规矩矩的写一个实现了Comparator接口的Java类去封装排序逻辑。若业务需要多种排序方式,那就得写多个类提供多种实现,而这些实现往往只需使用一次。
-
另外一种聪明一些的做法无外乎就是在需要的地方搞个匿名内部类,比如:
public class Test {
public static void main(String args[]) {
List persons = new ArrayList();
Collections.sort(persons, new Comparator(){
@Override
public int compare(Person o1, Person o2) {
return Integer.compareTo(o1.getAge(), o2.getAge());
}
});
}
}
匿名内部类实现的代码量没有多到哪里去,结构也还算清晰。Comparator接口在Jdk 1.8的实现增加了FunctionalInterface注解,代表Comparator是一个函数式接口,使用者可放心的通过lambda表达式来实例化。那我们来看看使用lambda表达式来快速new一个自定义比较器所需要编写的代码:
Comparator comparator = (p1, p2) -> Integer.compareTo(p1.getAge(), p2.getAge());
-> 前面的 () 是Comparator接口中compare方法的参数列表,-> 后面则是compare方法的方法体。
下面将Java提供的Function、Predicate这两个函数接口的源码,摘抄如下:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t); // 只有这一个未实现的方法
default Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
文末
我将这三次阿里面试的题目全部分专题整理出来,并附带上详细的答案解析,生成了一份PDF文档
- 第一个要分享给大家的就是算法和数据结构
- 第二个就是数据库的高频知识点与性能优化
- 第三个则是并发编程(72个知识点学习)
- 最后一个是各大JAVA架构专题的面试点+解析+我的一些学习的书籍资料
还有更多的Redis、MySQL、JVM、Kafka、微服务、Spring全家桶等学习笔记这里就不一一列举出来
) -> apply(before.apply(v));
}
文末
我将这三次阿里面试的题目全部分专题整理出来,并附带上详细的答案解析,生成了一份PDF文档
- 第一个要分享给大家的就是算法和数据结构
[外链图片转存中…(img-ox3faBgo-1721139778451)]
- 第二个就是数据库的高频知识点与性能优化
[外链图片转存中…(img-sY1ZDtCD-1721139778451)]
- 第三个则是并发编程(72个知识点学习)
[外链图片转存中…(img-mj2gQuJd-1721139778452)]
- 最后一个是各大JAVA架构专题的面试点+解析+我的一些学习的书籍资料
[外链图片转存中…(img-zLwUFZsc-1721139778453)]
还有更多的Redis、MySQL、JVM、Kafka、微服务、Spring全家桶等学习笔记这里就不一一列举出来