目录
函数式接口的定义
仅有一个抽象方法的接口,采用@FunctionalInterface注解的接口
lambda表达式
主要是作用在函数式接口上面
()->一个单纯的操作可以啥都不加,分号也可以不用写
(参数传递)->{有多个操作的情况,加分号,有return要return}
其实上面就可以看成是一个匿名内部类的一个简化。
因为就是说,匿名内部类他其实就是借助了父类的类名或者接口名来做这样一个类,必须存在继承或者实现这样一种关系,那么lambda其实就是一种只有一个抽象方法的函数式接口的匿名内部类的实现
先来看一个函数式接口:
FunctionInterface1.java
@FunctionalInterface
public interface FunctionInterface1 {
void say();
}
再来看一个利用了匿名内部类来实现函数式接口和Lambda表达式 实现函数式接口的区别:
Simple1.java
public class Simple1 {
public static void main(String[] args) {
String str1 = "i";
String str2 = "love";
String str3 = "you";
//做一个匿名内部类
new FunctionInterface1(){
@Override
public void say() {
System.out.println(str1 + " " + str2 + " " + str3);//可以调用函数定义的变量
}
}.say();
//还可以带引用变量的匿名内部类
FunctionInterface1 fi = new FunctionInterface1(){
@Override
public void say() {
System.out.println("这是另外一个可以说话的方法");
}
};
fi.say();
//匿名内部类
printStr(new FunctionInterface1() {
@Override
public void say() {
System.out.println(str1 + " " + str2 + " " + str3);
}
});
//采用lambda表达式来做
printStr(()-> System.out.println(str1 + " " + str2 + " " + str3));
}
//将匿名内部类用作函数参数来表示
public static void printStr(FunctionInterface1 fi) {
fi.say();
}
}
运行结果:
很明显lambda表达式更加简洁。
常见的函数式接口
下面做一个一些常见的函数式接口练习
1.supplier函数式接口练习:
Demo1.java
package FunctionInterface;
import java.util.function.Supplier;
/*
* 练习:求数组元素最大值
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。
提示:接口的泛型请使用java.lang.Integer类。
* */
public class Demo1 {
public static void main(String[] args) {
int[] arr = {3,8,2,1,10};
int res = getMax(()-> {
int max = arr[0];
for(int data : arr) {
if(max < data) {
max = data;
}
}
return max;
});
System.out.println(res);
}
public static int getMax(Supplier<Integer> supplier) {
return supplier.get();
}
}
运行结果:
2.Consumer函数式接口练习
Demo2.java
package FunctionInterface;
/**
* 练习:
* 字符串数组当中存有多条信息,请按照格式“姓名:XX。性别:XX。”的格式将信息打印出来。
* 要求将打印姓名的动作作为第一个Consumer接口的Lambda实例,
* 将打印性别的动作作为第二个Consumer接口的Lambda实例,
* 将两个Consumer接口按照顺序“拼接”到一起。
* */
import java.util.function.Consumer;
public class Demo2 {
public static void main(String[] args) {
String[] arr = {"周杰伦,男","莫文蔚,女","周星驰,男"};
format(arr,(str) -> {
String[] ret = str.split(",");
System.out.println("姓名:" + ret[0]);
},(str) -> {
String[] ret = str.split(",");
System.out.println("性别:" + ret[1]);
});
}
public static void format(String[] arr, Consumer<String> cons1,Consumer<String> cons2) {
for(String data : arr) {
cons1.andThen(cons2).accept(data);
}
}
}
运行结果:
3.predicate函数式接口
Demo3.java
package FunctionInterface;
/**
* /*
* 练习:集合信息筛选
* 数组当中有多条“姓名+性别”的信息如下,
* String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
* 请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中,
* 需要同时满足两个条件:
* 1. 必须为女生;
* 2. 姓名为4个字。
*
* 分析:
* 1.有两个判断条件,所以需要使用两个Predicate接口,对条件进行判断
* 2.必须同时满足两个条件,所以可以使用and方法连接两个判断条件
* */
import java.util.ArrayList;
import java.util.function.Predicate;
public class Demo3 {
public static void main(String[] args) {
String[] arr = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
ArrayList<String> list = filter(arr,(str) -> {
String[] ret = str.split(",");
if(ret[0].length() == 4) {
return true;
}
return false;
},(str) -> {
String[] ret = str.split(",");
if(ret[1].equals("女")) {
return true;
}
return false;
});
System.out.println(list);
}
public static ArrayList<String> filter(String[] arr, Predicate<String> predicate1,Predicate<String> predicate2) {
ArrayList<String> list = new ArrayList<String>();
for(String data : arr) {
boolean res = predicate1.and(predicate2).test(data);
if(res == true) {
list.add(data);
}
}
return list;
}
}
先来看一下Predicate接口中的and方法是什么样子
运行结果:
4.Function函数式接口
Demo4.java
package FunctionInterface;
import java.util.function.Function;
/*
Function接口中的默认方法andThen:用来进行组合操作
需求:
把String类型的"123",转换为Inteter类型,把转换后的结果加10
把增加之后的Integer类型的数据,转换为String类型
分析:
转换了两次
第一次是把String类型转换为了Integer类型
所以我们可以使用Function<String,Integer> fun1
Integer i = fun1.apply("123")+10;
第二次是把Integer类型转换为String类型
所以我们可以使用Function<Integer,String> fun2
String s = fun2.apply(i);
我们可以使用andThen方法,把两次转换组合在一起使用
String s = fun1.andThen(fun2).apply("123");
fun1先调用apply方法,把字符串转换为Integer
fun2再调用apply方法,把Integer转换为字符串
*/
public class Demo4 {
public static void main(String[] args) {
String str = "123";
String res = format(str,(str1) -> Integer.parseInt(str1) + 10,
(num1)-> Integer.toString(num1));
System.out.println(res);
}
public static String format(String str,Function<String,Integer> func1,Function<Integer,String> func2) {
return func1.andThen(func2).apply(str);
}
}
来看一下Function接口中的andThen方法
运行结果:
简单来说一下andThen这种方法的拼接原理:
拿Consumer接口来说:
上面就是andThean它本身是Consumer中的一个方法,也就是说实现Consumer接口本身的对象是可以去调用的,然后它内部又返回了一个Consumer<T>接口,利用lambda表达式实现的,也就是匿名内部去实现的,而这个的实现是依赖于传入的Consumer接口与接口本身。所以它可以又如下操作:
cons1->andthen(传入一个cons2接口)整体返回一个Consumer接口去调用accept(传入值)
直接看一段代码:
package FunctionInterface;
/**
* 练习:
* 字符串数组当中存有多条信息,请按照格式“姓名:XX。性别:XX。”的格式将信息打印出来。
* 要求将打印姓名的动作作为第一个Consumer接口的Lambda实例,
* 将打印性别的动作作为第二个Consumer接口的Lambda实例,
* 将两个Consumer接口按照顺序“拼接”到一起。
* */
import java.util.function.Consumer;
public class Demo2 {
public static void main(String[] args) {
String[] arr = {"周杰伦,男","莫文蔚,女","周星驰,男"};
format(arr,(str) -> {
String[] ret = str.split(",");
System.out.println("姓名:" + ret[0]);
},(str) -> {
String[] ret = str.split(",");
System.out.println("性别:" + ret[1]);
});
}
public static void format(String[] arr, Consumer<String> cons1,Consumer<String> cons2) {
for(String data : arr) {
cons1.andThen(cons2).accept(data);
}
}
}
函数式接口值得注意的几个问题
1.可以上下文推断类型
利用lambda表达式返回一个Lambda对象,通过返回对象里面的参数类型,lambda表达式可以上文推断出c1
和 c2
的类型为 Entry<K, V>
,从而允许调用 getKey()
方法
然而如果是匿名内部类的做法
如果是匿名内部类的实现,那就相当于是在实现接口的时候注明需要传入的类型
2.可以返回多个对象必须实现的接口,匿名内部类做不到这一点
3.Lambda 表达式在生成的实现类中会自动实现其所在的函数式接口及其父接口,包括 Serializable
接口
Lambda 表达式 (c1, c2) -> c1.getKey().compareTo(c2.getKey())
是一个函数式接口 Comparator<Entry<K, V>>
的实现。由于 Comparator
接口本身继承了 Serializable
接口,Lambda 表达式在生成的类中也会实现 Serializable
接口