函数式接口
-
概念
函数式接口在Java中是指:有且仅有一个抽象方法的接口。函数式接口,既适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
备注:“语法糖” 是指使用更加方便,但是原理湖边的代码语法。例如在遍历结合时使用发for-each语法,其实底层的实现原理仍然是迭代器,这便是"语法糖"。从应用层面来讲,Java中的Lambda可以被当做是匿名内部类的"语法糖",但是二者在原理上是不同的。 -
格式
只要确保接口中有且仅有一个抽象方法即可:修饰符interface接口名称{ public adstract 返回值类型 方法名称(可选参数信息); }
-
@FunctionalInterface注解
与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上:/** 只有一个抽象方法的接口就是函数式接口 */ @FunctionalInterface //声明接口必须是一个函数式接口 public interface MyInterface01 { void show(); }
代码使用:
public class Demo01 {
public static void main(String[] args) {
//函数式接口,主要是在函数式编程(lambda)中使用
//非函数式编程
method01(new MyInterface01() {
@Override
public void show() {
System.out.println("hello functional interface 111");
}
});
//函数式编程:就是在匿名内部类对象的基础上,省略接口名及方法名.是语法糖
method01(() -> {
System.out.println("hello functional interface 111");
});
//简写方式:如果方法体中只有一行代码,可以省略方法体的大括号
method01(()-> System.out.println("hello functional interface 111"));
}
public static void method01(MyInterface01 inter01){
inter01.show();
}
}
-
函数式接口的优势
函数式接口比匿名内部类对象产生更少的字节码对象,提升java执行效率 -
函数式接口作为参数,方法带参数
接口:@FunctionalInterface public interface MyInterface02 { void show(String msg , int num); }
代码使用:
public class Demo02 {
public static void main(String[] args) {
//非函数式
method(new MyInterface02() {
@Override
public void show(String msg, int num) {
System.out.println(msg + " , " + num);
}
});
//函数式
method((String msg, int num) -> {
System.out.println(msg + " , " + num);
}
);
//简写方式一:省略了形参的类型
method((msg, num) -> {
System.out.println(msg + " , " + num);
}
);
//简写方式二: 省略方法体的大括号
method((msg, num) -> System.out.println(msg + " , " + num));
}
public static void method(MyInterface02 inter) {
inter.show("hello", "你好");
}
}
-
函数式接口作为返回值,方法带参数
//定义函数式接口 public interface MyInterface02 { public abstract void show(String msg1, String msg2); } public static void main(String[] args) { getInter1().show("你好", "函数式"); getInter2().show("你好", "函数式"); } // 函数式完整 public static MyInterface02 getInter1() { return (String msg1, String msg2) ‐> { System.out.println(msg1 + msg2); }; } // 函数式简写 public static MyInterface02 getInter2() { return (msg1, msg2) ‐> System.out.println(msg1 + msg2); }
-
函数式接口的方法有返回值
//定义函数式接口 public interface MyInterface03 { public abstract String getMsg(); } public static void main(String[] args) { showMsg(new MyInterface03() { @Override public String getMsg() { return "hello functional interface"; } }); // lambada表达式 showMsg(() ‐> { return "hello1 functional interface"; }); // lambda表达式简写 showMsg(() ‐> "hello1 functional interface"); } public static void showMsg(MyInterface03 inter) { String msg = inter.getMsg(); System.out.println(msg); }
函数式编程应用场景
-
概念
在兼顾面向对象特性的基础上,Java语言通过Lambda表达式使用函数式接口,就叫做函数式编程 -
使用Lambda作为参数
如果抛开实现原理不说,Java中的Lambda表达式可以被当做是i匿名内部类的替代品。如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代public class Demo07 { public static void main(String[] args) { startThread(new Runnable() { @Override public void run() { System.out.println("线程要做的事"); } }); startThread(() -> { System.out.println("线程要做的事"); } ); //比第一种的可阅读性强很多!!! startThread(() -> System.out.println("线程要做的事~~~")); } public static void startThread(Runnable runnable) { new Thread(runnable).start(); } }
-
使用函数式接口作为返回值
如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式public class Demo08 { public static void main(String[] args) { Integer[] nums = {1 ,4 ,8,3}; Arrays.sort(nums,getComparator3()); for (Integer num : nums) { System.out.println(num); } } public static Comparator<Integer> getComparator1(){ return new Comparator<Integer>() { @Override public int compare(Integer num1, Integer num2) { return num1 - num2; } }; } public static Comparator<Integer> getComparator2(){ return (num1 , num2) -> { return num1 - num2; }; } public static Comparator<Integer> getComparator3(){ //如果方法体中,只有一行代码,且该代码是一个返回语句,就可以省略大括号及return关键字 return (num1 , num2) -> num1 - num2; } }
日志案例
-
传统日志
public class Demo09 { public static void main(String[] args) { String msg1 = "日志1"; String msg2 = "日志2"; String msg3 = "日志3"; printLog(2, msg1 + msg2 + msg3); } public static void printLog(int level , String msg){ if (level == 1) { System.out.println(msg); } } }
这段代码存在问题:无论级别是否满足 要求,作为log方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费
-
函数式日志
/** * 提供日志内容 */ @FunctionalInterface public interface MyInterface06 { String getMsg(); }
代码实现:
public class Demo09 {
public static void main(String[] args) {
//level=1 , 拼接msg1,msg2,msg3. 应该打印日志
//level=2 , 拼接msg1,msg2,msg3. 不应该打印日志
String msg1 = "日志1";
String msg2 = "日志2";
String msg3 = "日志3";
printLog(1, new MyInterface06() {
@Override
public String getMsg() {
return msg1 + msg2 + msg3;
}
});
printLog(1, () -> {
return msg1 + msg2 + msg3;
}
);
printLog(1 ,() -> msg1 + msg2 + msg3);
}
public static void printLog(int level , MyInterface06 inter){
if (level == 1) {
//只有当日志级别为1的时候,采取获取日志,并打印日志
System.out.println(inter.getMsg());
}
}
}
常用函数式接口(Supplier接口)
JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在java.util.function包中被提供。下面是简单的几个接口及使用示例
-
Supplier接口
java.util.function.Supplier接口仅包含一个无参的方法:T get()。 用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据 -
基本使用
public static void main(String[] args) { //非函数式 String msg1 = getMsg(new Supplier<String>() { @Override public String get() { return "abc"; } }); System.out.println(msg1); String msg2 = getMsg(() -> { return "abc"; }); System.out.println(msg2); String msg3 = getMsg(() -> "abc"); System.out.println(msg3); } /** * getMsg方法的返回值是由Supplier接口中的get方法返回!!! * * @param supplier * @return */ public static String getMsg(Supplier<String> supplier) { return supplier.get(); }
-
综合案例
-
需求:
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中最大值。
提示:接口的泛型请使用java.lang.integer类。public class Demo11 { public static void main(String[] args) { // 使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。 Integer max1 = getMax(new Supplier<Integer>() { @Override public Integer get() { int[] nums = {1, 2, 3, 4, 5}; int max = nums[0]; for (int num : nums) { if (max < num) { max = num; } } return max; } }); System.out.println(max1); System.out.println("-----------"); Integer max2 = getMax(() -> { int[] nums = {1, 2, 3, 4, 5}; int max = nums[0]; for (int num : nums) { if (max < num) { max = num; } } return max; }); System.out.println(max2); } /** * 获取最大值 * @param supplier * @return */ public static Integer getMax(Supplier<Integer> supplier){ return supplier.get(); } }
-
常用函数式接口(Consumer接口)
-
Consumer接口java.util.function.Consumer 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其他数据类型由泛型决定
-
accept方法Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。基本使用如:
public class Demo12 { public static void main(String[] args) { print(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } },"helloworld1"); print(( s)-> { System.out.println(s); },"helloworld2"); print((msg) -> System.out.println(msg) ,"helloworld3"); } //定义一个变量,由Consumer来消费该变量,就打印 public static void print(Consumer<String> consumer , String msg) { consumer.accept(msg); } }
-
andThen方法
如果 一个方法的参数和返回值全都是Consumer类型,那么就可以实现效果:消费数据时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer接口中的default方法andThendefault Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; }
注:java.uitl.Objects的requireNonNull静态方法将会在参数null时主动抛出NullPointerException异常。这省去了重复编写if语句和抛出空指针异常的麻烦
public class Demo13 {
//递归:同一个对象的方法调用自己
public static void main(String[] args) {
consumeMsg("武汉加油", new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
}, new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s+"中国加油");
}
});
consumeMsg("武汉加油", (String s) -> {
System.out.println(s);
}, (String s) -> {
System.out.println(s+"中国加油");
});
consumeMsg("武汉加油!",msg-> System.out.println(msg+"a"),msg-> System.out.println(msg+"中国加油!"));
}
public static void consumeMsg(String msg , Consumer<String> consumer1 , Consumer<String> consumer2){
consumer1.andThen(consumer2).accept(msg);
}
}
常用函数式接口(综合案例)
需求
下面的字符串数组当中存有多条信息,请按照格式“姓名:xx。性别:xx”的格式讲信息打印出来。要求将打印姓名的动作作为第一个Consumer接口的Lambda实例,将打印性别动作作为第二个Consumer接口的Lambda实例,将两个Consumer接口按照顺序“拼接”到一起
String[] arrays = {"迪丽热巴,女", "老邱,男", "古力娜扎扎,女"};
实现
public class Demo14 {
public static void main(String[] args) {
String[] arrays = {"迪丽热巴,女", "鑫磊,男", "古力娜扎扎,女"};
printInfo(arrays,
new Consumer<String>() {
@Override
public void accept(String arr) {
System.out.print("姓名:" + arr.split(",")[0] + ".");
}
},
new Consumer<String>() {
@Override
public void accept(String arr) {
System.out.println("性别:" + arr.split(",")[1]);
}
}
);
printInfo(arrays,
(arr) -> {
System.out.print("姓名:" + arr.split(",")[0] + ".");
},
(arr) -> {
System.out.println("性别:" + arr.split(",")[1]);
}
);
printInfo(arrays,
arr -> System.out.print("姓名:" + arr.split(",")[0] + "."),
arr -> System.out.println("性别:" + arr.split(",")[1])
);
}
public static void printInfo(String[] arrays, Consumer<String> consumer1, Consumer<String> consumer2) {
for (String arr : arrays) {
//consumer1和consumer2都在消费array数组中的每一个元素
consumer1.andThen(consumer2).accept(arr);
}
}
}
常用函数式接口(Predicate接口)
-
有时候我们需要对某种类型数据进行判断,从而得到一个Boolean值结果,这时候可以使用java.util.function.Predicate接口
-
test方法
Predicate接口中包含一个抽象方法:boolean(T t)。用户条件判断的场景public class Demo15 { // 判断字符是否等于4 public static void main(String[] args) { boolean isLength1 = isLength("hello", new Predicate<String>() { @Override public boolean test(String msg) { return 4 == msg.length(); } }); System.out.println(isLength1); boolean isLength2 = isLength("hello", (String msg) -> { return msg.length() == 4; }); System.out.println(isLength2); boolean isLength3 = isLength("hello", msg -> msg.length() == 4); System.out.println(isLength3); } public static boolean isLength(String msg,Predicate<String> predicate ){ return predicate.test(msg); } }
-
综合案例
需求:
将姓名长度为4,性别为女 的信息保存并打印public class Demo16 { public static void main(String[] args) { String[] arrays = {"迪丽热巴,女", "鑫磊,男", "古力娜扎扎,女"}; // List<String> list = method(arrays, arr -> arr.split(",")[0].length() == 4 && arr.split(",")[1].equals("女")); // System.out.println(list); List<String> list1 = method1( arrays, arr -> arr.split(",")[0].length() == 4, arr -> arr.split(",")[1].equals("女") ); System.out.println(list1); } public static List<String> method(String[] arrays,Predicate<String> predicate){ List<String> list = new ArrayList<>(); for (String array : arrays) { boolean test = predicate.test(array); if (test) { list.add(array); } } return list; } public static List<String> method1(String[] arrays,Predicate<String> predicate1 ,Predicate<String> predicate2){ List<String> list = new ArrayList<>(); for (String array : arrays) { boolean test = predicate1.negate().and(predicate2).test(array); if (test) { list.add(array); } } return list; } }