函数式接口
1、概念、格式
函数式接口在Java中是指只有一个抽象方法的接口,但是接口中还可以有其他方法,比如public、default等,只有确保接口只有一个抽象方法,Lambda才能进行顺利的推导。
格式
@FunctionalInterface// 使用注解来检测该接口是否函数式接口,也就是是否只有一个抽象方法
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(参数列表);
}
函数式接口作为方法的参数:
package cn.zhuobo.day16.aboutFuntionalInterface;
//函数式接口作为一个方法的参数,有三种用法去调用这个方法
public class MyFunctionalInterfaceMain {
public static void main(String[] args) {
// 1、创建一个实现类对象传递给show方法,该实现类里重写了抽象方法
show(new MyFunctionnalInterfaceImpl());
// 2、创建一个匿名内部类,传递给show方法
show(new MyfunctionalInterface() {
@Override
public void method() {
System.out.println("这是使用匿名内部类来实现的");
}
});
// 3、使用Lambda表达式简化匿名内部类的书写
show(() -> {
System.out.println("这是使用Lambda表达式实现的");
});
}
public static void show(MyfunctionalInterface myInter) {
myInter.method();
}
}
注意:Lambda表达式在应用上可以看作是一种语法糖,简化了匿名内部类的书写,但是,事实上他他们的原理是不一样的(for each 表达式是简化迭代器书写的一种语法糖,他们的实现原理都是一样,都是使用迭代器),Lambda表达式不会生成一个class文件,但是使用匿名内部类会生成一个xxx$1.class
文件。
2、函数式编程
2.1 延迟执行,函数式接口作为方法的参数
有时候,我们的代码的执行结果不会被马上使用,或者没有产生有用的结果,就会发生性能浪费的事情。就像下面的例子一样,一个方法的传递给方法的参数是三个字符串的拼接,也就是拼接字符串出现在判断level == 1
之前,也就是说哪怕level != 1
,也会初心字符串拼接,但是此时这些拼接的字符串就显得没有意义了,因为根本没有什么用。也就是发生了性能浪费。
package cn.zhuobo.day16.logger;
public class LoggerDemo1 {
public static void main(String[] args) {
String mess1 = "你是";
String mess2 = "猪猪";
String mess3 = "吗?";
showMessage(2, mess1 + mess2 + mess3);
}
public static void showMessage(int level, String message) {
if(level == 1) {
System.out.println(message);
}
}
}
Lambda表达式的延迟执行:把Lambda表达式作为参数传递到方法中,只有满足条件,这样才会执行Lambda表达式的代码块,这样就可以避免性能的浪费。
一个接口:
package cn.zhuobo.day16.logger;
@FunctionalInterface
public interface LoggerInterface {
public abstract String showMessage();// 返回String
}
性能优化:
package cn.zhuobo.day16.logger;
public class LoggerDemo02 {
public static void main(String[] args) {
String mess1 = "你是";
String mess2 = "猪猪";
String mess3 = "吗?";
method(2, () -> {
return mess1 + mess2 + mess3;
});
}
public static void method(int level, LoggerInterface logger) {
if(level == 1)
System.out.println(logger.showMessage());
}
}
仔细梳理一下就会发现:接口的抽象方法返回的是字符串,根据Lambda表达式,我们可以知道他返回的是一个拼接后的字符串。同时,在method方法中,只有level==1
才会调用接口的showMessage方法,这样才会进行字符串的拼接,反之就不会进行字符串拼接。没有性能浪费现象。
2.2 函数式接口作为方法的返回值
如果一个方法返回的是一个函数式接口,那么就可以返回一个Lambda表达式(当然也可以返回实现类对象,匿名内部类对象)
下面的例子是通过一个方法获得一个java.util.Comparator接口类型的对象作为Arrays.sort方法的参数,对数组进行排序,这个方法就返回一个函数式接口。
package cn.zhuobo.day16.functionalInterfaceReturn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
public class Demo01 {
// 这个方法的返回值是一个函数式接口,那么可以返回一个匿名内部类
public static Comparator<String> getComparator1() {
return new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();// 按照字符串的长度升序排序
}
};
}
// 这个方法的返回值是一个函数式接口,返回一个Lambda表达式
public static Comparator<String> getComparator2() {
return (o1, o2) -> o1.length() - o2.length();
}
public static void main(String[] args) {
String[] arr = {"aa", "bbbb", "ccccccccccc", "dd", "adf"};
System.out.println(Arrays.toString(arr));
Arrays.sort(arr, getComparator2());
System.out.println(Arrays.toString(arr));
}
}
2.3 常用的函数式接口
java.util.function中包含了大量的函数式接口
- java.util.function.Supplier
该接口仅包含一个无参数的方法T get()
,用来获取一个泛型参数指定类型的对象数据(生产数据),指定接口的泛型T是什么,那么get方法就会生产(返回)什么类型的数据
package cn.zhuobo.day16.aboutFuntionalInterface;
import java.util.function.Supplier;
public class InterfaceSupplier {
public static String getString(Supplier<String> sup) {
//System.out.println("shshs");
return sup.get();
}
public static void main(String[] args) {// getString方法是函数式接口,因此可以传递Lambda表达式
String str = getString(() -> {return "bilibili";});
System.out.println(str);
}
}
- java.util.function.Consumer
Consumer接口是和Supplier接口相反,不是生产一个数据,而是消费一个数据,该接口中包含抽象方法void accept(T, t),意思是消费一个数据,泛型T指定的是什么类型就消费什么类型的数据,至于具体怎么消费(也就是怎么使用数据),那就要根据自己的需要的(输出,运算.......)
package cn.zhuobo.day16.aboutFuntionalInterface;
import java.util.function.Consumer;
public class InterfaceConsumer {
// 一个函数式接口作为参数
public static void method(String message, Consumer<String> con) {
con.accept(message);
}
public static void method2(String message, Consumer<String> con1, Consumer<String> con2) {
con1.andThen(con2).accept(message);// 先将两个接口连接到一起再消费数据,但是还是调用者先消费,也就con1先消费
}
public static void main(String[] args) {
String message = "Hello";
// 参数里可以写Lambda表达式,字节决定如何重写accept方法,这里是是字符串翻转
method(message, (s) -> {
String reMessage = new StringBuilder(s).reverse().toString();
System.out.println(reMessage);
});
method2(message, (s) -> {
System.out.println(s.toUpperCase());
}, (s) -> {
System.out.println(s.toLowerCase());
});
}
}
- java.util.function.Predicate
该接口用来对某种类型的数据进行判断,结果返回一个boolean值,可以使用接口的一个抽象方法boolean test(T t)
判断,结果符合条件返回true,否则返回false
Predicate接口中三个默认方法and
、or
、negate
package cn.zhuobo.day16.aboutFuntionalInterface;
import java.util.function.Predicate;
public class InterfacePredicate {
// 一个条件
public static boolean stringPredicate1(String message, Predicate<String> pre){
return pre.test(message);// 调用test对message进行判断
}
// and方法
public static boolean stringPredicate2(String message, Predicate<String> pre1, Predicate<String> pre2) {
//return pre1.test(message) && pre2.test(message);
// 使用and方法有一样的效果
return pre1.and(pre2).test(message);
}
// or方法
public static boolean stringPredicate3(String message, Predicate<String> pre1, Predicate<String> pre2) {
//return pre1.test(message) && pre2.test(message);
// 使用and方法有一样的效果
return pre1.or(pre2).test(message);
}
//negate方法
public static boolean stringPredicate4(String message, Predicate<String> pre){
return pre.negate().test(message);// 调用test对message进行判断
}
public static void main(String[] args) {
String message = "hellollo";
boolean b1 = stringPredicate1(message, (str) -> {// 这里写判断的逻辑
return str.length() > 6;
});
System.out.println(b1);
boolean b2 = stringPredicate2(message, (str)->{
return str.length() > 6;
}, (str)->{
return str.contains("j");
});
System.out.println(b2);
}
}
- java.util.function.Function<T, R>
该接口用来根据一个类型的数据的到另一个类型的数据,前者为前置条件,后者为后置条件。该接口最主要的抽象方法是 R apply(T, t)
,根据T类型的参数获取R类型的参数
默认方法:andthen
,与consumer接口类似的情况,当使用两个Function接口作为方法的参数时,就可以类似的使用(也就是可以使用andThen方法,将两次转换连接到一起,还是一样的先进行andThen的调用者。)