函数式接口在Java中是指: 有且仅有一个抽象方法的接口
函数式接口, 即适用于函数式编程场景的接口; 而Java中函数式编程体现就是Lambda, 所以函数式接口就是可以适用于Lambda使用的接口; 只有确保接口中有且仅有一个抽象方法, Java中的Lambda才能顺利地进行推导
备注 : “语法糖"是指使用更加方便, 但是远离不变的代码语法; 例如在遍历集合时使用的for-earch语法, 其实底层实现的仍然是迭代器, 这便是"语法糖”, 从应用层面来讲, Java中的Lambda可以被当做匿名内部类的"语法糖", 但是二者在原理上是不同的
格式 : 只要确保接口中有且仅有一个抽象方法即可
修饰符 interface 接口名称{
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
由于接口中抽象发的public abstract
是可以省略的, 所以定义一个函数式接口很简单
public interface MyFunctionInterface{
void myMethod();
}
与 @Override
注解的作用类似, Java8中专门为函数式接口引入了一个新的注解 : @FunctionalInterface
, 该注解可用于一个接口的定义上
/* 函数式接口的使用 : 一般作为方法的参数与返回值类型 */
public class Demo{
// 定义一个方法, 参数使用函数式接口MyFunctionalInterface
public static void show(MyFunctionalInterface myInter){
myInter.method()
}
public static void main(String[] args){
// 调用show方法, 方法的参数是一个接口,所以可以传递接口的实现类对象
show(new MyFunctionInterfaceImpl);
// 调用show方法, 方法的参数是一个接口, 所以我们可以传递接口的匿名内部类
show(new MyFunctionInterface{
@Override
public void method(){
System.out.println("使用匿名内部类重写接口中的抽象方法")
}
});
}
// 调用方法, 方法的参数是一个函数式接口, 所以可以使用Lambda表达式
show(() -> {
System.out.println("使用Lambda表达式重写接口中的抽象方法")
});
// Lambda简化
show( -> System.out.println("使用Lambda表达式重写接口中的抽象方法"));
}
有些场景的代码执行后, 结果不一定会被使用, 从而造成性能浪费, 而Lambda表达式是延迟执行的, 这正好可以作为解决方案, 提升性能
日志可以帮助我们快速的定位问题, 记录程序运行过程中的情况, 以便项目的监控和优化
一种典型的场景就是对参数进行有条件使用, 例如对日志消息进行拼接后 ,在满足条件的情况下进行打印输出
public class Demo01Logger{
private static void log(int level,String msg){
// 根据日志级别,显示日志信息的方法
if(level == 1){
System.out.println(msg);
}
}
public static void main(String[] args){
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1,msgA+msgB+msgC);
}
}
以上的代码问题在于, 当传入的level 不为1时, 还是海鲜拼接字符,造成性能上的浪费
我们要做的是 当第一个参数不满足的shih,不执行后面的操作; 避免性能的浪费
// 接口类
@FunctionalInterface
public interface MessageBuilder{
// 定义一个拼接消息的抽象方法, 返回被拼接的信息
public abstract String builderMessaeg();
}
public class Demo02Lambda{
// 定义一个现实日志的方法, 方法的参数传递日志的等级和MessageBuilder接口
public static vid showLog(int level,MessageBuilder mb){
// 对日志的等级记性判断,如果是1级,则调用接口中的方法
if(level==1){
System.out.println(mb.builderMessage());
}
}
public static void main(String[] args){
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
// 调用showLog方法,参数MessageBuilder是一个函数式接口,所以可以使用Lambda表达式
shoLog(1, () ->{
return msgA+msgB+msgC;
});
}
// lambda表达式作为参数传递, 仅仅是把参数传递到showLog方法中, 当level=1,才会调用接口中的方法 如果条件不满足,则接口中的方法不会执行, 不存在性能浪费
}
在使用比较器接口的时候, 可以使用Lambda表达式, 避免重写 compare
方法
java.util.function.Supplier
接口仅包含一个无参的方法 : T get(); 用来获取一个泛型指定类型的对象数据; 由于这是一个函数式接口, 这也意味着对应的Lambda需要对外提供一个符合泛型类型的对象数据
Supplier
import java.function.Supplier
public class Demo01Supplier{
private Static getString(Supplier<String> sup){
return sup.get();
}
public static void main(String[] args){
String msgA = "Hello";
String msgB = "World";
System.out.println(getString(() -> msgA+msgB)));
}
}
练习 : 求数组中元素最大值
使用supplier
接口作为方法参数类型, 通过Lambda表达式求出int数组中的最大值, 提示 : 接口的泛型请使用java.lang.Integer
类
public class Demo02Test{
public static int getMax(Supplier<Integer> sup){
return sup.get();
}
public static vid main(String[] args){
int arr[] = {2,56,48,3,9,64};
// 调用getMax方法,参数传递Lambda
int manNum = getMax(() -> {
// 计算数组中的最大值
int max = arr[0];
// 遍历数组
for(int i:arr){
if(i>max){
max = i;
}
}
return max;
});
System.out.println("数组中最大的元素"+max);
}
}
java.util.function.Consumer
接口则正好与Supplier相反, 它不是生产一个数据, 而是消费一个数据, 其数据类型由类型决定
抽象方法 :accept
Consumer
接口中包含抽象方法 void accept(T t)
, 意为消费一个指定泛型的数据,基本使用
import javautil.function.Consumer
public class Demo01Consumer {
private static void consumeString(String name,Consumer<String> con){
con.accept(name);
}
public static int method(Supplier<Integer> sup){
consumeString(s -> System.out.println(s));
}
}
更好地方法是使用 方法引用
默认方法 : andThen()
如果一个方法的参数和返回值都是Consumer
类型., 那么就可以实现效果 : 消费数据的时候, 实现先做一个操作, 然后在做一个操作, 实现组合; 而这个方法就是接口中的默认方法 andThen, 源码是
default Consumer andThen(Consumer super T> after){
Objects.requireNonNull(after);
return (T t) -> {accept(t);after.accept(t);};
}
备注 :
java.util.Objects
的requireNonNull
静态方法将会在参数为null时 主动抛出NullPointerException
异常, 这省去了重复编写if语句和抛出空指针异常的麻烦
要想实现组合, 需要两个或多个Lambda表达式即可, 而 andThen
的语义正式"一步接一步"操作, 例如两个步骤组合的情况:
public class Demo02AndThen{
public static void method(String s,Consumer con1,Consumer con2){
// 连接两个consumer,再消费
con1.andThen(con2).accept(s);
}
public static void main(String[] args){
meth("hello",(t) ->{
System.out.println(t.toUpperCase());
}, (t) ->{
System.out.println(t.toLowerCase());
})
}
}
格式化打印信息
public class Demo03Test{
public static void printInfo(String[] arr,Consumer con1,Consumer con2){
// 遍历字符串数组
for(String message: arr){
con1.andThen(con2).accept(messge);
}
}
public static void main(String[] args){
// 定义一个字符串类型的数组
String[] arr = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男"};
printInfo(arr,(message)->{
String name = meaasge.split(",")[0];
System.out.print("姓名: "+name);
},(meaasge)->{
String age = messaeg.aplit(",")[1];
System.out.println("年龄 :"+age);
})
}
}
有时我们需要对某种类型数据进行判断, 从而得到一个boolean值结果, 这时就可以使用jav.util.function.Predicate
接口
抽象方法 : test
Predicate
接口中包含一个抽象方法 :boolean test\
, 用于条件判断场景
public class DemoPredicateTest{
public static void method(Predicate<String> preficate){
boolean veryLong = predicate.test("HelloWorld");
System.out.println("字符串很长吗 :"+veryLong);
}
public static void main(String[] args){
method(s -> s.length() >5);
}
}
条件判断的标准是传入的Lambda表达式逻辑, 只要字符串长度大于5 则认为很长
默认方法 : and
既然是条件判断, 就会存在与, 或, 非三种常见的逻辑关系; 其中将两个predicate
条件使用"与"逻辑连接起来实现"并且"的效果时, 就可以使用default方法 and
,JDK源码为
default Predicate and(Predicate super T> other){
Objects.requireNonNull(other);
return (t) ->test(t) && other.test(t);
}
如果要判断一个字符既要包含大写"H", 又要包含大写"W", 那么:
public class DemoPredicateAnd{
public static void method(Predicate<String> one,Predicate<String> two){
boolean isValid = one.and(two).test("HelloWorld");
System.out.println("字符串很长吗 :"+isVaild);
public static void main(String[] args){
method(s -> s.contains("H"),s -> s.contains("W"));
}
}
}
默认方法 : or
与 and 的与类似 , 默认方法 or 实现逻辑关系中的 或 , JDK源码为
default Predicate or(Predicate super T> other){
Objects.requireNonNull(other);
return (t) ->test(t) || other.test(t);
}
如果实现逻辑字符串包含大写 H 或者大写 W , 那么大代码中西药将 and 修改为 or即可, 其他不变
public class DemoPredicateOr{
public static void method(Predicate<String> one,Predicate<String> two){
boolean isValid = one.or(two).test("HelloWorld");
System.out.println("字符串很长吗 :"+isVaild);
public static void main(String[] args){
method(s -> s.contains("H"),s -> s.contains("W"));
}
}
默认方法 : negate
与 或已经了解了, 剩下的 非(取反)也非常简单, 默认发 negate的 JDK源码为
default Predicate negate(){
return (t) -> !test(t);
}
从实现中很容易看出, 它执行了test方法之后, 对结果boolean值进行 ! 取反而已; 一定要在 test 方法调用之前调用 negate 方法, 正如and 和 or方法一样
public class DemoPredicateNegate{
public static boolean checkString(String s,Predicate pre){
return !pre.test(s);
}
public static void main(String[] args){
String = "abc"
boolean b = checkString(s,(String str)-> {
return str.length > 5
});
}
}
练习 : 集合信息筛选
数组当中有多条 “姓名+性别” 的信息如下, 请通过Pridicate 接口的拼接将符合要求的字符串筛选到集合ArrayList 中, 需要同时满足下面两个条件:
public class DemoPredicate{
public static void main(String[] args){
String[] arra = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男","赵丽颖,女"}
}
}
public class DemoTest{
public class ArrayList<String> filter(String[] arr,Predicate<String> pre1,Predicate<String> pre1){
ArrayList<String> list = new ArrayList<>();
for(String a:arr){
// 使用接口之间中的test方法对获取的字符进行判断
boolean b = pre1.and(pre2).test(s);
if(b){
list.add(s)
}
}
return list
}
public static void main(String[] args){
String[] array = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男","赵丽颖,女"}
// 调用filterffa
filter(array,(String s) ->{
s.split(",")[1].equals("女");
},(String s) ->{
s.split(",")[0].length == 4;
});
// 遍历集合
for(String s:list){
System.out.println(s);
}
}
}
java.util.function.Function
接口用来根据一个类型的数据得到另一个数据的数据, 前者称为前置条件, 后者称为后置条件
抽象方法 : apply
Function
接口中最主要的抽象方法为 : R apply(T t)
, 根据类型的T的参数获取类型R的结果
使用的场景 : 例如将 String 类型转换为 Integer 类型
public class DemoFunctionApply{
private static void change(Function<String,Integer> function){
int num = function.apply(s);
System.out.println(num);
}
public static void main(String[] args){
String s = "1234";
change(s,(String str) -> return Integer.parseInt(s));
}
}
默认方法 : andThen
andThen
方法用来进行组合操作, JDK源码如下
default Function andThen(Function super R,? extends V>after){
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
该方法 同样用于"先做什么. 再做什么"的场景, 和 Consumer中的andThen差不多:
public class DemoFunctionAndThen{
public static void change(String s,Function fun1,Function fun2){
String ss = fun1.andThen(fun2).apply(s);
System.out.println(ss);
}
public static void main(String[] args){
String = "123";
change(s,(String str) ->{
return Integer.parseInt(str)+10
},(Integer i) ->{
return i+"";
});
}
}
练习: 自定义函数类型拼接
请使用Function
进行函数模型的拼接, 按照顺序需要执行的多个函数操作为
String str = “赵丽颖,20”;
public class Demo03Test{
public static int change(String s,Function<String,String> fun1,Function<String,String> fun2,Function<String,String> fun3){
// 使用andThen方法将三个组合到一起
return fun1.andThen(fun2).andThen(fun3).apply(s);
}
public static void main(String[] args){
String str = "赵丽颖,20";
int num =change(str,(String s)->{
return s.split(",")[1];
},(String s)->{
return Integer.parseInt(s);
},(Integer i)->{
return i+100
});
System.out.println(num);
}
}