7.函数式接口
7.1.概念
- 函数式接口在Java中是指:有且仅有一个抽象方法的接口。
- 函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
7.2.Lambda的延迟执行
- 有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。
//性能浪费的日志案例
public class Logger {
private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, msgA + msgB + msgC);
}
}
- 以上这段代码存在问题:无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
- Lambda的更优写法
//定义函数式接口
@FunctionalInterface
public interface MessageBuilder {
String buildMessage();
}
//改造log方法
public class LoggerLambda {
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, () ‐> msgA + msgB + msgC );
}
}
//只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接
- 证明Lambda的延迟
public class LoggerDelay {
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(2, () ‐> {
System.out.println("Lambda执行!");
return msgA + msgB + msgC;
});
}
}
扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法来完成。而是否调用其所在方法是在条件判断之后才执行的。
- Lambda作为参数和返回值
//Lambda作为参数
//只要参数是一个函数式接口,就可以使用Lambda表达式
public class Runnable {
private static void startThread(Runnable task) {
new Thread(task).start();
}
public static void main(String[] args) {
startThread(() ‐> System.out.println("线程任务执行!"));
/*原写法 startThread(new Runnable(){ @Override public void run(){ System.out.println(Thread.currentThread().getName+"-->线程启动") } }); */
}
}
//Lambda作为返回值
//只要返回值是一个函数式接口,就可以使用Lambda表达式
import java.util.Arrays;
import java.util.Comparator;
public class Comparator {
private static Comparator<String> newComparator() {
return (a, b) ‐> b.length() ‐ a.length();
/*原写法 return new Comparator<String>() { public int compare(String o1,String o2){ return o2.length-o1.length } } */
}
public static void main(String[] args) {
String[] array = {
"abc", "ab", "abcd" };
System.out.println(Arrays.toString(array));
Arrays.sort(array, newComparator());
System.out.println(Arrays.toString(array));
}
}
7.3.常用函数式接口
7.3.1.Supplier接口
- java.util.function.Supplier 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据
import java.util.function.Supplier;
public class Supplier {
private static String getString(Supplier<String> function) {
return function.get();
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
System.out.println(getString(() ‐> msgA + msgB));
}
}
7.3.2.Consumer接口
- java.util.function.Consumer 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
- 抽象方法:accept
- Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据
import java.util.function.Consumer;
public class Consumer {
private static void consumeString(Consumer<String> function) {
function.accept("Hello");
}
public static void main(String[] args) {
consumeString(s ‐> System.out.println(s));
}
}
- 默认方法:andThen
- 如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen
java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
- 要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组合的情况:
/* default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) ‐> { accept(t); after.accept(t); }; } */
import java.util.function.Consumer;
public class ConsumerAndThen {
private static void consumeString(Consumer<String> one, Consumer<String> two) {
one.andThen(two).accept("Hello");
}
public static void main(String[] args) {
consumeString(
s ‐> System.out.println(s.toUpperCase()),
s ‐> System.out.println(s.toLowerCase())
);
}
}
7.3.3.Predicate接口
- 有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate 接口。
- 抽象方法:test
- Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断的场景:
import java.util.function.Predicate;
public class PredicateTest {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() > 5);
}
}
- 默认方法:and
- 条件判断的标准是传入的Lambda表达式逻辑,只要字符串长度大于5则认为很长
- 既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法 and
//判断一个字符串既要包含大写“H”,又要包含大写“W”
/* default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) ‐> test(t) && other.test(t); } */
import java.util.function.Predicate;
public class PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.and(two).test("Helloworld");
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"));
}
}
- 默认方法:or
- 与 and 的“与”类似,默认方法 or 实现逻辑关系中的“或”
//实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称即可
/* default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) ‐> test(t) || other.test(t); } */
import java.util.function.Predicate;
public class PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.or(two).test("Helloworld");
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"));
}
}
- 默认方法:negate
//执行了test方法之后,对结果boolean值进行“!”取反。一定要在 test 方法调用之前调用 negate 方法
/* default Predicate<T> negate() { return (t) ‐> !test(t); } */
import java.util.function.Predicate;
public class PredicateNegate {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.negate().test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() < 5);
}
}
7.3.4.Function接口
- java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
- 抽象方法:apply
- Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。使用的场景例如:将 String 类型转换为 Integer 类型。
import java.util.function.Function;
public class FunctionApply {
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));
}
}
- 默认方法:andThen
- Function 接口中有一个默认的 andThen 方法,用来进行组合操作
/* default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) ‐> after.apply(apply(t)); } */
import java.util.function.Function;
public class FunctionAndThen {
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);
}
}
Function的前置条件泛型和后置条件泛型可以相同。