函数式接口与Lambda表达式
概念
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式变成场景的接口。而Java中函数式变成体现就是Lambda
,所以函数式接口就是适用于Lambda
使用的接口。只有确保接口中有且仅有一个抽象方法,Java
中的Lambda才能被顺利的进行推导。
语法糖
是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的forEach语法,其实底层实现原理仍然是迭代器。从应用层面来讲,Java
中的Lambda
也可以被当作是匿名内部类的语法糖
,但是二者在原理上是不相同的。
格式
只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数);
// 其他非抽象方法内容
}
Java中接口的特性:
- 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
- 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
- 接口中的方法都是公有的。
所以我们可以省略public abstract
的修饰,所以定义一个函数式接口就变得很简单:
public interface MyFunctionalInterface {
void hello();
}
@FunctionalInterface
注解
Java8
中专门为函数式接口引入了一个新的注解:@FunctionalInterface
。该注解可用于一个接口的定义上
@FunctionalInterface
public interface MyFunctionalInterface {
abstract void hello();
}
添加这个注解之后,如果接口中存在多个方法或者没有方法,就会报错,它的作用就是检测接口是否是一个函数式接口。(接口方法都是隐式抽象所以为了简化直接称为方法,而不再加抽象修饰)
函数式接口的使用
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
表达式是延迟执行的,这正好可以作为解决方案,提升性能。
性能浪费的日志案例
注:日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。
一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:
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的延迟
@FunctionalInterface
public interface MessageBuilder {
// 定义一个拼接消息的抽象方法,返回拼接消息
String joinMessage();
}
/**
* 使用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.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接口
java.util.function.consumer<T>
接口则正好与Supplier
接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
accept
/**
* 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
的源代码:
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
的语义正是“一步接一步”操作。例如两个步骤组合的情况:
/**
* 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)
。用于条件判断的场景:
/**
* 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
源码为:
default Predicate<T> and(Predicate<? super T> other) {
objects.requireNonNull(other);
return(t)->test(t) && other. test(t);
}
如果要判断一个字符围既要包含大写H
,又要包含大写W
,那么:
/**
* 逻辑表达式&&: 可以连接多个判断的条件
*/
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
名称即可,其他都不
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
源代码为:
default Predicate<T> negate(){
return (t) -> !test(t);
}
从实现中很容易看出,它是执行了test
方法之后,对结果boolean
值进行"”取反而已。一定要在test
方法调用之前调用negate
方法,正如and
和or
方法一样:
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
类型。
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
源代码如:
default <V> Function<T,V> andThen(Function<? super R,? extends V) after){
objects.requireNonNu11(after);
return(T t)->after.apply(apply(t));
}
该方法同样用于“先做什么,再做什么”的场景,和consumer
中的andThen
差不多:
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();
});
}
}