Java新特性:Java8函数式接口与Lambda表达式(一)
摘要
- 何为函数式接口?
- 什么是lambda表达式,lambda表达式的本质;
- 函数式接口与lambda表达式的联系:lambda是实现函数式接口的一个快捷方式,可以作为函数式接口的一个实例;
- 常用Java8内置的函数式接口 Function、Predicate、Consumer 和 Supplier 介绍;
一、函数式接口
何为函数式接口?
函数式接口也是 java interface 的一种,但还需要满足:
- 一个函数式接口只有一个抽象方法(SAM,single abstract method);
- Object 类中的 public abstract method 不会被视为单一的抽象方法;
- 函数式接口可以有默认方法和静态方法;
- 函数式接口可以用@FunctionalInterface 注解进行修饰。
满足这些条件的 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实现排序算法,我们的处理方式通常无外乎两种:
1、规规矩矩的写一个实现Comparator接口的java类去封装排序逻辑。若业务需要多种排序方式,那就得写多个类提供多种实现,而这些实现往往只需使用一次。。。
2、另外一种聪明些的做法无外乎就是在需要的地方搞个匿名内部类去扛了。比如:
class Test { public static void main(String args[]) { List<Person> persons = new ArrayList<Person>();Collections.sort(persons, new Comparator<Person>(){@Overridepublic 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 使用场景
我们知道,一个接口只要满足只有一个抽象方法的条件,即可以当成函数式接口使用,有没有@FunctionalInterface都无所谓。但是jdk定义了这个注解肯定是有原因的,对于开发者,该注解的使用一定要三思而后续行。
如果使用了此注解,再往接口中新增抽象方法,编译器就会报错,编译不通过。换句话说,@FunctionalInterface就是一个承诺,承诺该接口世世代代都只会存在这一个抽象方法。因此,凡是使用了这个注解的接口,开发者可放心大胆的使用lambda来实例化。当然误用@FunctionalInterface带来的后果也是极其惨重的:如果哪天你把这个注解去掉,再加一个抽象方法,则所有使用lambda实例化该接口的客户端代码将全部编译错误。。。
特别地,当某接口只有一个抽象方法,但没有增加@FunctionalInterface,则代表人家没有承诺该接口未来不增加抽象方法,所以建议不要用lambda来实例化,还是老老实实的用以前的方式比较稳妥。
二、lambda表达式
什么是lambda?
lambda表达式是实现函数式接口的一个快捷方式。下面这段代码是最直观的体现:
Runnable task = () -> System.out.println("i am coming...");
=号的前面是变量的声明,后面是一个lambda表达式。我们直接将lambda表达式赋值为Runnable类型的task变量,就意味着:lambda表达式是实现函数式接口的一个快捷方式。理解这一点是至关重要的。
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表达式的简化过程。
1、参数类型省略
绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型。这样,lambda表达式就变成了:
(param1, param2, ..., paramN) -> {statment1;statment2;...return statmentN;
}
上面的例子也可简化为:
Predicate<Person> predicate = (person) -> {Integer age = person.getAge();return age >= 18;
};
2、当lambda表达式的参数个数只有一个,可以省略小括号。
lambda表达式简写为:
param -> {statment1;statment2;...return statmentN;
}
对应的例子简化为:
Predicate<Person> predicate = person -> {Integer age = person.getAge();return age >= 18;
};
3、当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号。
其他省就省了,为啥return关键字也可以被省略呢?原因是,编译器会认为:既然只有一个语句,那么这个语句执行的结果就应该是返回值,所以return也就不需要了。因此,lambda表达式简化为:
param -> statment;
所以例子又可简化成:
Predicate<Person> predicate = person -> person.getAge() >= 18
现在这个样子,才是我们熟悉的模样。。。
Lambda表达式眼中的外部世界
我们前面所有的介绍,感觉上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);
}
Error:(124, 63) java: 从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量
上面的代码会报编译错误,因为变量i被lambda表达式引用,所以编译器会把其当成final来处理,但如代码所示,变量i是会自增变化的(ps:大家可以想象问什么上一个例子不报错,而这个报错)。细心的读者肯定会发现:以前java的匿名内部类在访问外部变量的时候,外部变量必须用final修饰。Bingo,java8对这个限制做了优化,外部变量可以不用显式使用final修饰,但编译器会自动把它当成final来处理。
lambda表达式与匿名类
lambda表达式与匿名类的异同集中体现在三点上:
- lambda就是为了优化匿名内部类而生,lambda要比匿名类简洁的多得多;
- lambda仅适用于函数式接口,匿名类不受限;
- 在匿名类内部,this关键字指向该抽象类实例,而lambda内部this指向闭包实例。如果需要在内部通过this关键字访问实例,必须使用匿名类;
三、Java8 预定义函数式接口
Java8 引入了函数式接口,并在java.util.function 包内预定义了常用函数式接口,下表罗列了一些常用的函数式接口:
Function
Function接口接受一个输入参数T,返回一个结果R,即: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,对应的函数表达为:
- compose方法的含义是 V=Function(ParamFunction(T)) ;
- andThen方法的含义是 V=ParamFunction(Function(T)) ;
- identity方法的含义是 T=Function(T) ;
看个例子:
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接口相关的接口包括:
- BiFunction :R apply(T t, U u);接受两个参数,返回一个值,代表一个二元函数;
- DoubleFunction :R apply(double value);只处理double类型的一元函数;
- IntFunction :R apply(int value);只处理int参数的一元函数;
- LongFunction :R apply(long value);只处理long参数的一元函数;
- ToDoubleFunction:double applyAsDouble(T value);返回double的一元函数;
- ToDoubleBiFunction:double applyAsDouble(T t, U u);返回double的二元函数;
- ToIntFunction:int applyAsInt(T value);返回int的一元函数;
- ToIntBiFunction:int applyAsInt(T t, U u);返回int的二元函数;
- ToLongFunction:long applyAsLong(T value);返回long的一元函数;
- ToLongBiFunction:long applyAsLong(T t, U u);返回long的二元函数;
- DoubleToIntFunction:int applyAsInt(double value);接受double返回int的一元函数;
- DoubleToLongFunction:long applyAsLong(double value);接受double返回long的一元函数;
- IntToDoubleFunction:double applyAsDouble(int value);接受int返回double的一元函数;
- IntToLongFunction:long applyAsLong(int value);接受int返回long的一元函数;
- LongToDoubleFunction:double applyAsDouble(long value);接受long返回double的一元函数;
- LongToIntFunction:int applyAsInt(long value);接受long返回int的一元函数;
Predicate
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 >= 28;};Predicate<Person> predicate2 = (Person person) -> {String name = person.getName();return name.startsWith("R");};Person rico = new Person();rico.setName("Riemann");rico.setAge(26);System.out.println("Riemann名字以R开头且年满28岁 : " + predicate1.and(predicate2).test(riemann));System.out.println("Riemann名字以R开头或年满28岁 : " + predicate1.or(predicate2).test(riemann));System.out.println("Riemann名字不是以R开头 : " + predicate1.negate().test(riemann));
}
Riemann名字以R开头且年满28岁 : false
Riemann名字以R开头或年满28岁 : true
Riemann名字不是以R开头 : false
其他Predicate接口:
- BiPredicate:boolean test(T t, U u);接受两个参数的二元谓词;
- DoublePredicate:boolean test(double value);入参为double的谓词函数;
- IntPredicate:boolean test(int value);入参为int的谓词函数;
- LongPredicate:boolean test(long value);入参为long的谓词函数;
Consumer
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("Riemann");rico.setAge(26);Consumer<Person> consumer = person -> System.out.println(person);consumer.accept(riemann);
}
output:
{"age":26,"name":"Riemann"}
其他Consumer接口:
- BiConsumer:void accept(T t, U u);接受两个参数
- DoubleConsumer:void accept(double value);接受一个double参数
- IntConsumer:void accept(int value);接受一个int参数
- LongConsumer:void accept(long value);接受一个long参数
- ObjDoubleConsumer:void accept(T t, double value);接受一个泛型参数一个double参数
- ObjIntConsumer:void accept(T t, int value);接受一个泛型参数一个int参数
- ObjLongConsumer:void accept(T t, long value);接受一个泛型参数一个long参数
Supplier
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("Riemann");rico.setAge(26);Supplier<Person> supplier = () -> riemann;System.out.println("supplier : " + supplier.get());
}
output:
supplier : {"age":26,"name":"Riemann"}
其他Supplier接口:
- BooleanSupplier:boolean getAsBoolean(); 返回boolean
- DoubleSupplier:double getAsDouble();返回double
- IntSupplier:int getAsInt();返回int
- LongSupplier:long getAsLong();返回long
Java新特性:Java8函数式接口与Lambda表达式(一)相关推荐
- JAVA学习笔记 15 - 函数式接口、Lambda表达式和方法引用
本文是Java基础课程的第十五课.主要介绍在JDK8中,Java引入的部分新特性,包括函数式接口.Lambda表达式和方法引用.这些新特性使得Java能够在按照面向对象思想进行开发的基础上,融合函数式 ...
- Java8函数式接口与Lambda表达式
摘要 何为函数式接口? 什么是lambda表达式,lambda表达式的本质: 函数式接口与lambda表达式的联系:lambda是实现函数式接口的一个快捷方式,可以作为函数式接口的一个实例: 常用Ja ...
- JAVA8的新特性之函数式接口
JAVA8的新特性之函数式接口 1.Lambda表达式使用的前提,就是接口必须是一个函数式接口 2.定义 在接口中,只有一个抽象方法 3.检查是否是函数式接口用的注解 @FunctionalInter ...
- java8 Lambda表达式的应用(函数式接口、lambda表达式,方法引用及Stream API)
之前写了一篇博客简单介绍了一下java 8发布新增的一些特性功能,java 8在2014年发布,距今也不少年了,但是lambda表达式使用并不熟练,现在一边学习,一边记录一下. 目录 一.Lambda ...
- JDK8新特性之函数式接口
转载自 JDK8新特性之函数式接口 什么是函数式接口 先来看看传统的创建线程是怎么写的 Thread t1 = new Thread(new Runnable() {@Overridepublic v ...
- java 函数式接口与lambda表达式的关系
函数式接口与lambda表达式的关系 在java中,lambda表达式与函数式接口是不可分割的,都是结合起来使用的. 对于函数式接口,我们可以理解为只有一个抽象方法的接口,除此之外它和别的接口相比并没 ...
- java convert函数_Java 函数式编程和Lambda表达式
1.Java 8最重要的新特性 Lambda表达式.接口改进(默认方法)和批数据处理. 2.函数式编程 本质上来说,编程关注两个维度:数据和数据上的操作. 面向对象的编程泛型强调让操作围绕数据,这样可 ...
- C++11新特性中的匿名函数Lambda表达式的汇编实现分析(二)
2019独角兽企业重金招聘Python工程师标准>>> C++11新特性中的匿名函数Lambda表达式的汇编实现分析(一) 首先,让我们来看看以&方式进行变量捕获,同样没有参 ...
- Java笔记整理五(Iterator接口,泛型,常见数据结构(栈,队列,数组,链表,红黑树,集合),jdk新特性,异常,多线程,Lambda表达式)
Java笔记整理五 1.1Iterator接口 Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象 ...
最新文章
- velocity 模板引擎
- 大厂Android面试,居然还问这些问题!
- 1356. 回文质数【难度: 中 / 数学】
- HarmonyOS应用开发者门户,HarmonyOS 手机应用开发者 Beta 版到来,对开发者意味着什么...
- SAP Vim和ABAP Editor的个人设置
- 计算机图形学前沿领域的设想,计算机图形学
- 如何解决SQL挂起问题
- C#调用TSC打印机打印数据
- Visual Foxpro 6.0教程
- Android 声音采集回声与回声消除
- 高中计算机网络技术应用教案,高中信息技术选修3《网络技术应用》教案.doc
- Python 转 exe
- 吉他调音器(1)之十二平均律
- websocket站内信实时消息推送
- 现代流行的平面设计风格有哪些?——黎乙丙
- selenium简介,原理,优点,工作过程,定位方式
- 欧拉函数定义及其性质
- 5-(4-羟基苯基)-10,15,20-三-(4-溴苯基)卟啉(TPP-Brs)/2-羟甲基-5,10,15,20-四苯基卟啉/2-氯甲基5,10,15,20-四苯基卟啉齐岳供应
- crf码控中,crf值是如何工作的?
- nested exception is java.lang.NoClassDefFoundError
热门文章
- 【报告分享】 2020中国男士美妆市场洞察报告-巨量算数(附下载)
- 在线广告系统架构变迁
- 小白 MySQL数据库链接查询语句_MySQL数据库——连接查询
- xxx/Pods/Target Support Files/Pods-xxx/Pods-xxx-frameworks.sh: line 121: ARCHS[@]: unbound variable
- Detecting Unstable Periodic Orbits in Chaotic Experimental Data (解析)
- linux 提取cpio_【rpm】从rpm包中提取文件:rpm2cpio和cpio的使用
- “没有买卖就没有伤害,要严查个人信息倒卖”
- struts标签--logic总结
- 计算金球铁球体积c语言,网球金球制规则
- C#:OleDbDataAdapter 进行增,删,改,查操作