## 概念
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式变成场景的接口。而Java中函数式变成体现就是`Lambda`,所以函数式接口就是适用于`Lambda`使用的接口。只有确保接口中有且仅有一个抽象方法,`Java`中的Lambda才能被顺利的进行推导。
> `语法糖`是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的forEach语法,其实底层实现原理仍然是迭代器。从应用层面来讲,`Java`中的`Lambda`也可以被当作是匿名内部类的`语法糖`,但是二者在原理上是不相同的。
## 格式
只要确保接口中有且仅有一个抽象方法即可:
```java
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数);
// 其他非抽象方法内容
}
```
Java中接口的特性:
- 接口是隐式抽象的,当声明一个接口的时候,不必使用**abstract**关键字。
- 接口中每一个方法也是隐式抽象的,声明时同样不需要**abstract**关键字。
- 接口中的方法都是公有的。
所以我们可以省略`public abstract`的修饰,所以定义一个函数式接口就变得很简单:
```java
public interface MyFunctionalInterface {
void hello();
}
```
## `@FunctionalInterface`注解
`Java8`中专门为函数式接口引入了一个新的注解:`@FunctionalInterface`。该注解可用于一个接口的定义上
```java
@FunctionalInterface
public interface MyFunctionalInterface {
abstract void hello();
}
```
添加这个注解之后,如果接口中存在多个方法或者没有方法,就会报错,它的作用就是检测接口是否是一个函数式接口。(接口方法都是隐式抽象所以为了简化直接称为方法,而不再加抽象修饰)
### 函数式接口的使用
```java
public class Demo1 {
// 定义一个方法,参数使用函数式接口
public static void show (MyFunctionalInterface myInterface) {
myInterface.hello();
}
public static void main(String[] args) {
// 调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
show(new MyFunctionalInterface() {
// 使用匿名内部类重写方法
@Override
public void hello() {
System.out.println("我是函数式接口中的方法hello...");
}
});
// 调用show方法,方法的参数一个函数时接口,所以可以使用Lambda表达式
show(()->{
System.out.println("使用Lambda表达式重写接口中的抽象方法。。。。");
});
// 简化Lambda表达式
show(()-> System.out.println("这是简化后的Lambda表达式写法。。"));
}
}
```
## 函数式编程
### Lambda的延迟执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而`Lambda`表达式是延迟执行的,这正好可以作为解决方案,提升性能。
#### 性能浪费的日志案例
注:日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。
一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:
```java
public class Demo01Logger {
// 定义一个根据日志级别显示日志信息的方法
public static void showLog(int level, String message) {
// 对日志的等级进行判断,如果级别是1那么输出日志信息
if (level == 1) {
System.out.println(message);
}
}
public static void main(String[] args) {
// 定义三个日志信息
String msg1 = "hello";
String msg2 = "world";
String msg3 = "Java";
//调用showLog方法,传递日志级别和日志信息
showLog(1, msg1 + msg2 + msg3);
showLog(2, msg1 + msg2 + msg3);
showLog(3, msg1 + msg2 + msg3);
// 以上代码存在性能浪费问题,我们是拼接字符串再调用showLog
// 如果level不是1,那么就不会输出拼接后的字符串,白拼接了字符串
}
}
```
#### 体验Lambda的延迟
```java
@FunctionalInterface
public interface MessageBuilder {
// 定义一个拼接消息的抽象方法,返回拼接消息
String joinMessage();
}
```
```java
/**
* 使用Lambda优化日志案例
* Lambda的特点1:延迟加载
* Lambda的使用前提,必须存在函数式接口
*/
public class Demo2Lambda {
// 定义一个写日志的方法
public static void showLog(int level, MessageBuilder messageBuilder) {
// 对日志的等级进行判断,如果是1,调用joinMessage方法
if (level == 1) {
System.out.println(messageBuilder.joinMessage());
}
}
public static void main(String[] args) {
// 定义三个日志信息
String msg1 = "hello";
String msg2 = "world";
String msg3 = "Java";
//调用showLog方法,参数MessageBuilder是函数式接口
showLog(1, ()->{
//返回拼接的字符串
return msg1 + msg2 + msg1;
});
// 使用Lambda表达式作为参数传递,仅仅是把参数传到showLog方法中
// 只有满足条件,才会调用joinMessage方法,而只有调用时才会拼接字符串
// 所以拼接字符串的代码得到了延迟加载的功能,不会造成性能浪费
}
}
```
### 使用Lambda作为参数和返回值
如果抛开实现原理不说,`Java`中的`Lambda`表达式可以被当作是匿名
内部类的替代品。如果方法的参数是一个函数式接口类型,那么就可
以使用`Lambda`表达式进行替代。使用`Lambda`表达式作为方法参数,
其实就是使用函数式接口作为方法参数。
例如`java.lang.Runnable`接口就是一个函数式接口,假设有一个
`startThread`方法使用该接口作为参数,那么就可以使用`Lambda`
进行传参。这种情况其实和`Thread`类的构造方法参数为`Runnable`没
有本质区别。
## 常用函数式接口
### Supplier接口
`java.util.function.Supplier<T>`接口仅包含一个无参的方法:T `get()`。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的`Lambda`表达式需要“**对外提供**“一个符合泛型类型的对象数据。
```java
/**
* 常用函数式接口
* java.util.function.Supplier<T>接口仅包含一个无参方法
* T get()。用于获取一个泛型参数指定类型的对象数据
* Supplier<T>接口被称之为生产型接口
*/
public class SupplierDemo {
// 定义一个方法,方法参数传递Supplier<T>接口
public static String getString(Supplier<String> supplier) {
return supplier.get();
}
public static void main(String[] args) {
// 调用getString
String result = getString(()->{
return "Hello World";
});
System.out.println(result);
}
}
```
### Consumer接口
j`ava.util.function.consumer<T>`接口则正好与`Supplier`接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
#### accept
```java
/**
* Consumer是一个消费型接口泛型是什么类型
* 就可以使用accept方法消费什么类型的数据
*/
public class ConsumerDemo {
public static void hello(String name, Consumer<String> consumer) {
consumer.accept(name);
}
public static void main(String[] args) {
// 调用hello方法,传递需要消费的字符串和Consumer函数式接口
hello("张三", (String name)->{
// 消费name
System.out.println("Hello "+name);
});
}
}
```
#### 默认方法:andThen
如果一个方法的参数和返回值全都是`consumer`类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是`consumer` 接口中的`default`方法andThen。下面是`JDK`的源代码:
```java
default Consumer<T> andThen(Consumer<? super T> after){
Objects. requireNonNu11(after);
return(T t)->{
accept(t);
after.accept(t);
};
}
```
> 备注:`java.util.objects`的`requireNonNull`静态方法将会在参数为`null`时主动抛出`NullPointerException`异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
要想实现组合,需要两个或多个`Lambda`表达式即可,而`andThen`的语义正是“一步接一步”操作。例如两个步骤组合的情况:
```java
/**
* Consumer接口的默认方法andThen
* 作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起再进行消费
*/
public class AndThenDemo {
public static void hello(String name, Consumer<String> consumer1, Consumer<String> consumer2) {
// consumer1.accept(name);
// consumer2.accept(name);
// 等同上面使用andThen方法连接两个Consumer再进行消费
consumer1.andThen(consumer2).accept(name);
}
public static void main(String[] args) {
hello("张三",(name)->{
System.out.println(name+"消费了一只鸡");
},(name)->{
System.out.println(name+"消费100元");
});
}
}
```
### Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个`boolean`值结果。这时可以使用`java.util.function.predicate<T>`接口。
#### 抽象方法:test
`Predicate` 接口中包含一个抽象方法:`boolean test(T t)`。用于条件判断的场景:
```java
/**
* Predicate接口中包含一个抽象方法:
* boolean test(T t):用来对指定数据进行判断的方法,符合返回true
*/
public class PredicateDemo {
/**
* 定义一个方法,参数传递一个String类型的字符串
* 传递一个Predicate接口,使用Predicate的test
* 方法对字符串进行判断,返回判断结果
*/
public static boolean checkString(String s, Predicate<String> predicate) {
return predicate.test(s);
}
public static void main(String[] args) {
String s= "abcde";
boolean flag = checkString(s, (String str)->{
//对参数传递的字符串进行判断
return str.length() > 5;
});
System.out.println(false);
}
}
```
#### 默认方法:and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个`Predicate`条件使用“与”逻辑连接起来实现“**并且**”的效果时,可以使用`default`方法and。其`JDK`源码为:
```java
default Predicate<T> and(Predicate<? super T> other) {
objects.requireNonNull(other);
return(t)->test(t) && other. test(t);
}
```
如果要判断一个字符围既要包含大写`H`,又要包含大写`W`,那么:
```java
/**
* 逻辑表达式&&: 可以连接多个判断的条件
*/
public class PredicateAndDemo {
public static void hello(Predicate<String> predicate1, Predicate<String> predicate2){
boolean isValid = predicate1.and(predicate2).test("Hello World");
System.out.println("字符串是否符合要求:"+isValid);
}
public static void main(String[] args) {
hello(s->s.contains("H"), s->s.contains("W"));
}
}
```
如果希望实现逻辑字符串包含大写`H`**或者**包含大写`W`,那么代码只需要将`and`修改为`or`名称即可,其他都不
```java
public class PredicateOrDemo {
public static void hello(Predicate<String> predicate1, Predicate<String> predicate2){
// 使用or
boolean isValid = predicate1.or(predicate2).test("Hello World");
System.out.println("字符串是否符合要求:"+isValid);
}
public static void main(String[] args) {
hello(s->s.contains("H"), s->s.contains("W"));
}
}
```
#### 默认方法:negate
表示逻辑非(取反),默认方法`negate`的`JDK`源代码为:
```java
default Predicate<T> negate(){
return (t) -> !test(t);
}
```
从实现中很容易看出,它是执行了`test`方法之后,对结果`boolean`值进行"”取反而已。一定要在`test `方法调用之前调用`negate`方法,正如`and`和`or`方法一样:
```java
public class PredicateNegateDemo {
public static void hello(Predicate<String> predicate) {
// 使用negate
boolean isValid = predicate.negate().test("Hello World");
System.out.println("字符串是否符合要求:"+isValid);
}
public static void main(String[] args) {
hello(s->s.contains("H"));
}
}
```
### Function接口
`java.util.function.Function<T,R>`接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
#### 抽象方法:apply
`Function `接口中最主要的抽象方法为:`R apply(T t)`,根据类型`T`的参数获取类型R的结果。
使用的场景例如:将`String` 类型转换为`Integer` 类型。
```java
public class FunctionDemo {
// String转Integer
public static void convertType(String s, Function<String, Integer> fun) {
Integer number = fun.apply(s);
System.out.println(number);
}
public static void main(String[] args) {
String s = "12313";
convertType(s, (str)->{
// String转Integer
return Integer.parseInt(str);
});
}
}
```
#### 默认方法:andThen
`Function` 接口中有一个默认的`andThen`方法,用来进行组合操作。`JDK`源代码如:
```java
default <V> Function<T,V> andThen(Function<? super R,? extends V) after){
objects.requireNonNu11(after);
return(T t)->after.apply(apply(t));
}
```
该方法同样用于“先做什么,再做什么”的场景,和`consumer`中的`andThen`差不多:
```java
public class FunctionAndThenDemo {
// 将String转为Integer在转为Long型
public static void convertType(String s, Function<String,Integer> fun1, Function<Integer, Long> fun2) {
Long number = fun1.andThen(fun2).apply(s);
System.out.println(number.getClass()+",值:" + number);
}
public static void main(String[] args) {
String s = "12313";
convertType(s, str->{
return Integer.parseInt(str);
}, num->{
return num.longValue();
});
}
}
```

函数式接口与Lambda表达式