函数式接口也是 java interface 的一种,但还需要满足:
满足这些条件的 interface,就可以被视为函数式接口。例如,java8中的Comparator接口:
@FunctionalInterface
public interface Comparator<T> {
/** * 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<T> reversed() {
return Collections.reverseOrder(this);
}
......
/** * 静态方法 * @since 1.8 */
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}
......
一句话,函数式接口带给我们最大的好处就是:可以使用极简的lambda表达式实例化接口。为什么这么说呢?我们或多或少使用过一些只有一个抽象方法的接口,比如 Runnable, ActionListener, Comparator …。比如,我们要用Comparator实现排序算法,我们的处理方式通常无外乎两种:
class Test {
public static void main(String args[]) {
List<Person> persons = new ArrayList<Person>();
Collections.sort(persons, new Comparator<Person>(){
@Override
public int compare(Person o1, Person o2) {
return Integer.compare(o1.getAge(), o2.getAge());
}
});
}
}
匿名内部类实现的代码量没有多到哪里去,结构也还算清晰。不过如今脚本语言横行无阻,无不以其简洁的语法为杀手锏,俘获程序员的欢心。jdk开发者们应该是意识到了这一点,我们从上面Comparator的实现就可以得到验证。该接口在jdk8的实现增加了FunctionalInterface注解,代表Comparator是一个函数式接口,使用者可放心的通过lambda表达式来实例化。那我们来看看使用lambda实例化所需编写的代码:
Comparator<Person> comparator = (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge());
就这么简单,->
前面的()
是Comparator接口中compare方法的参数列表,->
后面则是run方法的方法体。
我们知道,一个接口只要满足只有一个抽象方法的条件,即可以当成函数式接口使用,有没有@FunctionalInterface都无所谓。但是jdk定义了这个注解肯定是有原因的,对于开发者,该注解的使用一定要三思而后续行。
如果使用了此注解,再往接口中新增抽象方法,编译器就会报错,编译不通过。换句话说,@FunctionalInterface就是一个承诺,承诺该接口世世代代都只会存在这一个抽象方法。因此,凡是使用了这个注解的接口,开发者可放心大胆的使用lambda来实例化。当然误用@FunctionalInterface带来的后果也是极其惨重的:如果哪天你把这个注解去掉,再加一个抽象方法,则所有使用lambda实例化该接口的客户端代码将全部编译错误。。。
特别地,当某接口只有一个抽象方法,但没有增加@FunctionalInterface,则代表人家没有承诺该接口未来不增加抽象方法,所以建议不要用lambda来实例化,还是老老实实的用以前的方式比较稳妥。
lambda表达式是实现函数式接口的一个快捷方式。下面这段代码是最直观的体现:
Runnable task = () -> System.out.println("i am coming...");
=
号的前面是变量的声明,后面是一个lambda表达式。我们直接将lambda表达式赋值为Runnable类型的task变量,就意味着:lambda表达式是实现函数式接口的一个快捷方式。理解这一点是至关重要的。
lambda表达式语法可抽象表示为:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
...
return statmentN;
}
以Predicate判断是否为成年人为例:
Predicate<Person> predicate = (Person person) -> {
Integer age = person.getAge();
return age >= 18;
};
上面的lambda表达式语法抽象表示是比较官方的,和我们平时所写的lambda表达式比起来要繁琐一些。别着急,下面会一点一点地演示lambda表达式的简化过程。
绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型。这样,lambda表达式就变成了:
(param1, param2, ..., paramN) -> {
statment1;
statment2;
...
return statmentN;
}
上面的例子也可简化为:
Predicate<Person> predicate = (person) -> {
Integer age = person.getAge();
return age >= 18;
};
lambda表达式简写为:
param -> {
statment1;
statment2;
...
return statmentN;
}
对应的例子简化为:
Predicate<Person> predicate = person -> {
Integer age = person.getAge();
return age >= 18;
};
其他省就省了,为啥return关键字也可以被省略呢?原因是,编译器会认为:既然只有一个语句,那么这个语句执行的结果就应该是返回值,所以return也就不需要了。因此,lambda表达式简化为:
param -> statment;
所以例子又可简化成:
Predicate<Person> predicate = person -> person.getAge() >= 18
现在这个样子,才是我们熟悉的模样。。。
我们前面所有的介绍,感觉上lambda表达式像一个闭关锁国的家伙:可以访问给它传递的参数,也能自己内部定义变量,但是却从来没看到其访问它外部的变量。是不是lambda表达式不能访问其外部变量?我们可以这样想:lambda表达式其实是快速创建函数式接口实例的语法糖,匿名内部类所实现的实例都可以访问接口外部变量,那么lambda表达式肯定也是可以。事实上,不但可以,在java8中还做了一个小小的升级。看下面例子:
String[] array = {"a", "b", "c"};
for (Integer i : Lists.newArrayList(1, 2, 3)) {
System.out.println(i);
Stream.of(array).map(item -> Strings.padEnd(item, i, '*')).forEach(s -> System.out.println(s));
}/*** output a b c a* b* c* a** b** c** */
上面的这个例子中,map中的lambda表达式访问外部变量Integer i,并且外部变量 i 还可以不用显式声明为final。事实上,lambda表达式同匿名内部类一样,访问外部变量有一个非常重要的限制:变量不可变(只是引用不可变,而不是真正的不可变)。看下面的例子:
String[] array = {"a", "b", "c"};
for (int i = 1; i < 4; i++) {
System.out.println(i);
Stream.of(array).map(item -> Strings.padEnd(item, i, '*')).forEach(System.out::println);
}/*** output Error:(124, 63) java: 从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量 */
上面的代码会报编译错误,因为变量i被lambda表达式引用,所以编译器会把其当成final来处理,但如代码所示,变量i是会自增变化的(ps:大家可以想象问什么上一个例子不报错,而这个报错)。细心的读者肯定会发现:以前java的匿名内部类在访问外部变量的时候,外部变量必须用final修饰。Bingo,java8对这个限制做了优化,外部变量可以不用显式使用final修饰,但编译器会自动把它当成final来处理。
lambda表达式与匿名类的异同集中体现在三点上:
Java8 引入了函数式接口,并在java.util.function 包内预定义了常用函数式接口,下表罗列了一些常用的函数式接口:
Function接口接受一个输入参数T,返回一个结果R,即:R = F u n c t i o n ( T ) R = Function(T)R=Function(T)。接口定义为:
/** * Represents a function that accepts one argument and produces a result. * @param <T> the type of the input to the function * @param <R> the type of the result of the function * @since 1.8 */
@FunctionalInterface
public interface Function<T, R> {
/** * Applies this function to the given argument. */
R apply(T t);
/** * Returns a composed function that first applies the {@code before} * function to its input, and then applies this function to the result. * If evaluation of either function throws an exception, it is relayed to * the caller of the composed function. */
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/** * Returns a composed function that first applies this function to * its input, and then applies the {@code after} function to the result. * If evaluation of either function throws an exception, it is relayed to * the caller of the composed function. */
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/** * Returns a function that always returns its input argument. */
static <T> Function<T, T> identity() {
return t -> t;
}
}
Function接口默认实现了3个default方法,分别是compose、andThen和identity,对应的函数表达为:
看个例子:
public static void main(String[] args) {
Function<Integer, Integer> func1 = x -> {
return x + 1;
};
Function<Integer, Integer> func2 = y -> {
return y * 7;
};
int x = 3, y = 5;
System.out.println(func1.apply(x));
System.out.println(func2.apply(y));
System.out.println(func1.compose(func2).apply(x));
System.out.println(func1.andThen(func2).apply(x));
}/*** output 4 35 22 28 */
Function接口相关的接口包括:
Predicate接口接受一个输入参数,返回一个布尔值结果。其定义为:
/** * Represents a predicate (boolean-valued function) of one argument. * @param <T> the type of the input to the predicate * @since 1.8 */
@FunctionalInterface
public interface Predicate<T> {
/** * Evaluates this predicate on the given argument. */
boolean test(T t);
/** * Returns a composed predicate that represents a short-circuiting logical * AND of this predicate and another. When evaluating the composed * predicate, if this predicate is {@code false}, then the {@code other} * predicate is not evaluated. */
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/** * Returns a predicate that represents the logical negation of this * predicate. */
default Predicate<T> negate() {
return (t) -> !test(t);
}
/** * Returns a composed predicate that represents a short-circuiting logical * OR of this predicate and another. When evaluating the composed * predicate, if this predicate is {@code true}, then the {@code other} * predicate is not evaluated. */
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
/** * Returns a predicate that tests if two arguments are equal according * to {@link Objects#equals(Object, Object)}. */
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
其默认方法也封装了and、or和negate逻辑,看个例子:
public static void main(String[] args) {
Predicate<Person> predicate1 = (Person person) -> {
Integer age = person.getAge();
return age >= 18;
};
Predicate<Person> predicate2 = (Person person) -> {
String name = person.getName();
return name.startsWith("R");
};
Person rico = new Person();
rico.setName("Rico");
rico.setAge(16);
System.out.println("Rico名字以R开头且年满18岁 : " + predicate1.and(predicate2).test(rico));
System.out.println("Rico名字以R开头或年满18岁 : " + predicate1.or(predicate2).test(rico));
System.out.println("Rico名字不是以R开头 : " + predicate1.negate().test(rico));
}/*** output Rico名字以R开头且年满18岁 : false Rico名字以R开头或年满18岁 : true Rico名字不是以R开头 : true */
其他Predicate接口:
Consumer接口接受一个输入参数,没有返回值。其定义为:
/** * Represents an operation that accepts a single input argument and returns no * result. Unlike most other functional interfaces, {@code Consumer} is expected * to operate via side-effects. * * @param <T> the type of the input to the operation * * @since 1.8 */
@FunctionalInterface
public interface Consumer<T> {
/** * Performs this operation on the given argument. */
void accept(T t);
/** * Returns a composed {@code Consumer} that performs, in sequence, this * operation followed by the {@code after} operation. If performing either * operation throws an exception, it is relayed to the caller of the * composed operation. If performing this operation throws an exception, * the {@code after} operation will not be performed. */
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
看个例子:
public static void main(String[] args) {
Person rico = new Person();
rico.setName("Rico");
rico.setAge(16);
Consumer<Person> consumer = person -> System.out.println(person);
consumer.accept(rico);
}/*** output {"age":16,"name":"Rico"} */
其他Consumer接口:
Supplier接口不需要任何参数,返回一个值。其定义为:
/** * Represents a supplier of results. * * @param <T> the type of results supplied by this supplier * * @since 1.8 */
@FunctionalInterface
public interface Supplier<T> {
/** * Gets a result. */
T get();
}
看个例子:
public static void main(String[] args) {
Person rico = new Person();
rico.setName("Rico");
rico.setAge(16);
Supplier<Person> supplier = () -> rico;
System.out.println("supplier : " + supplier.get());
}/*** output supplier : {"age":16,"name":"Rico"} */
其他Supplier接口:
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/justloveyou_/article/details/89066782
内容来源于网络,如有侵权,请联系作者删除!