今天我们来学习几个常见的函数式接口。
以下是今天的学习目标:
- 能够使用@FunctionalInterface注解
- 能够理解Lambda延迟执行的特点
- 能够使用Lambda作为方法的参数
- 能够使用Lambda作为方法的返回值
- 能够使用Supplier<T>函数式接口
- 能够使用Consumer<T>函数式接口
- 能够使用Predicate<T>函数式接口
- 能够使用Function<T, R>函数式接口
以下是今天的详细笔记:
函数式接口
函数式接口概念, 自定义函数式接口
函数式接口: JDK 8 新特性
有且仅有一个抽象方法的接口, 适用于函数式编程场景的接口
(默认方法, 静态方法, 私有方法, 与java.lang.Object类中定义相同的抽象方法, 都不算作抽象方法)
自定义函数式接口:
接口中有且只有一个抽象方法
@FunctionalInterface的作用:
在接口上使用, 检测当前的接口是否为函数式接口
[Java] 纯文本查看 复制代码 // 格式
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
// 示例
@FunctionalInterface
public interface MyFunctionalInterface {
void method();
}
函数式接口的使用
函数式接口的使用: 代替匿名内部类方式
函数式编程性能浪费的日志案例
日志: 就是存储程序运行信息的文本文件.
我们平时打印输出一些信息是在控制台上, 如果将这些信息写入文本文件, 这些文件就是日志
[Java] 纯文本查看 复制代码 public static void showLog(int level, String message) {
if (level == 1) {
System.out.println(message);
}
}
showLog(2, s1 + s2 + s3);
// 先拼接s1+s2+s3出结果"helloworldjava", 然后才执行方法内部的判断
// 相当于方法不需要执行, 但仍然在拼接字符串时浪费了一些性能
使用Lambda延迟执行的特性优化日志案例
Lambda具有延迟执行的特点:
传递Lambda对象, 只有当符合执行条件时, 才会执行代码
[Java] 纯文本查看 复制代码 @FunctionalInterface
interface MessageBuilder {
String buildMessage();
}
public static void showLog(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
showLog(2, () -> s1+s2+s3);
// 传递Lambda对象, 即MessageBuilder实现类对象进方法
// 判断2和1不相等, 所以不会执行builder.buildMessage()
// 从而也不会执行重写方法中的s1+s2+s3
// 避免了无意义的性能浪费
5分钟练习: 对比Lambda和普通方式执行效率
需求:
定义函数式接口 MessageBuilder, 使用@FunctionalInterface注解修饰
定义抽象方法用于拼接字符串: String buildMessage();
定义测试类:
定义方法模拟拼接大量字符串:
[Java] 纯文本查看 复制代码 public static String getString() {
// 模拟拼接字符串的耗时
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String s1 = "hello";
String s2 = "world";
String s3 = "java";
return s1 + s2 + s3;
}
定义方法: public static void showLog(int level, String message)
方法内部判断如果level==1, 则打印输出message
定义方法: public static void showLogLambda(int level, MessageBuilder builder)
方法内部判断如果level==1, 则打印输出builder.buildMessage()返回的字符串
定义main方法:
调用 showLog(2, getString()), 在方法调用前后获取毫秒值, 相减得出执行时间, 打印出来
调用 showLogLambda(2, ()->getString());在方法调用前后获取毫秒值, 相减得出执行时间, 打印出来
代码:
[Java] 纯文本查看 复制代码 @FunctionalInterface
public interface MessageBuilder {
String buildMessage();
}
public class Test {
public static String getString() {
// 模拟拼接字符串的耗时
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String s1 = "hello";
String s2 = "world";
String s3 = "java";
return s1 + s2 + s3;
}
// 普通方式打印日志方法
public static void showLog(int level, String message) {
if (level == 1) {
System.out.println(message);
}
}
// Lambda方式打印日志方法
public static void showLogLambda(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
// 统计普通方式消耗时间
long start1 = System.currentTimeMillis();
showLog(2, getString());
long end1 = System.currentTimeMillis();
System.out.println("普通方式耗时:" + (end1 - start1) + "毫秒");
// 统计Lambda延迟执行方式耗时
long start2 = System.currentTimeMillis();
showLogLambda(2, ()->{return getString();});
long end2 = System.currentTimeMillis();
System.out.println("Lambda延迟执行方式耗时:" + (end2 - start2) + "毫秒");
}
}
使用Lambda表达式作为方法参数
当一个方法的参数是一个函数式接口时, 可以使用Lambda表达式传递该参数, 简化匿名内部类的代码
5分钟练习: 函数式接口作为方法参数需求:
定义方法, 用来快速开启线程执行一个任务: public static void startThread(Runnable r)
方法内部创建一个线程对象, 并将任务传递, 开启线程: new Thread(r).start();
在main方法中调用startThread()方法, 使用Lambda表达式方式, 传入一个任务, 任务中打印线程名+"线程启动了"
代码:
[Java] 纯文本查看 复制代码 public class Test {
public static void main(String[] args) {
// 匿名内部类方式
startThread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->线程启动了");
}
});
// Lambda表达式方式: 标准格式
startThread(()->{
System.out.println(Thread.currentThread().getName() + "-->线程启动了");
});
// Lambda表达式方式: 省略格式
startThread(()->System.out.println(Thread.currentThread().getName() + "-->线程启动了"));
}
public static void startThread(Runnable r) {
new Thread(r).start();
}
}
使用Lambda表达式作为方法返回值
当一个方法的返回值是一个函数式接口时, 可以返回Lambda表达式, 简化匿名内部类的代码
5分钟练习: 使用Lambda表达式作为返回值需求:
定义方法, 用于获取一个比较器对象: public static Comparator<String> getComparator()
return (String s1, String s2)->{...}}
方法内部使用Lambda表达式作为Comparator接口的返回值, 比较规则是:
第二个字符串参数的长度 - 第一个字符串参数的长度
在main方法中
定义字符串数组: {"aaa", "b", "cccccc", "ddddddddddd"}
调用 getComparator() 方法获取比较器对象
调用 Arrays.sort(数组, 比较器) 方法, 将字符串数组排序, 然后打印出来
代码:
[Java] 纯文本查看 复制代码 public class Test {
// 返回一个按照字符串长度降序的比较器对象
private static Comparator<String> getComparator() {
// 匿名内部类方式
/*return new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.length() - o1.length();
}
};*/
// Lambda表达式: 标准格式
/*return (String o1, String o2)->{
return o2.length() - o1.length();
};*/
// Lambda表达式: 省略格式
return (o1, o2)->o2.length() - o1.length();
}
public static void main(String[] args) {
String[] arr = {"aaa", "b", "cccccc", "ddddddddddd"};
System.out.println("排序前:" + Arrays.toString(arr)); // 排序前:[aaa, b, cccccc, ddddddddddd]
Arrays.sort(arr, getComparator());
System.out.println("排序后:" + Arrays.toString(arr)); // 排序后:[ddddddddddd, cccccc, aaa, b]
}
}
常用函数式接口Supplier生产型函数式接口
java.util.function.Supplier<T>函数式接口: 生产型函数式接口
// 抽象方法
T get(): 用于获取一个对象或值. 至于获取什么值, 怎么获取, 需要我们根据应用场景编写Lambda来实现
[Java] 纯文本查看 复制代码 public class Test {
public static void main(String[] args) {
// 调用方法
String s = getString( ()->"赵丽颖" );
System.out.println(s); // 赵丽颖
}
// 定义方法: 用来获取一个字符串 参数: 获取字符串的方式(代码)
public static String getString(Supplier<String> sup) {
return sup.get();
}
}
Supplier生产型函数式接口练习: 求数组最大值5分钟练习: 使用Supplier接口求数组最大值
需求:
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值
定义测试类:
定义方法, 用于获取数组中的最大值: public static int getMax(Supplier<Integer> sup)
方法内部直接调用sup对象的 get() 方法获取得到的结果, 并return
定义main方法:
方法中定义数组: {100, 0, -50, 88, 99, 33, -30}
调用 getMax() 方法, 方法内部传递Supplier接口的Lambda表达式
表达式中编写获取数组最大值的代码
代码:
[Java] 纯文本查看 复制代码 public class Test {
// 定义方法, 用于获取数组中的最大值, 传递的参数是: 获取数组最大值的实现代码
private static int getMax(Supplier<Integer> supplier) {
return supplier.get();
}
public static void main(String[] args) {
// 定义数组
int[] arr = {100, 0, -50, 88, 99, 33, -30};
// 调用方法, 传递获取数组最大值的实现代码
int result = getMax(()->{
int max = arr[0];
for (int i : arr) {
if (i > max) {
max = i;
}
}
return max;
});
System.out.println(result); // 100
// 使用函数式接口传递Lambda表达式的好处是: 可以省去定义方法, 直接将方法实现的代码传递执行
// 比如下面就传递了另一种获取最大值的代码
result = getMax(()->{
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
});
System.out.println(result); // 100
}
}
Consumer消费型函数式接口
java.lang.StringBuilder类: 可变字符序列, 类似于String, 线程不安全效率高 "abc" -> "cba"
StringBuilder reverse(): 将StringBuilder内部保存的内容反转
String toString(): 转换为String new StringBuilder("abc").reverse().toString() "cba"
java.lang.StringBuffer类: 可变字符序列, 类似于String, 线程安全效率低
StringBuffer reverse(): 将StringBuffer内部保存的内容反转
String toString(): 转换为String
java.util.function.Consumer<T>函数式接口: 消费型函数式接口
// 抽象方法
void accept(T t): 用于消费(使用)一个对象或值. 至于怎么消费, 需要我们根据应用场景编写Lambda来实现
// 默认方法
default Consumer<T> andThen(Consumer<? super T> after): 拼接两个Consumer接口的Lambda对象实现连续操作. 谁写前面, 谁先消费
[Java] 纯文本查看 复制代码 public class Test {
// 定义方法用来消费一个字符串. 参数是 被消费的字符串 和 消费方式
public static void method(String name, Consumer<String> son1){
son.accept(name);
}
public static void main(String[] args) {
String name = "赵丽颖";
// 消费这个字符串, 我们的消费方式是 反转字符串
method(name, (String name)->{
// 反转字符串
String reName = new StringBuilder(name).reverse().toString();
System.out.println(reName); // 颖丽赵
});
}
}
Consumer消费型函数式接口: 默认方法andThen()
java.util.function.Consumer<T>函数式接口: 消费型函数式接口
// 默认方法
default Consumer<T> andThen(Consumer<? super T> after): 拼接两个Consumer接口的Lambda对象实现连续操作. 谁写前面, 谁先消费
[Java] 纯文本查看 复制代码 public class Test {
// 定义方法用来消费一个字符串. 参数是 被消费的字符串 和 消费方式1 和 消费方式2
public static void method(String s, Consumer<String> con1, Consumer<String> con2){
// con1.accept(s);
// con2.accept(s);
// 使用andThen简化
one.andThen(two).accept(s);
}
public static void main(String[] args) {
// 传入两种消费方式
method("Hello",
(t) -> {
System.out.println(t.toUpperCase()); // HELLO
},
(t) -> {
System.out.println(t.toLowerCase()); // hello
});
}
}
Consumer消费型接口练习: 字符串拼接输出5分钟练习: Consumer接口实现字符串拼接输出
需求:
下面的字符串数组当中存有多条信息, 请按照格式"姓名:XX. 性别:XX."格式将信息打印出来.
要求:
将打印姓名的动作, 作为第一个Consumer接口的Lambda实例
将打印性别的动作, 作为第二个Consumer接口的Lambda实例
将两个Consumer接口按照顺序"拼接"到一起
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
}
// 最终效果
姓名:迪丽热巴. 性别:女.
姓名:古力娜扎. 性别:女.
姓名:马尔扎哈. 性别:男.
被消费的字符串s: "迪丽热巴,女"
[Java] 纯文本查看 复制代码 // 消费方式1
String[] split = s.split(",");
String name = split[0];
System.out.print("姓名:"+name)
// 消费方式2
String[] split = s.split(",");
String gender = split[1];
System.out.println(". 性别:"+gender+".")
最终结果: 打印: 姓名:迪丽热巴. 性别:女.
代码:
[Java] 纯文本查看 复制代码 public class Test {
// 定义方法, 用来将数组里面的元素打印为指定格式
// 参数为: 数组, 消费方式1, 消费方式2
private static void printInfo(String[] arr, Consumer<String> con1, Consumer<String> con2) {
// 遍历出数组中的字符串元素, 最终消费的是字符串元素
for (String message : arr) {
// 连接两种消费方式, 然后消费字符串元素
con1.andThen(con2).accept(message);
}
}
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
// 调用方法消费
printInfo(
array,
(message)->{ // 消费方式1: 打印姓名不换行
String name = message.split(",")[0];
System.out.print("姓名:" + name);
},
(message)->{ // 消费方式2: 打印性别换行
String gender = message.split(",")[1];
System.out.println(". 性别:" + gender + ".");
}
);
}
}
Predicate条件判断函数式接口
java.util.function.Predicate<T>函数式接口: 条件接口, 用于判断
// 抽象方法
boolean test(T t): 判断参数传递的对象. 至于怎么判断, 要判断什么, 需要我们编写Lambda表达式来实现
// 默认方法 (用于连接多个判断条件)
default Predicate<T> and(Predicate<? super T> other): 与 &&
default Predicate<T> or(Predicate<? super T> other): 或 ||
default Predicate<T> negate(): 非, 取相反结果 !
[Java] 纯文本查看 复制代码 public class Test {
// 定义方法用来检测字符串. 参数是 被检测的字符串 和 检测方式
private static boolean checkString(String s, Predicate<String> pre) {
return pre.test(s);
}
public static void main(String[] args) {
String s = "abcde";
boolean b = checkString(s, str -> str.length()>5);
System.out.println(b); // false
}
}
Predicate条件判断函数式接口: 默认方法and()
[Java] 纯文本查看 复制代码 public class Test {
// 定义方法用于按照2个条件检测字符串. 参数: 被检测的字符串, 检测条件1, 检测条件2
public static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2){
//return pre1.test(s) && pre2.test(s);
return pre1.and(pre2).test(s);
// 两个条件必须同时满足
}
public static void main(String[] args) {
String s = "abcdef";
boolean b = checkString(
s,
(String str)->{
return str.length()>5; //判断字符串的长度是否大于5
},
(String str)->{
return str.contains("a"); //判断字符串中是否包含a
});
System.out.println(b);
}
}
Predicate条件判断函数式接口: 默认方法or(), negate()default Predicate<T> or(Predicate<? super T> other): 或
default Predicate<T> negate(): 非, 取相反结果
[Java] 纯文本查看 复制代码 // 或
public class Test {
// 定义方法用于检测字符串. 参数 被检测的字符串, 检测方式1, 检测方式2
public static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2){
//return pre1.test(s) || pre2.test(s);
return pre1.or(pre2).test(s);
// 两个条件满足其一即可
}
public static void main(String[] args) {
String s = "bc";
boolean b = checkString(
s,
(String str)->{
return str.length()>5; //判断字符串的长度是否大于5
},
(String str)->{
return str.contains("a"); //判断字符串中是否包含a
});
System.out.println(b); // false
}
}
// 取反
public class Test {
// 定义方法用于检测字符串. 参数: 被检测字符串, 检测方式
public static boolean checkString(String s, Predicate<String> pre){
//return !pre.test(s);
return pre.negate().test(s);
// 返回相反的结果
}
public static void main(String[] args) {
String s = "abc";
boolean b = checkString(
s,
(String str)->{
return str.length()>5; //判断字符串的长度是否大于5,并返回结果
});
System.out.println(b); // true
}
}
为什么需要将逻辑操作封装为方法? [Java] 纯文本查看 复制代码 return rule1.and(rule2) // rule1 && rule2
.or(rule3) // || rule3
.negate().or(rule4); // || !rule4
Predicate条件判断函数式接口练习: 集合信息筛选5分钟练习: 使用Predicate实现集合信息筛选
需求:
数组当中有多条"姓名+性别"的信息如下, 请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中, 需要同时满足两个条件:
1. 必须为女生
2. 姓名为4个字
[Java] 纯文本查看 复制代码 public class DemoPredicate {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
}
}
代码:
[Java] 纯文本查看 复制代码 public class Test {
// 定义方法, 将字符串数组中满足条件的元素过滤到ArrayList集合当中
private static ArrayList<String> filter(String[] arr, Predicate<String> pre1, Predicate<String> pre2) {
// 创建一个空数组, 用于保存过滤到的元素
ArrayList<String> list = new ArrayList<>();
// 遍历数组
for (String s : arr) {
// 对每个元素进行条件判断
boolean b = pre1.and(pre2).test(s);
// 符合要求的添加到集合
if (b) {
list.add(s);
}
}
// 返回集合
return list;
}
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
ArrayList<String> list = filter(
array,
(String s)->{ // 判断条件1: 必须为女生
return s.split(",")[1].equals("女");
},
(String s)->{ // 判断条件2: 姓名为4个字
return s.split(",")[0].length() == 4;
}
);
// 打印结果
for (String s : list) {
System.out.println(s);
}
/*
迪丽热巴,女
古力娜扎,女
*/
}
}
Function转换型函数式接口
函数 y = f(x)
java.util.function.Function<T,R>: 根据一个 T类型的数据 转换为 另一个R类型的数据
T称为前置条件, 也就是输入(input)的类型
R称为后置条件, 也就是返回结果(result)的类型
有进有出, 所以称为"函数Function"
// 抽象方法
R apply(T t): 将T转换为R
// 默认方法
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after): 拼接多个Function转换
[Java] 纯文本查看 复制代码 public class Test {
// 定义方法用于将String转换为Integer. 参数: 被转换的字符串, 将String转换为Integer的方式
public static void change(String s, Function<String,Integer> fun){
//Integer in = fun.apply(s);
int in = fun.apply(s);
System.out.println(in);
}
public static void main(String[] args) {
String s = "1234";
change(
s,
(String str)->{
return Integer.parseInt(str); //把字符串类型的整数,转换为Integer类型的整数返回
});
//优化Lambda
change(s, str->Integer.parseInt(str));
}
}
Function转换型函数式接口: 默认方法andThen()
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after): 连接多种转换操作, 先做什么, 再做什么
[Java] 纯文本查看 复制代码 public class Test {
// 定义方法用于将String转换为Integer, 然后再将Integer转换为String
// 参数: 被转换的字符串, String转Integer的方式, Integer转String的方式
public static void change(String s, Function<String,Integer> fun1, Function<Integer,String> fun2){
String ss = fun1.andThen(fun2).apply(s);
System.out.println(ss);
}
public static void main(String[] args) {
String s = "123";
change(
s,
(String str)->{
return Integer.parseInt(str)+10; //把字符串转换为整数+10
},
(Integer i)->{
return i+""; //把整数转换为字符串
});
//优化Lambda表达式
change(
s,
str->Integer.parseInt(str)+10,
i->i+""
);
}
}
Function转换型函数式接口: 使用Function连续转换5分钟练习: 使用Function连续转换需求:
请使用Function进行函数模型的拼接, 按照顺序需要执行的多个函数操作为:
String str = "赵丽颖,20";
1. 将字符串截取数字年龄部分, 得到字符串 String->String
2. 将上一步的字符串转换成为int类型的数字 String->Integer
3. 将上一步的int数字累加100, 得到结果int数字 Integer->Integer
代码:
[Java] 纯文本查看 复制代码 public class Test {
// 定义方法, 将字符串转换为要求的int值
private static int change(String s, Function<String, String> fun1,
Function<String, Integer> fun2, Function<Integer, Integer> fun3) {
// 连续转换, 并返回结果
return fun1.andThen(fun2).andThen(fun3).apply(s);
}
public static void main(String[] args) {
String str = "赵丽颖,20";
/*int result = change(
str,
(String s)->{ // 将字符串截取数字年龄部分
return s.split(",")[1];
},
(String s)->{ // 将上一步的字符串转换成为int类型的数字
return Integer.parseInt(s);
},
(Integer i)->{ // 将上一步的int数字累加100, 得到结果int数字
return i+100;
}
);*/
// 省略格式
int result = change(
str,
s->s.split(",")[1], // 将字符串截取数字年龄部分
s->Integer.parseInt(s), // 将上一步的字符串转换成为int类型的数字
i->i+100 // 将上一步的int数字累加100, 得到结果int数字
);
System.out.println(result); // 120
}
}
今日API
java.util.function.Supplier<T>函数式接口: 生产型函数式接口
// 抽象方法
T get(): 用于获取一个对象或值. 至于获取什么值, 怎么获取, 需要我们根据应用场景编写Lambda来实现
java.util.function.Consumer<T>函数式接口: 消费型函数式接口
// 抽象方法
void accept(T t): 用于消费(使用)一个对象或值. 至于怎么消费, 需要我们根据应用场景编写Lambda来实现
// 默认方法
default Consumer<T> andThen(Consumer<? super T> after): 拼接两个Consumer接口的Lambda对象实现连续操作. 谁写前面, 谁先消费
java.util.function.Predicate<T>函数式接口: 条件接口, 用于判断
// 抽象方法
boolean test(T t): 判断参数传递的对象. 至于怎么判断, 要判断什么, 需要我们编写Lambda表达式来实现
// 默认方法 (用于连接多个判断条件)
default Predicate<T> and(Predicate<? super T> other): 与
default Predicate<T> or(Predicate<? super T> other): 或
default Predicate<T> negate(): 非, 取相反结果
java.util.function.Function<T,R>: 根据一个T类型的数据得到另一个R类型的数据
T称为前置条件, 也就是输入(input)的类型
R称为后置条件, 也就是返回结果(result)的类型
有进有出, 所以称为"函数Function"
// 抽象方法
R apply(T t): 将T转换为R
// 默认方法
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after): 拼接多个Function转换
|
|