函数式接口简介
只包含一个抽象方法的接口,称为函数式接口。
我们可以通过Lambda表达式来创建该接口的对象。如果Lambda表达式抛出一个非运行时异常,那么该异常需要在目标接口的抽象方法上进行声明。
Java8中用@FunctionalInterface来检查函数式接口,当然我们也可以在自己写的接口上使用这个注解来检查接口是否是函数式接口。
在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表达式是啥?
众所周知,Java提倡的是”一切皆对象“。但是一些新的语言及技术的兴起,Java不得不做出调整以支持更加广泛的技术要求,也就是需要支持OOF(面向函数编程)。
在函数式编程语言中,Lambda表达式的类型是函数。但是在Java中,Lambda表达式的类型是对象,且必须依附于函数式接口。简单来说,Lambda表达式就是函数式接口的实例,只要一个对象是函数式接口的实例就可以用Lambda表达式来表示。所以,以前用匿名实现类表示的都可以用Lambda表达式来编写。
Lambda表达式是Java8引入的一种新的语法。这里又用到一种新的操作符,这个操作符就是”->”,称为Lambda表达式操作符或者箭头操作符。
该操作符将Lambda表达式分为两个部分:
左侧:指定了Lambda表达式需要的参数列表。
右侧:指定了Lambda表达式体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。
Lambda表达式基本的语法格式:
(参数列表) -> {Lambda表达式体}
为什么要用Lambda表达式?
Lambda表达式是一个匿名函数,是一段可以传递的代码。使用Lambda表达式能够写出更简洁、更灵活的代码。
既然Lambda表达式是一个函数式接口的实例,那么就找一个函数式接口来应用一下。
在之前用匿名内部类创建一个Runnable接口对象是这样写的:
Runnable run = new Runnable() {
@Override
public void run() {
System.out.println("###");
}
};
现在可以这样写:
Runnable run = () -> {System.out.println("lambda表达式实现");};
一行代码就实现了之前好多行代码的功能,确实很简洁。
Lambda表达式的使用语法
Runnable run = () -> {System.out.println("lambda表达式实现");};
如果执行语句只有一行,可以省略”{}“:
Runnable run = () -> System.out.println("lambda表达式实现");
Consumer<String> consumer = (String s) -> {System.out.println(s);};
参数类型可以省略,因为编译器会根据上下文环境推断出参数的类型,也叫做类型推断。这里指定了泛型为String。
而且只有一个参数时,参数列表的”()“也可以省略。所以可以这么写:
Consumer<String> consumer = s -> System.out.println(s);
Comparator<Integer> comparator1 = (o1,o2) -> {
System.out.println(o2);
return Integer.compare(o1,o2);
};
Comparator<Integer> comparator1 = (o1,o2) -> {
return Integer.compare(o1,o2);
};
如果有返回值时只有一条执行语句,那么”return“可以省略。
Comparator<Integer> comparator1 = (o1,o2) -> Integer.compare(o1,o2);
Thread thread = new Thread(() -> {
System.out.println("lambda表达式实现");
});
为了将Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该Lambda 表达式兼容的函数式接口的类型。比如上面代码中的Lambda表达式的作用就是创建了一个Runnale对象,而new Thread()的参数可以是一个Runnable对象。
方法引用
方法引用是Lambda表达式的另一种表现形式。简单来说,就是将Lambda表达式的内容封装成一个方法再进行调用或者已经有现成的方法可以直接使用。
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
要求:实现接口的抽象方法的参数列表和返回值类型必须与方法引用的方法的参数列表和返回值类型保持一致!
方法引用中引入了一个新的操作符:“::”
使用格式:类(或对象):: 方法名
具体分为三种情况:
举个简单的例子:
比如遍历一个集合,将每个对象打印出来,原来是这样写:
@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进行遍历:
而该方法的参数是一个消费型接口,有一个入参,而返回值为void。
而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;