常用的函数式接口
回顾
函数接口的概念在之前的文章《lambda表达式》中就有提到,函数接口配合Lambda表达式的使用在一些情况下可以大大简化我们java代码的书写。函数接口指的就是接口中只有一个抽象方法, 并且接口使用@FunctionalInterface注解进行修饰。JDK中帮我们定义好了一些常用的函数接口,这些函数接口配合Lambda和后面要讲的stream流进行一些过滤数据的操作,用起来非常简洁,下面就来讲常用到的函数接口有哪些。
值得一提的是函数式接口一个比较大的应用场景是作为参数传递给一个方法。
Supplier接口
(supplier 供应商)是一个"工厂"类型接口,用来"生产"对应的数据类型,不用传入任何数据。具体使用在后面的stream流中可以体现。
@FunctionalInterface
public interface Supplier<T> {}
抽象方法
T get():用来获取一个泛型参数类型指定的数据,即用来提供一个符合要求的数据类型,"生产"一个数据,就跟"供应商"一样。
举例:获取一个数组中的最大值
public class SupplierDemo {
public static int getMaxValue(Supplier<int> supplier) { //接口作为参数进行传递
return supplier.get();
}
public static void main(String[] args) {
int[] nums = {2, 9, 19, 1, 20};
int max = getMaxValue(()->{ //使用Lambad表达式
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
max = nums[i] > max ? nums[i] : max;
}
return max;
});
}
}
当然上面举例只是为了看一下get()方法的目的就是给我们提供一个想要的数据类型。
Consumer接口
(consumer 消费)与supplier生产一个数据相反的是,consumer是为消费数据,即传递数据进行操作,没有返回值。
@FunctionalInterface
public interface Consumer<T> {}
抽象方法
void accept(T):对给定的参数执行该方法的内容
举例:将传递的字符串数据进行逆转
public class ConsumerDemo {
public static void reserveString(String str, Consumer<String> con) {
con.accept(str);
}
public static void main(String[] args) {
String str = "lambda";
reserveString(str, (str)->{
System.out.println(str); //lambda
StringBuffer reStr = new StringBuffer(str);
reStr.reverse().toString(); //将字符串进行逆转
System.out.println(reStr); //adbmal
});
}
}
默认方法
与Supplier接口不同的是,Consumer接口中实现了一个默认的方法,addThen()
/**
* 默认方法
* 1. 返回一个组合的Consumer对象,并且会按照顺序执行accept方法:即可以拼接多个进行执行
* 2. 如果after方法执行出现异常(或接下去拼接的出现异常了)就会将该异常转发给调用者(例如main)。
* 3. 如果执行this操作出现异常时,不会执行after的操作。
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after); //判断是否为空指针
return (T t) -> {
accept(t);
after.accept(t);
}; //这也是一个Consumer接口accept的lambad实现
}
举例:将一个字符串转化为大小写
public class ConsumerDemo {
public static void method(String str, Consumer<String> con1, Consumer<String> con2) {
con1.andThen(con2).accept(str);
}
public static void main(String[] args) {
String str = "Lambda";
method(str, (t)->{
System.out.println(t.toUpperCase()); //LAMBDA
}, (t) -> {
System.out.println(t.toLowerCase()); //lambda
});
}
}
Predicate接口
(predicate 谓词,离散数学中的概念),传入一个参数,对参数进行逻辑判断,看参数是否符合条件(在给定的参数上评估这个谓词),返回值为boolean。
@FunctionalInterface
public interface Predicate<T> { }
抽象接口
boolean test(T t):判断传入的参数是否符合条件
举例:传入一个字符串列表,打印长度大于4的字符串
public class PredicateDemo {
public static void checkString(String[] strs, Predicate<String> pre) {
for (int i = 0; i < strs.length; i++) {
if (pre.test(strs[i])) {
System.out.println(strs[i]);
}
}
}
public static void main(String[] args) {
String[] strs = {"java", "c", "C++", "python", "javascript", "Typescript"};
checkString(strs, (str)->{
return str.length() > 4;
}); //python, javascript,Typescript
}
}
默认方法
既然是用来进行逻辑判断的,少不了要用到逻辑运算符,Predicate接口里面就内置了一些默认的逻辑运算方法
/**
* 1.返回一个通过逻辑与组合的Predicate接口
* 2.other.test抛出异常的话将该异常转发给调用者
* 3.this.test抛出异常的话不执行other.test的内容
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
//逻辑非
default Predicate<T> negate() {
return (t) -> !test(t);
}
//逻辑或
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
举例:判断字符串包含a并且长度大于5
public class PredicateDemo {
public static boolean checkString(String str, Predicate<String> pre1, Predicate<String> pre2) {
return pre1.and(pre2).test(str);
}
public static void main(String[] args) {
String str = "lambda";
boolean b = checkString(str, (str1)->{
return str1.length() > 5;
}, (str1) -> {
return str1.contains("a");
});
System.out.println(b); // true
}
}
Function接口
@FunctionalInterface
public interface Function<T, R> {}
该接口用来将类型T转化为类型R,T称为前置条件,R称为后置条件
抽象方法
R apply(T t):将给定参数类型T转化为R类型
例如,将String类型数字转化为Integer类型
public class FunctionDemo {
private static void transform(Function<String, Integer> function) {
int num = function.apply("10"); //数字10
System.out.println(num + 20);
}
public static void main(String[] args) {
transform(s ‐> Integer.parseInt(s));
}
}
默认方法
与Consumer类似的,Function也有一个addThen方法,该方法也是返回一个组合操作后的Function接口,最终转化的数据类型与after转化的数据类型一致。
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after); //判断是否为空指针
return (T t) -> after.apply(apply(t)); //这也是一个Consumer接口apply的lambad实现
}
举例:将字符串数字转化为Integer类型后+10再转化为字符串
public class FunctionDemo{
public static void addTen(String str, Function<String, Integer> fun1, Function<Integer, String> fun2) { //注意最后返回的与fun2的类型相同
String s = fun1.andThen(fun2).apply(str);
System.out.println(s); //"20"
}
public static void main(String[] args) {
String s = "10";
addTen(s, (String str1) ->{
return Integer.parseInt(str1) + 10;
}, (Integer integer) -> {
return integer.toString();
});
}
}
小结
- 通过函数式接口,我们可以看到lambda表达式的简洁之处,当然更简洁的是使用方法引用的方式。
- 虽然现阶段我们可能会觉得这么进行操作多此一举,但是这些接口在后面的stream流就有很大的用处。