函数式接口
Lambda表达式需要接口的支持,并且接口的抽象方法还只能有一个,要么没法区分实现的是哪一个抽象方法了。因此Lambda表达式需要函数式接口的支持。
什么是函数式接口
接口中只有一个抽象方法的接口称为函数式接口。
函数式接口可以使用一个注解@FunctionalInterface修饰,此注解可以检查是否是函数式接口
函数式接口的使用
假设我们现在有一个需求:对一个数进行运算,什么运算都可以。如果我们想用Lambda表达式来实现的话,我们就需要一个接口来支持,下面我们先写一个MyFunction接口
@FunctionalInterface
public interface MyFunction {
public Integer getValue(Integer num);
}
然后我们需要一个运算的方法
//运算方法
public Integer operation(Integer num, MyFunction fun){
return fun.getValue(num);
}
测试类
@Test
public void test06(){
//平方运算
Integer num = operation(10,(x) -> x*x);
System.out.println(num);
//加和运算
Integer num2 = operation(100,(y) -> y+200);
System.out.println(num2);
}
执行结果
100
300
不管是什么运算,我们只需要关注Lambda表达式的方法体如何实现运算就可以了。
Java8内置的四大核心函数式接口
接口 | 参数 | 返回 | 中文 | 实例 |
---|---|---|---|---|
Supplier | None | T | 提供者 | 工厂方法创建对象 |
Consumer | T | None | 消费者 | 消费一个数据,无返回 |
Predicate | T | boolean | 判断/谓词 | 对指定条件进行测试 |
Function | T | R | 函数 | 根据一个参数得到另一个参数值 |
一、Supplier 接口
Supplier 接口代表一个结果的提供者。
Supplier 接口是用来生成数据的,数据的类型通过泛型参数给定,使用 get()方法获得返回值。
接口的源码如下:
@FunctionalInterface
public interface Supplier {
/**
* 得到一个结果,返回 T 对象
*
* @return a result
*/
T get();
}
Supplier 接口中的方法
T get() 获得指定类型的数据
1.1、将 Supplier 直接使用
案例需求:get()方法的基本使用,在 Supplier 接口中 get 方法返回一个字符串。
案例步骤:
- 使用 Lambda 创建 Supplier 对象,泛型类型为 String,方法体返回一个字符串,return 可以省略。
- 调用 Supplier 的 get()方法得到字符串,并打印输出
public class LambdaTest {
public static void main(String[] args) {
//使用 Lambda 创建 Supplier 对象
Supplier supplier = () -> "Hello Java!";
//输出它的值
System.out.println(supplier.get());
}
}
// 输出
// Hello Java!
1.2、将 Supplier 使用生成对象
案例需求:
下面的例子演示了如何通过调用一个静态方法,生成一个员工对象返回。使用构造方法做为 Supplier 参数的引用。
案例步骤:
- 在主类内部创建一个私有的静态 Employee 对象,重写 toString()方法,返回一个字符串:"我是员工"。
- 在 main 函数中创建一个 Supplier 对象,泛型类型是 Employee。使用 Lambda 传入 Supplier 对象,方法体实例化员工对象,省略 return 方法。
- 使用 supplier 对象的 get()方法得到员工对象
- 打印输出员工对象
因为 Employee 对象是私有的,外部类无法直接实例化员工对象。
调用 Supplier 的 get()方法来生成员工对象,这样做的目的是可以控制员工对象的生成方式,类似于工厂模式。
public class LambdaTest {
public static void main(String[] args) {
//使用 Lambda 传入 Supplier 对象,将生成一个员工对象
//此时仅仅是实例化了接口并未执行里面代码
Supplier supplier = ()->new Employee();
//输出员工对象
System.out.println(supplier.get());
}
//员工类
private static class Employee {//注意static
@Override
public String toString() {
return "我是员工";
}
}
}
// 输出
// 我是员工
1.3、将 Supplier 做为方法的参数
需求说明:求数组中的最大值,使用 Supplier 接口作为方法参数类型,通过 Lambda 表达式求出 int 数组中的最大值。
需求分析:
- 定义整型数组 int[] arr = {12,68,10,2,99,313,46};
- 创建静态方法 getMax():返回 int 类型,将 Supplier 做为参数,泛型类型为 Integer,方法体调用 get()方法返回值。
- 在 main 函数中调用 getMax()方法,使用 Lambda 传入 Supplier 对象,并且实现查找最大值的功能。
- 在 Lambda 表达式相当于方法体:遍历每个元素,比较大小,找出最大值。
public class LambdaTest {
public static void main(String[] args) {
int[] arr = {12, 68, 10, 2, 99, 313, 46};
// 调用 getMax 方法获得最大值,Lambda 相当于方法体
int num = getMax(() -> {
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (max < arr[i]) {
max = arr[i];
}
}
return max;
});
//输出最大值
System.out.println("最大值是:" + num);
}
//使用 Supplier 做为参数
public static int getMax(Supplier supplier) {
return supplier.get();
}
}
// 输出
// 最大值是:313
二、Consumer 接口
Consumer 接口代表接受单一的输入变量而且没有返回值的一类操作。 它的作用和 Supplier 相反,是消费一个数据的,消费的数据类型需要通过泛型指定。
源代码如下:
@FunctionalInterface
public interface Consumer {
/**
* 接受 t 对象,无返回值
*/
void accept(T t);
/**
* 默认的组合方法,参数和返回值都是 Consumer 类型
* 先调用自己的 accept()方法,再调用参数的 accept()方法
*/
default Consumer andThen(Consumer super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
Consumer 接口中的方法
void accept(T t) 接受对给定的参数进行操作。
Consumer 接口中的默认方法:
default Consumer
andThen(Consumer after)
如果一个方法的参数和返回值全都是 Consumer
消费一个数据的时候,首先做一个操作,然后再做另一个操作,两个操作依次执行,实现一种组合操作。而这个方法就是 Consumer 接口中的默认方法 andThen。
2.1、直接使用 Consumer 对象
实现步骤:
- 使用 Lambda 创建 Consumer 对象,直接打印传入的字符串数据。
- 调用 Consumer 的 accept()方法,在 accept()方法中传入一个字符串数据。
public class LambdaTest {
public static void main(String[] args) {
//创建 Consumer 对象,打印传入的变量 t
Consumer consumer = t -> System.out.println(t);
//调用 Consumer 中的方法
consumer.accept("Hello Lambda");
}
}
// 输出
// Hello Lambda
2.2、Consumer 做为参数
List 和 Set 集合中遍历的 forEach 方法它的参数就是 Consumer,请看下面的代码:
案例需求:
- 创建一个数组,使用 Arrays.asList("孙悟空", "猪八戒", "白骨精", "嫦娥") 转成 List 对象。
- 使用 forEach 方法打印每一个元素,forEach 中使用 Lamba 表达式输出传入的字符串
public class LambdaTest {
public static void main(String[] args) {
//将数组转成 List 对象
List names = Arrays.asList("孙悟空", "猪八戒", "白骨精", "嫦娥");
//打印每一个字符串,forEach 的参数就是 Consumer
names.forEach(t -> System.out.println(t));
}
}
// 输出
//孙悟空
//猪八戒
//白骨精
//嫦娥
分析 forEach()方法的源代码
default void forEach(Consumer super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
这是定义在 java.lang.Iterable 接口中的默认方法,参数就是 Consumer 对象,方法体内对当前集合使用 for遍历,this 就是集合对象。每次对一个元素调用 accept()方法。而我们外部调用的代码中对 accept()方法进行了实现,输出了每个元素。
public static T requireNonNull(T obj) 静态方法,JDK7 中新增的方法,判断传入的对象是否为 NULL,如果是 NULL 则抛出异常,不为 NULL 则返回对象本身。常用于方法或构造方法中传入对象参数的校验。
default Consumer andThen(Consumer super T> after) {
//判断 after 是否为 null
Objects.requireNonNull(after);
//先调用自己的 accept()方法,再调用参数的 accept()方法
return (T t) -> { accept(t); after.accept(t); };
}
要想实现组合,需要两个或多个 Lambda 表达式,而 andThen 的语义正是执行“一步接一步”操作。
案例需求:将字符串 Hello 首先打印大写的 HELLO,然后打印小写的 hello
实现步骤:
- 创建 Consumer 对象 c1,使用 Lambda 打印 s 对象的大写
- 创建 Consumer 对象 c2,使用 Lambda 打印 s 对象的小写
- c1 调用 andThen(c2)方法,再调用 accept("字符串"),完成依次的操作。
public class LambdaTest {
public static void main(String[] args) {
//打印大写
Consumer c1 = s -> System.out.println(s.toUpperCase());
//打印小写
Consumer c2 = s-> System.out.println(s.toLowerCase());
//调用方法
c1.andThen(c2).accept("Hello Consumer");
}
}
// 输出
// HELLO CONSUMER
// hello consumer
2.3、使用 Consumer 做为参数
需求说明:
格式化打印信息,下面的字符串数组当中存有多条信息,请按照格式“姓名:XX。性别:XX。”的格式将信息打印出来。要求将打印姓名的动作作为第一个 Consumer 接口的 Lambda 实例,将打印性别的动作作为第二个Consumer 接口的 Lambda 实例,将两个 Consumer 接口按照顺序“拼接”到一起。以下数组共 5 个元素,每个元素包含 2 项信息用逗号分隔。
String[] arr = { "张飞,男", "貂蝉,女", "曹操,男","孙尚香,女","小乔,女" };
实现步骤
- 创建静态方法 printInfo(),有 3 个参数,第 1 个是需要打印的字符串数组,第 2 个是 Consumer用于打印姓名 name,第 3 个是 Consumer用于打印性别 gender。
- 在 printInfo 方法中遍历数组中每个元素,再调用 name.andThen(gender).accept(单个元素)
- 每调用一次 andThen()方法,在下面输出一行横线
- 在 main 函数中创建上面要遍历的数组
- 调用 printInfo 方法,传入 3 个参数,第 1 个参数是数组,第 2 个参数使用 Lambda 打印姓名,参数 s 表示数组中的每个元素。第 3 个参数使用 Lambda 打印性别。
public class LambdaTest {
public static void main(String[] args) {
String[] arr = {"张飞,男", "貂蝉,女", "曹操,男","孙尚香,女"};
//这里的 s 表示数组中的每个元素
printInfo(arr,
s ->System.out.println("姓名:" + s.split(",")[0]),
s ->System.out.println("性别:" + s.split(",")[1]));
}
public static void printInfo(String[] arr, Consumer name, Consumer gender) {
for (String s : arr) {
name.andThen(gender).accept(s);
System.out.println("------------------");
}
}
}
三、Predicate 接口
Predicate 中文意思为谓语,"我是一个程序员","是"或"不是"就是谓语。
它代表只有一个变量的函数,返回 boolean 类型。有时候我们需要进行某种判断,从而得到一个 boolean 值的结果。可以使用 java.util.function.Predicate接口。
@FunctionalInterface
public interface Predicate {
/**
* 抽象方法,对 t 进行测试,返回 boolean 类型
*/
boolean test(T t);
/**
* 组合方法,将当前的谓语与另一个谓语进行短路的与操作,返回一个谓语对象
*/
default Predicate and(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/**
* 对当前的谓语进行逻辑非操作,返回一个谓语对象
*/
default Predicate negate() {
return (t) -> !test(t);
}
/**
* 组合方法,将当前的谓语与另一个谓语进行短路的或操作,返回一个谓语对象
*/
default Predicate or(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
/**
* 静态方法,判断 test(object)方法传入的对象是否与参数 targetRef 对象相等
*/
static Predicate isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
Predicate 接口中的方法
boolean test(T t) 对 t 进行指定条件的测试,返回 boolean 类型
3.1、test()方法演示
案例需求:判断 test("字符串")方法给定的参数长度是否大于 5
案例步骤:
- 创建一个 Predicate 谓语对象,使用 Lambda 实现 boolean test(T t)方法
- 方法体的参数是 s,返回字符串的长度大于 5,省略 return 关键字。
- 两次调用 test()方法看运行结果,第 1 次使用字符串 Hello,第 2 次使用字符串 Predicate
public class LambdaTest {
public static void main(String[] args) {
//创建一个 Predicate 谓语对象,boolean test(T t)方法接收字符串类型,返回 boolean 类型
Predicate predicate = s -> s.length() > 5;
//两次调用 test 方法看运行结果
System.out.println("Hello 的长度是否大于 5:" + predicate.test("Hello"));
System.out.println("Predicate 的长度是否大于 5:" + predicate.test("Predicate"));
}
}
// 执行结果
// Hello 的长度是否大于 5:false
// Predicate 的长度是否大于 5:true
3.2、默认方法 and()
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。 其中将两个 Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用 default 方法 and。 这个默认方法接收一个 Predicate 参数,返回一个 Predicate参数。
其 JDK 源码为:
/**
* 组合方法,将当前的谓语与另一个谓语进行短路的与操作,返回一个谓语对象
*/
default Predicate and(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
and 方法演示示例:
案例需求:判断一个字符串是否包含指定的字符串:既包含大写“H”,又要包含大写“W”
案例步骤:
- 创建 2 个需要判断的字符串:s1="Hello world"和 s2="Hello World"
- 使用 Lambda 表达式,创建两个 Predicate 对象
- 判断字符串 s 是否包含 H
- 判断字符串 s 是否包含 W
- 调用 and 方法和 test 方法,分别输出 s1 和 s2 的结果
public class LambdaTest {
public static void main(String[] args) {
//创建 2 个需要判断的字符串
String s1 = "Hello world";
String s2 = "Hello World";
// 使用 Lambda 表达式,创建两个 Predicate 对象
//判断 s 是否包含 H
Predicate p1 = s -> s.contains("H");
//判断 s 是否包含 W
Predicate p2 = s -> s.contains("W");
//调用 and 方法
System.out.println(s1 + "是否包含 H 和 W:" + p1.and(p2).test(s1));
System.out.println(s2 + "是否包含 H 和 W:" + p1.and(p2).test(s2));
}
}
// 输出结果
// Hello world是否包含 H 和 W:false
// Hello World是否包含 H 和 W:true
3.3、默认方法 or()
与 and 的“与”类似,默认方法 or 实现逻辑关系中的“或”操作。 这个默认方法接收一个 Predicate 参数,返回一个 Predicate 参数。
JDK 源码为:
/**
* 组合方法,将当前的谓语与另一个谓语进行短路的或操作,返回一个谓语对象
*/
default Predicate or(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
or 方法演示示例:
案例需求:判断一个字符串的长度大于 10 或者小于 5
案例步骤:
- 创建三个字符串 s1,s2,s3 内容如下图
- 使用 Lambda 创建 2 个 Predicate 接口对象,第 1 个判断长度是否大于 10,每 2 个判断长度是否小于 5
- 调用 or 和 test 方法输出每个字符串的测试结果
public class LambdaTest {
public static void main(String[] args) {
//创建三个字符串
String s1 = "Hello World"; //大于 10
String s2 = "Java"; //小于 5
String s3 = "I am boy"; //既不大于 10,又不小于 5
//使用 Lambda 创建 2 个 Predicate 接口对象
Predicate p1 = s -> s.length() > 10;
Predicate p2 = s -> s.length() < 5;
//输出每个字符串的测试结果
System.out.println(s1 + "=" + p1.or(p2).test(s1));
System.out.println(s2 + "=" + p1.or(p2).test(s2));
System.out.println(s3 + "=" + p1.or(p2).test(s3));
}
}
// 输出结果
// Hello World=true
// Java=true
// I am boy=false
3.4、默认方法 negate()
“与”、“或”已经了解了,剩下的“非”(取反)也会简单。方法没有参数,返回值为 Predicate。
默认方法 negate的 JDK 源代码为:
/**
* 对当前的谓语进行逻辑非操作,返回一个谓语对象
*/
default Predicate negate() {
return (t) -> !test(t);
}
从实现中很容易看出,它是执行了 test 方法之后,对结果 boolean 值进行“!”取反而已。
要在 test 方法调用之前调用 negate 方法,正如 and 和 or 方法一样。
案例需求:判断年龄是否小于 18 岁,将判断的结果取反。
案例步骤
- 创建 2 个整数类型的年龄,一个 25,一个 15 岁。
- 使用 Lambda 创建 1 个 Predicate,判断年龄小于 18 岁。
- 使用 nagate()取反以后再调用 test()方法,输出两个年龄的结果
public class LambdaTest {
public static void main(String[] args) {
int age1 = 25; //25 岁
int age2 = 15; //15 岁
Predicate predicate = (a) -> a < 18; //判断是否小于 18 岁
System.out.println(age1 + "小于 18 岁,取反:" + predicate.negate().test(age1));
System.out.println(age2 + "小于 18 岁,取反:" + predicate.negate().test(age2));
}
}
// 执行结果
// 25小于 18 岁,取反:true
// 15小于 18 岁,取反:false
3.5、静态方法 isEqual ()
Predicate 中唯一的静态方法,方法的参数是两个 Object 类型,返回一个 Predicate 类型。
作用:根据 Objects.equals(Object, Object)方法比较两个参数是否相等,
一个对象通过 isEqual()传入,另一个对象通过 test()传入。
// java.util.Objects 类中的方法 说明
public static boolean equals(Object a,Object b)
作用:用于比较两个对象是否相等
参数:a 和 b 是要比较的两个对象
返回:如果两个对象相等,则返回 true,否则返回 false
JDK 源代码为:
/**
* 静态方法,判断 test(object)方法传入的对象是否与参数 targetRef 对象相等
*/
static Predicate isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
案例需求:比较两个字符串是否相等
案例步骤:
- 通过静态方法 isEqual("newboy"),直接返回 Predicate 对象
- 调用 Predicate 中的 test()方法传入另两个字符串分别比较
public class LambdaTest {
public static void main(String[] args) {
//通过静态方法直接返回 Predicate 对象
Predicate predicate = Predicate.isEqual("newboy");
//调用 test()方法传入另两个字符串分别比较
System.out.println("两个字符串是否相等:" + predicate.test("newboy"));
System.out.println("两个字符串是否相等:" + predicate.test("NewBoy"));
}
}
// 执行结果
// 两个字符串是否相等:true
// 两个字符串是否相等:false
3.6、Predicate 的应用示例
需求说明:
集合当中有多条“姓名+性别”的信息如下:"张飞,男", "貂蝉,女", "曹操,男","孙尚香,女","小乔,女",请通过 Predicate 接口的 and 组合方法,将符合要求的字符串筛选到集合 ArrayList 中,需要同时满足两个条件:
- 必须为女生
- 姓名为两个字
开发步骤:
- 创建第 1 个 Predicate 判断条件:使用逗号分隔的第 0 个元素姓名长度是 2
- 创建第 2 个 Predicate 判断条件:使用逗号分隔的第 1 个元素性别等于女
- 创建一个新的 List 集合,用于存储过滤以后符合条件的字符串
- 使用 List 中的 forEach(Lambda)遍历上面的原始 List 集合,使用 Predicate 中的 and 和 test 方法判断每个元素
- 两个条件都为真才添加到新的 List 集合中
- 创建第 1 个 Consumer 接口,输出使用逗号分隔的第 0 个元素姓名
- 创建第 2 个 Consumer 接口,输出使用逗号分隔的第 1 个元素性别
- 使用 List 中的 forEach(Lambda)遍历,输出过滤后的新的集合
- 使用 Consumer 接口中的 andThen 和 accept 方法,输出每一个元素
public class LambdaTest {
public static void main(String[] args) {
//从数组中创建一个 List 集合
List list = Arrays.asList("张飞,男", "貂蝉,女", "曹操,男","孙尚香,女","小乔,女");
//创建第 1 个 Predicate 判断条件:使用逗号分隔的第 0 个元素姓名长度是 2
Predicate pname = s -> s.split(",")[0].length() ==2;
//创建第 2 个 Predicate 判断条件:使用逗号分隔的第 1 个元素性别等于女
Predicate pgender = s-> s.split(",")[1].equals("女");
//创建一个新的 List 集合
List infos = new ArrayList<>();
//使用 Lamba 中的 forEach()遍历上面的 List 集合
//使用 Predicate 中的 and 和 test 方法判断每个元素
list.forEach(s -> {
//两个都为真才添加到集合中
if (pname.and(pgender).test(s)) {
infos.add(s);
}
});
//创建第 1 个 Consumer 接口,输出使用逗号分隔的第 0 个元素姓名
Consumer cname = s -> System.out.println("姓名:" + s.split(",")[0]);
//创建第 2 个 Consumer 接口,输出使用逗号分隔的第 1 个元素性别
Consumer cgender = s -> System.out.println("性别:" + s.split(",")[1]);
//使用 Lamba 中的 forEach()遍历,输出过滤后的集合
infos.forEach(s -> {
//使用 Consumer 接口中的 andThen 和 accept 方法,每输出一个元素隔一条线
cname.andThen(cgender).accept(s);
System.out.println("---------------");
});
}
}
// 输出结果
姓名:貂蝉
性别:女
---------------
姓名:小乔
性别:女
---------------
四、Function 接口
Function接口:
- 根据一个参数得到另一个参数值,前面称为计算的参数,后面称为计算的结果。
- 有进有出,所以称为“函数 Function”。
类似于数学中的函数,通过一个变量求出另一个变量的值。如:f(x) = 2x+3
以下是它的 Java 源代码:
// 代表通过一个变量求出另一个变量的结果的函数
@FunctionalInterface
public interface Function {
/**
* 对给定的变量 t 进行计算,得到返回的结果 R
*/
R apply(T t);
/**
* 默认组合方法,先计算当前函数,再计算传入的函数
*/
default Function compose(Function super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* 默认组合方法,先计算传入的函数,再计算当前函数
*/
default Function andThen(Function super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* 静态方法:总是返回它的输入变量
*/
static Function identity() {
return t -> t;
}
}
4.1、抽象方法:apply()
是java.util.function.Function 接口中的方法
R apply(T t); 对给定的变量 t 进行计算,得到返回的结果 R
apply 方法演示示例:
案例需求:将 Integer 类型转换为 String 类型,并且输出转换以后字符串的长度。
- 创建一个 Function 对象,输入类型是整数,输出类型是字符串
- Lambda 表达式将一个整数 i 转成字符串
- 调用 apply(数字)方法得到转换后的字符串,再调用字符串的 length()方法得到长度,打印输出
- 第 1 次转换 99 这个数字,第 2 次转换 1000 这个数字
public class LambdaTest {
public static void main(String[] args) {
//创建一个 Function 对象
Function converter = i -> Integer.toString(i);
System.out.println("99 转成字符串的长度是:" + converter.apply(99).length());
System.out.println("1000 转成字符串的长度是:" + converter.apply(1000).length());
}
}
// 输出结果
99 转成字符串的长度是:2
1000 转成字符串的长度是:4
4.2、默认方法:andThen()
Function 接口中有一个默认的 andThen 方法,用来进行组合操作。
先计算当前函数,再计算传入的函数。两个函数依次执行。
andThen 方法的参数是 Function 对象,返回一个 Function 对象。
JDK 源代码:
/**
* 默认组合方法,先计算当前函数,再计算传入的函数
*/
default Function andThen(Function super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
案例需求:
连续进行两个操作:第 1 个操作是将字符串转换成为 int 数字,第 2 个操作将转换好的数字乘以 10。两个操作按照前后顺序组合到一起。
- 让用户从键盘输入 1 个数字,使用字符串接收。
- 创建第 1 个 Function 函数将字符串转成整数
- 创建第 2 个函数将整数乘以 10 返回
- 调用 andThen 方法和 apply,并且输出结果
public class LambdaTest {
public static void main(String[] args) {
//用户输入一个字符串
System.out.println("请输入数字:");
Scanner input = new Scanner(System.in);
String str = input.nextLine();
//第 1 个函数将字符串转成整数
Function f1 = s -> Integer.parseInt(s);
//第 2 个函数将整数乘以 10 返回
Function f2 = i -> i * 10;
//调用 andThen 方法,并且输出结果
System.out.println("转成整数并乘以 10 以后的结果是:" + f1.andThen(f2).apply(str));
}
}
// 输出结果
请输入数字:
2
转成整数并乘以 10 以后的结果是:20
4.3、默认方法:compose()
Function 中有一个与 andThen 非常类似的 compose 方法。
中文是"组成"的意思,方法参数是 Function,返回值是 Function,先运行参数的 apply 方法,再调用自己的 apply 方法。
其 JDK 源代码为:
/**
* 默认组合方法,先计算传入的函数,再计算当前函数
*/
default Function compose(Function super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
// 结合 andThen 方法的 JDK 源码实现进行对比,会发现 compose 方法的参数 Lamda 将会先执行。
所以二者只是先后顺序的不同而已。
compose 方法的演示
案例需求:
创建两个函数对象:1 个将字符串转成大写,1 个将字符串转成小写。分别使用 andThen 和 compose 方法组合调用,查看不同的计算结果。
开发步骤:
- 创建第 1 个 Function,输入输出都是 String 类型,将字符串转成大写。
- 创建第 2 个 Function,输入输出都是 String 类型,将字符串转成小写。
- 调用第 1 个函数的 apply 方法,并且输出值
- 调用第 2 个函数的 apply 方法,并且输出值
- 调用 andThen 方法和 apply 方法查看运行结果
- 调用 compose 方法和 apply 方法查看运行结果
public class LambdaTest {
public static void main(String[] args) {
Function f1 = s -> s.toUpperCase();
Function f2 = s -> s.toLowerCase();
System.out.println("转成大写:" + f1.apply("Hello"));
System.out.println("转成小写:" + f2.apply("Hello"));
System.out.println("先转成大写,再转成小写:" + f1.andThen(f2).apply("Hello"));
System.out.println("先转成小写,再转成大写:" + f1.compose(f2).apply("Hello"));
}
}
// 执行结果
转成大写:HELLO
转成小写:hello
先转成大写,再转成小写:hello
先转成小写,再转成大写:HELLO
Function 的应用示例
需求说明:请使用 Function 进行函数拼接,按照顺序执行多个函数。
操作依次为:
- 将字符串"赵丽颖,20"截取数字年龄部分,得到字符串;
- 将上一步的字符串转换成为 int 类型的数字;
- 将上一步的 int 数字累加 100,得到结果 int 数字。
开发步骤:
- 创建第 1 个 Function 对象,将字符串 20 取出,返回一个字符串
- 创建第 2 个 Function 对象,将字符串转成整数,返回整数
- 创建第 3 个 Function 对象,将整数加 100,返回计算结果4) 调用 andThen 方法 2 次,apply 方法应用字符串:"赵丽颖,20",输出结果
代码实现
public class LambdaTest {
public static void main(String[] args) {
//创建第 1 个 Function 对象,将字符串 20 取出,返回一个字符串
Function fun1 = s -> s.split(",")[1];
//创建第 2 个 Function 对象,将字符串转成整数,返回整数
Function fun2 = s -> Integer.parseInt(s);
//创建第 3 个 Function 对象,将整数加 100,返回计算结果
Function fun3 = num -> num + 100;
//调用 andThen 方法 2 次,apply 方法应用字符串,输出结果
System.out.println("计算结果:" + fun1.andThen(fun2).andThen(fun3).apply("赵丽颖,20"));
}
}
//输出结果
计算结果:120
五、BinaryOperator 接口
BinaryOperator 表示对两个相同类型的操作数进行操作,产生相同类型的结果。
接口中的方法
@FunctionalInterface
public interface BinaryOperator extends BiFunction {
/**
*
* 返回BinaryOperator返回根据指定的两个元件的较小的Comparator
*
*/
public static BinaryOperator minBy(Comparator super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
/**
*
* 返回一个BinaryOperator ,它根据指定的Comparator返回两个元素中的较大Comparator
*
*/
public static BinaryOperator maxBy(Comparator super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
这个接口中定义了两个静态方法,
BiFunction 是用于定义两个操作符的函数接口。
BiFunction接口中的方法
@FunctionalInterface
public interface BiFunction {
/**
* 传入两个参数 t 和 u 进行函数计算,返回计算的结果。
* 两个参数和返回值都是同一种类型。
*
*/
R apply(T t, U u);
/**
* 返回一个组合函数,首先将该函数应用于其输入,然后将after函数应用于结果。
* 如果任一函数的评估引发异常,则将其转发给组合函数的调用者。
*
*/
default BiFunction andThen(Function super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
5.1、方法的演示:apply()
案例需求:
使用 BinaryOperator 接口的 apply()方法,计算 2 个整数的和,并且输出结果。
案例步骤:
- 创建类 Demo25BinaryOperator
- 创建 BinaryOperator 接口,使用 Lambda 实现方法,方法有两个参数,返回方法的计算结果。
- 调用 apply()方法传入实际的参数,打印计算结果。
案例代码:
public class LambdaTest {
public static void main(String[] args) {
BinaryOperator operator = (m, n) -> m + n;
System.out.println("计算结果是:" + operator.apply(3, 5));
}
}
// 输出结果
计算结果是:8
静态方法
public static BinaryOperator minBy(Comparator comparator)
通过后面的 Comparator 比较器判断,返回两个元素中较小的元素
public static BinaryOperator maxBy(Comparator comparator)
通过后面的 Comparator 比较器判断,返回两个元素中较大的元素
Comparator 接口中的静态方法说明
naturalOrder() 按元素的自然排序的大小进行比较,返回一个 Comparator 对象
reverseOrder() 按元素的倒序大小进行比较,返回一个 Comparator 对象
5.2、BinaryOperator 接口做为方法参数
案例需求:
有如下数组{2,1,3,5},对数组中的每个元素进行替换。替换算法如下:
- 第 0 个元素不变
- 第 0 个+第 1 个元素的结果代替第 1 个元素
- 第 1 个新元素+第 2 个元素的结果代替 2 个
- 第 2 个新元素+第 3 个元素的结果代替第 3 个
- 依次类推,直到所有的元素替换完成为止。
//Arrays 类中的方法说明
void parallelPrefix(T[] array, BinaryOperator op)
作用:对数组中每个元素使用指定的二元操作函数进行替换操作,并行累积
参数 1:要替换的数组
参数 2:指定二元操作函数
案例步骤
- 创建 BinaryOperator对象,指定 2 个数的算法是 m+n
- 创建 Integer 类型的数组:{2,1,3,5}
- 输出操作前的数组
- 调用上面的 parallelPrefix()方法,将 BinaryOperator 做为参数传入
- 输出操作后的数组
- 如果使用不同的算法,则每个元素的替换的结果不同。如:换成两个数相乘。
案例代码
public class LambdaTest {
public static void main(String[] args) {
BinaryOperator operator = (m,n) -> m+n;
Integer [] arr = {2,1,3,5};
System.out.println("操作前的数组:" + Arrays.toString(arr)) ;
Arrays.parallelPrefix(arr,operator);
System.out.println("操作后的数组:" + Arrays.toString(arr)) ;
}
}
//输出结果
操作前的数组:[2, 1, 3, 5]
操作后的数组:[2, 3, 6, 11]
5.3、静态方法的演示
案例需求:
比较两个整数,使用 minBy 静态方法找出最小值比较两个字符串,使用 maxBy 静态方法找出最大值
案例步骤
- 创建 BinaryOperator对象,使用 minBy()静态方法,按数字的正常大小进行比较。
- 输出最小值,调用 apply()方法,传入 2 个整数。
- 创建 BinaryOperator对象,使用 maxBy()静态方法,按字符串的大小进行比较。
- 输出最大值,调用 apply()方法,传入 2 个字符串:"ABCD","xyz"
案例代码
public class LambdaTest {
public static void main(String[] args) {
//naturalOrder()是 Comparator 中的静态方法,即按数字的正常大小进行比较
BinaryOperator oper1 = BinaryOperator.minBy(Comparator.naturalOrder());
System.out.println("最小值是:" + oper1.apply(3,5));
//naturalOrder()是 Comparator 中的静态方法,即按字符串的正常大小进行比较
BinaryOperator oper2 = BinaryOperator.maxBy(Comparator.naturalOrder());
System.out.println("最大值是:" + oper2.apply("ABCD","xyz"));
}
}
// 输出结果
最小值是:3
最大值是:xyz
六、UnaryOperator 接口
UnaryOperator 表示对单个操作数的操作,该操作数生成与其操作数类型相同的结果。
UnaryOperator 接口继承于 Function 接口,
所以有 T apply(T t)抽象方法,与前面的 Function 接口中的 apply()方法相同。
它的输入类型和返回类型是相同的类型。
UnaryOperator 接口的源码
@FunctionalInterface
public interface UnaryOperator extends Function {
/**
* 始终返回其输入参数的一元运算符
*/
static UnaryOperator identity() {
return t -> t;
}
}
方法的演示
UnaryOperator 接口中的方法说明
T apply(T t);
从 Function 接口中继承下来的抽象方法,使用给定的参数应用此一元运算函数,返回另一个值。
参数和返回值是同一种类型。
static UnaryOperator identity()
始终返回其输入参数的一元运算符也就是后续 apply()输入的是什么,就返回什么。
案例步骤
- 使用 UnaryOperator.identity()静态方法创建 UnaryOperator对象
- 应用 apply()方法,输入字符串 abc,得到结果也是 abc。
public class LambdaTest {
public static void main(String[] args) {
//创建一个 UnaryOperator对象,
UnaryOperator operator = UnaryOperator.identity();
//调用 apply()方法,输出参数的值
System.out.println("输出与输入一样:" + operator.apply("abc"));
}
}
//输出结果
输出与输入一样:abc
UnaryOperator 使用方法的参数
案例需求:
有一个整数的列表集合,将集合中每个元素乘以 2,再替换这个元素,输出替换前后的列表集合有一个字符串的列表集合,将集合中每个元素用它的大写进行替换。
ArrayList 中的方法说明
replaceAll(UnaryOperator operator)
使用一元操作函数的结果,替换列表中的每个元素
案例步骤:
- 使用 Arrays.asList()创建一个整数列表
- 创建 UnaryOperator一元运算函数,指定运算表达式是 x*2
- 调用 ArrayList 的 replaceAll()方法,把上面创建的一元运算函数做为参数传入
- 输出替换前后的列表
- 使用 Arrays.asList()创建一个字符串列表
- 这次直接在 replaceAll()方法中传入 Lambda 表达式,s.toUpperCase()
- 输出替换前后的列表
public class LambdaTest {
public static void main(String[] args) {
List nums = Arrays.asList(3, 10, 8, 2);
System.out.println("替换前:" + nums);
UnaryOperator oper = x -> x * 2;
nums.replaceAll(oper);
System.out.println("替换后:" + nums);
List names = Arrays.asList("Jack","Rose","Tom","NewBoy");
System.out.println("替换前:" + names);
names.replaceAll(s -> s.toUpperCase());
System.out.println("替换后:" + names);
}
}
//输出
替换前:[3, 10, 8, 2]
替换后:[6, 20, 16, 4]
替换前:[Jack, Rose, Tom, NewBoy]
替换后:[JACK, ROSE, TOM, NEWBOY]
七、常用的函数式接口小结
1、Supplier 提供数据者
T get();没有传入参数,有结果。
2、Consumer 消费数据者
void accept(T t); 传入数据,没有结果。
andThen()
3、Predicate 谓语
boolean test(T t); 对传入的数据逻辑判断
and()
or()
negate()
isEqual()
4、Function 函数
R apply(T t); 传入一个变量返回计算结果
andThen()
compose()
identity()
5、BinaryOperator 二元操作符
T apply(T t,T u); 传入两个参数返回一个结果
andThen()
继承于 BiFunction
6、UnaryOperator
继承于 Function
一元操作符
T apply(T t); 传入一个参数返回一个结果
andThen()
compose()
identity()
参考:
https://blog.csdn.net/swadian2008/article/details/119780384
https://www.cnblogs.com/qwera/p/14710347.html
https://www.cnblogs.com/cathyqq/p/14493801.html