目录
01函数式接口
1.1概念
- 函数式接口在Java中是指:有且仅有一个抽象方法的接口。
- 函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
1.2格式
- 只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
- 由于接口当中抽象方法的 public abstract 是可以省略的,所以定义一个函数式接口很简单:
public interface MyFunctionalInterface {
void myMethod();
}
1.3@FunctionalInterface注解
- 与
@Override
注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface
。该注解可用于一个接口的定义上:
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
- 注意:一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样
1.4自定义函数式接口
- 对于刚刚定义好的 MyFunctionalInterface 函数式接口,典型使用场景就是作为方法的参数:
//定义函数式接口
@FunctionalInterface
interface MyFunctionalInterface {
void myMethod();
}
//测试类
public class Test {
// 使用自定义的函数式接口作为方法参数
private static void doSomething(MyFunctionalInterface inter) {
inter.myMethod(); // 调用自定义的函数式接口方法
}
public static void main(String[] args) {
// 调用使用函数式接口的方法,并重写此抽象方法
doSomething(()-> System.out.println("Lambda执行啦!"));
}
}
02函数式编程
2.1Lambda的延迟执行
- 有些时候的代码执行后,结果不一定会被使用,从而造成性能浪费。
- 而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。
- 例如,日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,一种典型的场景就是对参数进行有条件使用,将日志消息进行拼接后,在满足条件的情况下进行打印输出
public class Test {
//打印日志的方法
public static void log(int level,String msg){
//当等级为1时,信息被打印出来
if(level==1){
System.out.println(msg);
}
}
public static void main(String[] args) {
String msgA="Hello";
String msgB="World";
log(1,msgA+msgB);
//我们故意把等级设置为2,看看信息拼接会不会执行
log(2,msgA+msgB);
}
}
- 分析:我们在IDEA中使用debug的方式启动,运行后发现,当调用log方法时,在进入方法体之前,log方法的第二个参数,两个字符串已经被拼接好了,然后在进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费
2.1.1体验Lambda的延迟执行
- 注意:使用Lambda必然需要一个函数式接口:
//定义一个函数式接口
@FunctionalInterface
interface Message{
//定义一个抽象方法(public abstract 可以省略)
public abstract String printMsg();
}
//主函数
public class Test {
//打印日志的方法,第二个参数为抽象接口类型的
//此处使用到了多态和匿名内部类,使用抽象接口类型的变量,操作匿名内部类中的方法
public static void log(int level,Message msg){
//当等级为1时,信息被打印出来
if(level==1){
System.out.println(msg.printMsg());
}
}
public static void main(String[] args) {
String msgA="Hello";
String msgB="World";
//重写抽方法
log(1,()->{
System.out.println("当等级为1时执行了吗?");
return msgA+msgB;});
//我们故意把等级设置为2,看看信息拼接会不会执行
log(2,()->{
System.out.println("当等级为2时执行了吗?");
return msgA+msgB;});
}
}
- 分析:我们同样使用IDEA的debug功能启动,运行后发现,当调用log方法时,在第二参数拼接信息之前,先进行了等级判断,当等级不满足的时候,后面的拼接动作就不会继续执行
2.2 使用Lambda作为参数和返回值
2.2.1作为参数
- 如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。
- 例如
java.lang.Runnable
接口就是一个函数式接口,假设有一个 startThread 方法使用该接口作为参数,那么就可以使用Lambda进行传参。这种情况其实和 Thread 类的构造方法参数为 Runnable 没有本质区别。
//此处我们使用的是JDK自带的Runnable接口
public class Test {
//定义一个启动线程的方法
public static void startThread(Runnable runnable){
new Thread(runnable).start();
}
//测试方法
public static void main(String[] args) {
//调用启动线程的方法
startThread(()-> System.out.println(Thread.currentThread().getName()+":线程启动了"));
//对比:使用匿名内部类启动线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":线程启动啦");
}
}).start();
}
}
2.2.2作为返回值
- 类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一个方法来获取一个
java.util.Comparator
接口类型的对象作为排序器时,就可以调该方法获取
public class Test {
private static Comparator<String> newComparator() {
return (a,b)-> b.length()-a.length();
}
public static void main(String[] args) {
String[] array = { "abc", "ab", "abcd" };
System.out.println(Arrays.toString(array));
//使用sort方法对数组进行排序,排序方法由newComparator方法指定
Arrays.sort(array, newComparator());
System.out.println(Arrays.toString(array));
}
}
03常用函数式接口
- 在JDK-9文档中,
java.util.function
包下,存放的就是JDK提供的函数式接口,此处介绍几个常用的函数式接口,更多的可以查看帮助文档
3.1Supplier接口
- java.util.function.Supplier 接口仅包含一个无参的方法:
T get()
。用来获取一个泛型参数指定类型的对象数据。 - 由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
- 例如:求数组元素最大值
public class Test {
//定义一个方法,使用Supplier接口作为参数
//指定泛型类型
public static int getMax(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
int[] arr={1,2,10,4,5,6};
//调用maxNum方法
int maxNum= 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(maxNum);
}
}
分析:代码执行流程
- 1) 由于lambda作为方法参数的延时性,当调用getMax方法后,执行return中的语句,此时return中的语句并未真正执行
- 2)程序会跳转到调用getMax方法的位置,调用lambda表达式,对抽象方法进行重写
- 3)之后程序会跳转到getMax方法中的return语句,这是才真正执行此语句,然后将返回值返回并打印出来
3.2Consumer接口
java.util.function.Consumer<T>
接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。Consumer
接口中包含抽象方法void accept(T t)
,意为消费一个指定泛型的数据
public class Test {
//创建一个消费字符串的方法
private static void consumeString(Consumer<String> function) {
//使用方法接收要消费的字符串
function.accept("HelloWorld");
}
public static void main(String[] args) {
//s为形参
consumeString((s)-> System.out.println(s));
}
}
3.3Predicate接口
-
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用
java.util.function.Predicate<T>
接口 -
Predicate
接口中包含一个抽象方法:boolean test(T t)
。用于条件判断 -
判断字符串长度是否大于5,大于就返回true
public class Test {
//定义一个判断方法
public static boolean checkString(String str, Predicate<String> pre){
return pre.test(str);
}
public static void main(String[] args) {
//定义一个字符串
String str="AISMALL";
//调用checkString方法对字符串进行检验
//s为形参,str为实参
boolean result=checkString(str,(s)->{
return s.length()>5;
});
System.out.println(result);
}
}
分析:代码执行流程
- 1)当程序调用checkString方法时,由于Lambda表达是的延时性,此时只有str参数被传入到方法中
- 2)然后程序就会执行到return语句,但是并不会真正的被执行,
- 3)程序会跳转到到延时执行的Lambda表达式处,执行Lambda表达式,对抽象方法(test)进行重写,
- 4)最后程序才会真正执行checkString方法的return语句,然后返回布尔值
对比使用匿名内部类的方式:
public class Test {
//定义一个判断方法
public static boolean checkString(String str, Predicate<String> pre){
return pre.test(str);
}
public static void main(String[] args) {
//定义一个字符串
String str="AISMALL";
//调用checkString方法对字符串进行检验
boolean result=checkString(str, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length()>5;
}
});
System.out.println(result);
}
}
3.3.1默认方法:and
- 既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法 and 。其JDK源码为:
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) && other.test(t);
}
- and方法使用:判断字符串中是否包含W和H
public class Test {
//定义一个判断方法
public static boolean method(Predicate<String> pre1,Predicate<String> pre2){
return pre1.and(pre2).test("HelloWorld");
}
public static void main(String[] args) {
//判断字符串中是否既包含W有包含H
boolean result=method((str1)->str1.contains("H"),(str2)->str2.contains("W"));
System.out.println(result);
}
}
3.3.2默认方法:or
- 与 and 的“与”类似,默认方法 or 实现逻辑关系中的“或”。JDK源码为:
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) || other.test(t);
}
- or方法使用:判断字符串中是否包含W或H
public class Test {
//定义一个判断方法
public static boolean method(Predicate<String> pre1,Predicate<String> pre2){
return pre1.or(pre2).test("helloworld");
}
public static void main(String[] args) {
boolean result=method((str1)->str1.contains("H"),(str2)->str2.contains("W"));
System.out.println(result);
}
}
3.3.3默认方法:negate
- “与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法 negate 的JDK源代码为:
default Predicate<T> negate() {
return (t) ‐> !test(t);
}
- negate方法使用:判断字符串中是否不包含W
public class Test {
//定义一个判断方法
public static boolean method(Predicate<String> pre1) {
return pre1.negate().test("AISMALL");
}
public static void main(String[] args) {
boolean result=method((str1)->str1.contains("H"));
System.out.println(result);
}
}
3.3.4应用
- 把一个String类型的数组中包含女和AISMALL的成员提取出来
public class Test {
//定义一个判断方法
public static List<String > filter(String[] array,Predicate<String> pre1,Predicate<String> pre2 ) {
//定义一个集合,用来存储满足条件的成员
List<String> list=new ArrayList<>();
//遍历传入的数组
for (String info:array) {
//判断条件
if(pre1.and(pre2).test(info))list.add(info);
}
return list;
}
public static void main(String[] args) {
//定义一个String类型数组
String[] array={"AISMALL_01,女","AISMALL_02,男","AISMALL_03,女"};
//调用filter方法,使用Lambda表达式写出过滤条件
List<String> list=filter(array,s->"女".equals(s.split(",")[1]),s->s.split(",")[0].contains("AISMALL"));
System.out.println(list);
}
}
- 运行结果:
[AISMALL_01,女, AISMALL_03,女]
3.4Function接口
java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
3.4.1抽象方法:apply
Function
接口中最主要的抽象方法为:R apply(T t)
,根据类型T的参数获取类型R的结果。- 使用的场景例如:将 String 类型转换为 Integer 类型。
public class Test {
private static void method(Function<String, Integer> function) {
int num = function.apply("10");
System.out.println(num + 20);
}
public static void main(String[] args) {
method(s-> Integer.parseInt(s));
}
}
3.4.2默认方法:andThen
- Function 接口中有一个默认的 andThen 方法,用来进行组合操作。JDK源代码如下:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) ‐> after.apply(apply(t));
}
- 该方法同样用于“先做什么,再做什么”的场景
public class Test {
private static void method(Function<String, Integer> one, Function<Integer, Integer> two) {
int num = one.andThen(two).apply("10");
System.out.println(num + 20);
}
public static void main(String[] args) {
method(str->Integer.parseInt(str)+10, i -> i *= 10);
}
}
- 分析:第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一起