Java8中新增了函数式接口的概念,那么和之前的接口相比,函数式接口有什么不同呢?西红柿下面来一一告诉你。
函数式接口是what嘞
函数式接口:函数式接口中有且仅有一个抽象方法,这个抽象方法的意义在于表达某种行为。
通过定义貌似看不出什么,👇我们看几个具体的案例。
案例一
接口中可以有多个默认实现,但是默认实现需要用default关键字显示标注。
@FunctionalInterface
//此注解用于显式表示一个接口是否满足函数式接口的定义,如果不满足定义会标红
public interface MyInterface {
String getName();
//这是默认实现的方法,并不违反函数接口的规则。
default int getAge(){
return 1;
}
}
在Java8之前,接口中的方法都必须是抽象,Java团队巧妙的利用default,为了解决版本的兼容问题。
案例二
接口可以包含静态方法。
@FunctionalInterface
public interface MyInterface {
String getName();
//这是静态的方法,并不违反函数接口的规则。
static int getAge() {
return 1;
}
}
案例三
接口可以包含和Object的public一样方法签名的方法,但是此方法需要是抽象的。
@FunctionalInterface
public interface MyInterface {
String getName();
//该方法的方法签名与Object的相同
//并且是抽象的,因为实现此接口的实现类,必然会提供方法的实现。
int hashCode();
}
上面三个案例就是函数式接口的声明,那么如何去使用函数式接口呢?
函数式接口是咋用嘞
函数式接口的实例有三种方式创建:Lambda表达式、方法引用和构造方法引用。
因为函数式接口只有一个抽象方法,所以Lambda表达式实际代表的行为就是接口中的抽象方法,也从侧面说明了Lambda表达式不能脱离函数式接口的语境。
我们👇就看一下 函数式接口与Lambda表达式之间的联系。
Lambda表达式的基本结构是这样的👇:
(Type1 arg1,Type2,arg2,...) -> {body}
为了简化上面结构,Java为我们定义了一些规则:
1) 如果参数可以通过类型推导出来,那么参数类型可以不写。(可以先不写试试)
2) 如果参数只有一个,可以将括号省略。
3) body如果只有一行代码,可以省略花括号。注意的是一行代码的表示有表达式和语句之分,需要配合return、分号、花括号的情况。
4) 为了便于理解和说明,这里将Lambda表达式的按着其参数和返回值类型进行描述。<br>
(int a,int b)->a+b 的类型为(int,int)->int<br>
(int a,int b)->println(a+b) 的类型为(int,int)->Unit
通过Lambda表达式就可以使用函数式接口,贴心的JDK为我们提供了一些常用的接口,下面来看看有哪些
JDK中的函数式接口
Consumer
接受一个参数,不返回结果
/**
* 这个函数式接口代表一种操作行为:无返回结果的单一参数操作
* 接口中的抽象方法,就是操作
* 仍包含一个默认方法
*/
@FunctionalInterface
public interface Consumer<T> {
/**
* 对给定的参数进行操作
*/
void accept(T t);
/**
* 返回一个组合的Consumer。对t参数执行完给定的行为后,再去执行after行为。
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
我们以集合遍历为例
public class Main {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
//forEach函数参数类型是Consumer,对集合的每个元素执行accept行为(①)
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
//此处的行为就是对元素进行打印,
System.out.println(integer);
}
});
//Lambda表达式 就是描述的接口的抽象方法
//本例中,接口的抽象方法 (接受一个参数,执行一个无返回结果的操作)
//也就是(T t)->Unit,式子中的item可以命名为任意名字
list.forEach(item -> System.out.println());
}
}
①的源码如下:
//调用的是Iterable接口的默认方法forEach
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
//遍历集合的每一个元素
for (T t : this) {
//调用接口的accept行为方法,参数是集合的元素
action.accept(t);
}
}
以上的源码就说明了 Consumer代表一种抽象的行为,集合的元素去参与这个行为,开发者需定义出具体行为。
Function
接受一个参数,返回一个结果
/**
* 接受一个t参数返回一个R类型的结果,T与R可以相同也可以不同
*/
@FunctionalInterface
public interface Function<T, R> {
/**
* t参数去执行给定的行为
*/
R apply(T t);
/**
* 返回一个符合函数,在本实例行为执行前,先去执行before的行为,将before的结果作为本实例行为的输入
* 因此,before的结果类型一定要是T类型的子类或本身。
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
//先执行before,再执行本实例的行为
return (V v) -> apply(before.apply(v));
}
/**
* 返回一个符合函数,在本实例行为执行后,紧接着去执行after的行为,将本实例的结果作为after行为的输入
* 因此,after的输入一定是本实例结果的父类型或本身
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
//先执行本实例,再执行after
return (T t) -> after.apply(apply(t));
}
/**
* 返回一个参数无操作的行为
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
以字符串取小写为例
public class FunctionTest {
public static void main(String[] args) {
FunctionTest functionTest = new FunctionTest();
//匿名函数 (①)
functionTest.operation("Hello world", new Function<String, String>() {
@Override
public String apply(String str) {
return str.toLowerCase();
}
});
//表达式
functionTest.operation("Hello world", item -> item.toLowerCase());
//Lambda表达式声明函数式接口的实例
Function<String, String> function = item -> item.toLowerCase();
functionTest.operation("Hello world",function);
//语句
functionTest.operation("Hello world", item -> {
return item.toLowerCase();
});
//以上几种方式 都是对Function的实践。定义一种行为:对字符串进行小写并返回
//输入类型和输出类型都是字符串。
//开发者自己定义操作的行为,调用的方法都是operation。
}
public String operation(String str, Function<String, String> function) {
return function.apply(str);
}
}
以①为例分析其执行流程
首先,调用operation方法,参数是"Hello World"、匿名类。
其次,调用参数function的apply方法,实际调用的是传入的匿名类的apply方法。
最后,apply方法的参数是传入的"Hello World",执行取小写操作。
综上:开发者需要的是在方法的调用处,定义出需执行的行为,传递的是行为。定义行为的方式有匿名类,Lambda表达式,方法引用等,Lambda表达式更加简洁。
BiFunction
接受两个参数,返回一个结果
/**
* 是Function思想的特殊化,接受两个参数,返回一个R类型结果
*/
@FunctionalInterface
public interface BiFunction<T, U, R> {
/**
* 对t和u参数执行给定的行为
*/
R apply(T t, U u);
/**
* 返回一个复合的函数,在执行完本实例行为后,将本实例的结果作为after行为的输入,执行after行为。
*/
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
//先执行本实例行为,在执行after行为
return (T t, U u) -> after.apply(apply(t, u));
}
}
以数字的四则运算操作为例
public class FunctionTest2 {
public static void main(String[] args) {
FunctionTest2 test = new FunctionTest2();
//Lambda表达式创建出BiFunction实例
//开发者需自己定义出 执行的行为,并传递给compute函数
//compute函数执行具体的行为
test.compute(2, 3, (a, b) -> a + b);
test.compute(2, 3, (a, b) -> a - b);
test.compute(2, 3, (a, b) -> a * b);
test.compute(2, 3, (a, b) -> a / b);
//先执行两个数字相乘,将结果执行平方(①)
test.compute1(2, 3, (a, b) -> a * b, a -> a * a);
}
public int compute(int a, int b, BiFunction<Integer, Integer, Integer> function) {
return function.apply(a, b);
}
public int compute1(int a, int b, BiFunction<Integer, Integer, Integer> biFunction, Function<Integer, Integer> function) {
return biFunction.andThen(function).apply(a, b);
}
}
①的执行流程如下:
首先,将Lambda表达式构建的接口实例传入compute1方法。
然后,调用BiFunction(数字的乘积)的andTen方法,参数是 function(数字的平方)
之后,调用BiFunction(数字的乘积)的apply方法,计算出实际的结果
接着,调用function(数字的平方)的apply方法,参数是上一步的结果
最后,返回结果
Predicate
判断参数是否满足 断言
/**
* 判断参数是否满足 断言(也就是布尔值)
*/
@FunctionalInterface
public interface Predicate<T> {
/**
* 对给定的参数t执行测试行为
*/
boolean test(T t);
/**
* 与操作
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/**
*非操作
*/
default Predicate<T> negate() {
return (t) -> !test(t);
}
/**
* 或操作
*/
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
/**
*测试两个实例是否相同(equals相等)
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
以字符串判断equals为例
public class PredicateTest {
public static void main(String[] args) {
PredicateTest predicateTest = new PredicateTest();
//第一处
predicateTest.predicateString("Hello", item -> item.equals("test"));
//第二处
Predicate<String> predicate = Predicate.isEqual("test");
predicate.test("Hello");
//以上两个例子并无不同,Predicate的静态isEqual方法只是帮我们写了断言条件
//第一处的流程分析
//①调用predicateString方法,参数是“Hello”和Lambda表达式生成的接口实例
//②调用接口实例的test方法,就是item -> item.equals("test")
// item就是“Hello”
//③返回断言结果false
//第二处的流程分析
//①调用静态方法isEqual("test"),生成接口实例
//②调用test方法,参数是“Hello”
//③test的实现是isEqual,返回结果false
}
public boolean predicateString(String str, Predicate<String> predicate) {
return predicate.test(str);
}
}
Supplier
返回一个结果即可,无参数
/**
*返回一个结果
*/
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*/
T get();
}
总结以上接口
| 函数式接口 | 作用 |
|---|---|
| Consumer | 接受一个T类型的参数,不返回结果 |
| Function<T,R> | 接受一个T的参数,返回一个R类型的结果 |
| BiFunction<T, U, R> | 接受T和R类型的两个参数,返回一个R结果 |
| Predicate | 接受一个T的参数,返回一个Boolean值 |
| Supplier | 不接受参数,返回一个T类型的结果 |
以上就是西红柿总结的函数式接口的简略内容。