Java【函数式接口】
一、函数式接口
1.1 概念
函数式接口:有且仅有一个抽象方法的接口。适用于函数式编程的接口,在java中的函数式编程的体现就是Lambda表达式,所以函数式接口就是可以适用于Lambda表达式使用的接口。
语法糖:是指使用更加方便,但是原理不便的代码。例如遍历集合中使用的for-each
语法,其实底层的实现原理仍然是迭代器。从应用层面上来讲,Java的Lambda可以被认为是匿名内部类的语法糖,但是二者原理上有区别。
1.2 定义
有且仅有一个抽象方法,public abstract
可以省略。
public interface MyFunctionalInterface {
// 定义一个抽象方法
void method();
}
1.3 @FunctionalInterface注解
作用:可以检测接口是否是一个函数式接口。
1.4 自定义函数式接口
用法:一般作为方法的参数或者返回值
注意:接口实现类/匿名内部类编译运行后会生成一个class文件,Lambda不会生成class文件,内存开销更小,效率更高。
/*
函数式接口的使用:一般作为方法的参数或者返回值类型
*/
public class Demo {
// 定义一个方法,参数使用函数式接口MyFunctionalInterface
public static void show(MyFunctionalInterface myInter) {
myInter.method();
}
public static void main(String[] args) {
// 调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象/接口的匿名内部类
show(new MyFunctionalInterface() {
@Override
public void method() {
// 函数式接口的内部类中重写方法的内容
System.out.println("使用匿名内部类重写接口中的抽象方法");
}
});
// 调用show方法,方法的参数是一个接口,所以可以使用Lambda
show(() -> {
System.out.println("使用Lamda重写接口中的抽象方法");
});
// 简化Lambda
show(() -> System.out.println("使用简化的Lambda重写接口中的抽象方法"));
}
}
二、函数式编程
在兼顾面向对象的基础上,Java语言通过Lambda表达式与方法引用,为开发者打开函数式编程的大门。
2.1 Lambda的延迟执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式具备延迟执行的特性,作为解决方案,提升性能。
- 日志案例:不管传递的日志等级是不是1级,都会存在字符串拼接问题,代码存在性能上的浪费。
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);
}
}
- 利用
Lambda
的延迟执行特点,优化后的日志案例:
@FunctionalInterface
public interface MessageBuilder {
// 定义一个拼接消息的抽象方法,返回被拼接的字符串
public abstract String buildMessage();
}
public class Demo02Lambda {
public static void showLog(int level, MessageBuilder mb) {
// 判断当日志等级为1的时候,打印MessageBuilder接口的抽象方法的返回值
if(level == 1) {
System.out.println(mb.buildMessage());
}
}
public static void main(String[] args) {
// 定义三个日志信息
String msg1 = "hello";
String msg2 = "world";
String msg3 = "java";
/*
使用Lambda:
如果日志等级不是1级,就不会执行MessageBuilder接口的抽象方法,也就不会拼接字符串,降低了资源的占用,提高效率。
*/
showLog(2,()->{
System.out.println("日志等级是1");
return msg1 + msg2 + msg3;
});
// 优化
//showLog(1,()->msg1+msg2+msg3);
}
}
2.2 使用Lambda作为参数和返回值
- Lambda作为参数:
java.util.Runnable
接口作为方法参数,该接口是一个函数式接口,所以可以使用Lambda进行传参,这种情况其实和Thread类的构造方法参数为Runnable
没有本质区别。
public class Demo01Runnable {
public static void startThread(Runnable run){
// 创建线程并启动
new Thread(run).start();
}
public static void main(String[] args) {
// 调用startThread方法,参数传递匿名内部类
startThread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + "线程启动了");
}
});
// 调用startThread方法,参数传递Lambda
startThread(() -> {
System.out.println(Thread.currentThread().getName()+ "-->" + "线程启动了");
});
// 优化Lambda
startThread(() -> System.out.println(Thread.currentThread().getName()+"-->" + "线程启动了"));
}
}
- Lambda作为返回值:
java.util.Comparator
来作为方法的返回值类型,注释部分使用了匿名内部类作为方法的返回值,Lambda
是匿名内部类的简化形式。
public class Demo02Comparator {
public static Comparator<String> getComparator(){
// 方法的返回值类型是Comparator接口,返回匿名内部类
//return new Comparator<String>() {
// @Override
// public int compare(String o1, String o2) {
// // 使用字符串的长度降序排列
// return o2.length()-o1.length();
// }
//};
// 使用Lambda作为返回值
//return (o1, o2) -> {return o2.length()- o1.length();};
// 优化后的Lambda
return (o1, o2) -> o2.length()-o1.length();
}
public static void main(String[] args) {
String[] strings = {"aaaaa","bbb","cccccc","ddddddddd"};
// 输出排序前的数组
System.out.println("排序前:" + Arrays.toString(strings));
// 使用自定义规则进行排序
Arrays.sort(strings, getComparator());
// 输出排序后的数组
System.out.println("排序后:" + Arrays.toString(strings));
}
}
三、常用的函数式接口
java.util.function
包中提供了大量常用的函数式接口,以此来丰富Lambda的典型使用场景。
3.1 Supplier接口
概念及使用方法
java.util.function.Supplier<T>
接口仅包含一个无参的方法:T get()
,用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
public class Demo01Supplier {
// 定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String
public static String getString(Supplier<String> sup) {
return sup.get();
}
public static void main(String[] args) {
// 调用getString方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
String s = getString(()->{
// 生产一个字符串,并返回
return "hello";
});
System.out.println(s);
// 优化Lambda
s = getString(()-> "zx");
System.out.println(s);
}
}
练习:求数组元素最大值。
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。
public class Demo02Test {
// 定义一个方法,用户获取int类型数组中元素的最大值,方法的参数传递Supplier接口,泛型使用Integer
public static Integer getMax(Supplier<Integer> sup) {
return sup.get();
}
public static void main(String[] args) {
// 定义一个int类型的数组,并赋值
int[] arr = {100, 20, -88, 23, 878, -239};
// 调用getMax方法,方法的参数是一个函数式接口,传递Lambda表达式
int maxValue = getMax(() -> {
// 定义一个int类型变量
int max = arr[0];
// 循环遍历数组,比较大小
for (int i : arr) {
if(max < i) {
max = i;
}
}
return max;
});
// 打印最大值
System.out.println("arr的最大值:" + maxValue);
}
}
3.2 Consumer接口
概念及使用方法
java.util.function.Consumer<T>
接口与Supplier
接口相反,不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
抽象方法:accept
Consumer
接口中包含抽象方法void accept(T t)
,意为消费一个指定泛型的数据。
使用Consumer接口消费字符串数据:
public class Demo01Consumer {
/*
定义一个方法
方法的参数传递一个字符串的姓名
方法的参数传递Consumer接口,泛型使用String类型
可以使用Consumer接口消费字符串的姓名
*/
public static void method(String name, Consumer<String> con){
con.accept(name);
}
public static void main(String[] args) {
// 调用method方法,传递字符串姓名,方法的另一个参数是Consumer接口,是一个函数式接口
method("赵晓", (String name)->{
消费字符串的方式为打印字符串
//System.out.println(name);
// 消费方式:把字符串进行反转输出
String reName = new StringBuffer(name).reverse().toString();
System.out.println(reName);
});
}
}
默认方法:andThen
如果方法的参数和返回值都是Consumer
类型,那么可以通过default方法andThen
来实现:在消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。
源码:
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
使用案例
public class Demo02AndThen {
// 定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用字符串
public static void method(String s, Consumer<String> con1, Consumer<String> con2) {
//con1.accept(s);
//con2.accept(s);
// 使用andThen方法,把两个Consumer接口连接到一起,再消费数据
con1.andThen(con2).accept(s);
}
public static void main(String[] args) {
// 调用method方法,传递一个字符串,两个Lambda表达式
method("HelloZhaoXiao",
(t)->{
// 消费方式:把字符串转换为大写输出
System.out.println(t.toUpperCase());
},(t)->{
// 消费方式:把字符串转换为小写输出
System.out.println(t.toLowerCase());
});
}
}
练习:格式化打印信息
需求:字符串数组当中存有多条信息,请按照格式:“姓名:xx。性别:xx。”的格式将信息打印出来。
public class Demo03Test {
//定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String
public static void printInfo(String[] arr, Consumer<String> con1, Consumer<String> con2) {
// 遍历字符串数组
for (String s : arr) {
// 消费字符串
con1.andThen(con2).accept(s);
}
}
public static void main(String[] args) {
// 定义一个字符串类型的数组
String[] arr = {"迪丽热巴,女", "古力娜扎,女","玛尔扎哈,男","铁打赵晓,女"};
// 调用方法printInfo,参数传递String类型的数组和两个Lambda表达式,泛型使用String
printInfo(arr,
(s)->{
// 消费方式:获取姓名并打印
String name = s.split(",")[0];
System.out.print("姓名:" + name);
},
(s)->{
// 消费方式:获取性别并打印
String sex = s.split(",")[1];
// 打印并换行
System.out.println(",性别:" + sex + "。");
});
}
}
3.3 Predicate接口
概念及使用方法
java.util.function.Predicate<T>
接口,用于对某种数据类型的数据进行判断,得到一个boolean类型的值作为结果。
抽象方法:test
boolean test<T t>
用来对指定数据类型进行判断的方法。
使用实例:
public class Demo01Predicate {
/*
定义一个方法
参数传递一个String类型的字符串
传递一个Predicate接口,泛型使用String
使用Predicate接口中的方法test对字符串进行判断,并把判断的结果返回
*/
public static boolean checkString(String s, Predicate<String> pre) {
return pre.test(s);
}
public static void main(String[] args) {
// 定义一个字符串
String s = "abcdef";
//
调用checkString()方法对字符串进行校验,参数传递字符串和Lambda表达式
//boolean b = checkString(s, (String str)->{
// // 对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断结果返回
// return str.length()>5;
//});
// 优化Lambda
boolean b = checkString(s, str->str.length()>5);
System.out.println(b);
}
}
默认方法:and &or & negate
逻辑表达式:可以连接多个判断的条件,&&:与运算符,||:或运算符,!:非(取反)运算符。Predicate
接口中的三个default
方法具备这三个功能。
and:连接两个判断,实现逻辑与的功能。
源码及使用案例:
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
public class Demo02Predicate_and {
/*
定义一个方法,方法的参数,传递一个字符出纳
传递两个Predicate接口
一个用于判断字符串的长度是否大于5
一个用于判断字符串中是否包含a
两个条件必须同时满足
*/
public static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2){
//return pre1.test(s) && pre2.test(s);
// 使用default方法and
return pre1.and(pre2).test(s);
}
public static void main(String[] args) {
// 定义一个字符串
String s = "abefg";
// 调用checkString,参数传递字符串和两个Lambda表达式
boolean b = checkString(s, (String str)-> {
// 判断字符串的长度是否大于5
return str.length()>5;
},(String str)->{
// 判断字符串中是否包含a
return str.contains("a");
});
System.out.println(b);
}
}
or:连接两个判断,实现逻辑或的功能。
源码及使用案例
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
public class Demo03Predicate_or {
/*
定义一个方法,方法的参数,传递一个字符出纳
传递两个Predicate接口
一个用于判断字符串的长度是否大于5
一个用于判断字符串中是否包含a
两个条件必须满足一个
*/
public static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2){
//return pre1.test(s) || pre2.test(s);
// 使用default方法or
return pre1.or(pre2).test(s);
}
public static void main(String[] args) {
// 定义一个字符串
String s = "befgeeeedf";
// 调用checkString,参数传递字符串和两个Lambda表达式
boolean b = checkString(s, (String str)-> {
// 判断字符串的长度是否大于5
return str.length()>5;
},(String str)->{
// 判断字符串中是否包含a
return str.contains("a");
});
System.out.println(b);
}
}
negate:实现逻辑非的功能。
源码及使用:
default Predicate<T> negate() {
return (t) -> !test(t);
}
public class Demo04Predicate_negate {
/*
定义一个方法,方法的参数,传递一个字符串
使用Predicate接口判断字符串的长度是否大于5
*/
public static boolean checkString(String s, Predicate<String> pre) {
//return !pre.test(s);
// 使用negate方法
return pre.negate().test(s);
}
public static void main(String[] args) {
// 定义一个字符串
String s = "adds";
// 调用 checkString方法,传递一个字符串和Lambda表达式
boolean b = checkString(s, (String str) -> { // 加了取反后变成字符串的长度大于5返回false
// 字符串的长度大于5返回true
return str.length()>5;
});
System.out.println(b);
}
}
练习:集合信息筛选
/*
练习:集合信息筛选
数组当中有多条“姓名+性别”的信息如下,
String[] arr = {"迪丽热巴,女", "古力娜扎,女","玛尔扎哈,男","铁打赵晓,女"};
请通过Predicate接口的拼装将符合要去的字符串筛选到ArrayList中,需要同时满足两个条件:
1、必须为女生;
2、姓名为4个字
*/
public class Demo05Test {
/*
定义一个方法,方法的参数传递一个包含人员信息的数组
传递两个Predicate接口,用于对数组中的信息进行过滤
把满足条件的信息保存到ArrayList集合中并返回
*/
public static ArrayList<String> filter(String[] arr, Predicate<String> pre1, Predicate<String> pre2){
// 定义一个ArrayList集合,存储筛选后的数据
ArrayList<String> list = new ArrayList<>();
// 遍历数组中的字符串进行判断
for (String s : arr) {
if(pre1.and(pre2).test(s)){
// 同时满足两个条件添加到集合中
list.add(s);
}
}
return list;
}
public static void main(String[] args) {
// 定义一个字符串数组
String[] arr = {"迪丽热巴,女", "古力娜扎,女","玛尔扎哈,男","打铁赵晓,女"};
// 调用filter方法,参数传递一个字符串数组,两个Lambda表达式
ArrayList<String> arrayList = filter(arr,
(String str)->{
// 条件1:必须为女生
return str.split(",")[1].equals("女");
},
(String str)->{
// 条件2:姓名为4个字
return (str.split(",")[0].length()) == 4;
});
// 遍历集合输出
for (String s : arrayList) {
System.out.println(s);
}
}
}
3.4 Function接口
概念及使用方法
java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
抽象方法:apply
Function
接口中抽象方法为:R apply(T t)
,根据类型T的参数获取类型R的结果。使用场景例如:将String
类型的数据转换为Integer
类型。
public class Demo01Function {
/*
定义一个方法
方法的参数为一个字符串类型的整数、一个Function接口<String,Integer>
使用apply方法,把字符串类型的整数转换为Integer类型的整数
*/
public static void change(String s, Function<String, Integer> func) {
int in = func.apply(s);// 自动拆箱
System.out.println(in);
}
public static void main(String[] args) {
// 定义一个字符串类型的整数
String s = "1234";
// 调用change方法
change(s,(String str)->{
// 将字符串转换为Integer类型
return Integer.parseInt(str);
});
}
}
默认方法:andThen
/*
Function接口中的default方法andThen:用来进行组合操作
需求:
把String类型的“123”,转换为Integer类型,把转换后的结果加10
把增加之后的Integer类型的数据,转换为String类型
*/
public class Demo02Function_andThen {
/*
定义一个方法:
参数传递一个字符串类型的整数,和两个Function接口
*/
public static void change(String s, Function<String, Integer> func1, Function<Integer, String> func2) {
// 使用andThen方法进行拼接两个转换操作
String str = func1.andThen(func2).apply(s);
System.out.println(str);
}
public static void main(String[] args) {
// 定义一个字符串类型的数
String s = "123";
// 调用change方法
change(s,
(String str)->{
// 把String类型的“123”,转换为Integer类型,把转换后的结果加10
return Integer.parseInt(s) + 10;
}, (Integer in)->{
// 把增加之后的Integer类型的数据,转换为String类型
return in.toString();
});
}
}
练习:自定义函数模型拼接
需求:使用Function进行函数的拼接,按照顺序需要执行的多个函数操作:
String str = “打铁赵晓,20”;
1.将字符串截取数字年龄部分,得到字符串;
2.将上一步的字符串转换为Int类型的数字;
3.将上一步的Int类型的数字累加100,得到结果int数字。
public class Demo03Tets {
/*
定义一个方法
参数传递包含姓名和年龄的字符串、3个Function接口
*/
public static int change(String s, Function<String, String> func1,
Function<String, Integer> func2, Function<Integer, Integer> func3){
return func1.andThen(func2).andThen(func3).apply(s);
}
public static void main(String[] args) {
//定义一个字符串
String s = "打铁赵晓,20";
调用change方法
//int in = change(s,
// (String str)->{
// return str.split(",")[1];
// },
// (String str)->{
// return Integer.parseInt(str);
// },
// (Integer integer)->{
// return integer +100;
// });
// 优化Lambda
int in = change(s,
str->str.split(",")[1],
str->Integer.parseInt(str),
integer->integer +100);
System.out.println(in);
}
}