函数式接口和函数式编程
函数式接口
概念
有且仅有⼀个抽象⽅法的接⼝。
如:
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
格式
也可也自定义一个函数式接口
修饰符 interface 接⼝名称 {
public abstract 返回值类型 ⽅法名称(可选参数信息);
// 其他⾮抽象⽅法内容
}
//接口中的抽象方法public abstract可以省略不写,默认为public abstract
函数式编程
概念
在兼顾⾯向对象特性的基础上,Java语⾔通过Lambda表达式与⽅法引⽤等,为开发者打开了函数式编程的⼤⻔。下⾯我们做⼀个初探。
lambda 表达式
为什么要要使用lambda表达式
有些场景的代码执⾏后,结果不⼀定会被使⽤,从⽽造成性能浪费。⽽Lambda表达式是延迟执⾏的,这正好可以作为解决⽅案,提升性能。
性能浪费的⽇志案例
注:⽇志可以帮助我们快速的定位问题,记录程序运⾏过程中的情况,以便项⽬的监控和优化。
⼀种典型的场景就是对参数进⾏有条件使⽤,例如对⽇志消息进⾏拼接后,在满⾜条件的情况下
进⾏打印输出:
public class Demo01Logger {
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的更优写法
使⽤Lambda必然需要⼀个函数式接⼝:
@FunctionalInterface
public interface MessageBuilder {
String buildMessage();
}
然后对log方法进行改造
public class Demo02LoggerLambda {
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表达式作为参数和返回值
如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果⽅法的参数是⼀个函数式接⼝类型,那么就可以使⽤Lambda表达式进⾏替代。使⽤Lambda表达式作为⽅法参数,其实就是使⽤函数式接⼝作为⽅法参数。
例如 java.lang.Runnable 接⼝就是⼀个函数式接⼝,假设有⼀个 startThread ⽅法使⽤该接⼝作为参数,那么就可以使⽤Lambda进⾏传参。这种情况其实和 Thread 类的构造⽅法参数为 Runnable 没有本质区别。
public class Demo04Runnable {
private static void startThread(Runnable task) {
new Thread(task).start();
}
public static void main(String[] args) {
startThread(() -> System.out.println("线程任务执⾏!"));
}
}
类似地,如果⼀个⽅法的返回值类型是⼀个函数式接⼝,那么就可以直接返回⼀个Lambda表达式。当需要通过⼀个⽅法来获取⼀个 java.util.Comparator 接⼝类型的对象作为排序器时,就可以调该⽅法获取。
import java.util.Arrays;
import java.util.Comparator;
public class Demo06Comparator {
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));
Arrays.sort(array, newComparator());
System.out.println(Arrays.toString(array));
}
}
其中直接return⼀个Lambda表达式即可。
常⽤函数式接⼝
Supplier接口(生产者)
java.util.function.Supplier 接⼝仅包含⼀个⽆参的⽅法: T get() 。⽤来获取⼀个泛型参数指定类型的对象数据。由于这是⼀个函数式接⼝,这也就意味着对应的Lambda表达式需要“对外提供”⼀个符合泛型类型的对象数据。
import java.util.function.Supplier;
public class Demo08Supplier {
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));
}
}
当然,更好的写法是使⽤方法引用。
Consumer接⼝(消费者)
抽象方法 accept()
java.util.function.Consumer 接⼝则正好与Supplier接⼝相反,它不是⽣产⼀个数据,⽽是消费⼀个数据,其数据类型由泛型决定。
import java.util.function.Consumer;
public class Demo09Consumer {
private static void consumeString(Consumer<String> function) {
function.accept("Hello");
}
public static void main(String[] args) {
consumeString(s -> System.out.println(s));
}
}
默认⽅法 andThen()
andThen()语义是"一步接一步操作"
先去执行调用andThen()方法的调用者的accept()语句,然后执行andThen()里的对象的accept()语句
如果⼀个⽅法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,⾸先做⼀个操作,然后再做⼀个操作,实现组合。⽽这个⽅法就是 Consumer 接⼝中的default⽅法andThen 。
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
先去执行调用andThen()方法的调用者的语句,然后执行andThen()里的对象的语句 如:
import java.util.function.Consumer;
public class Demo10ConsumerAndThen {
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()),//one
s -> System.out.println(s.toLowerCase()));//two
}
}
Predicate 接口(条件判断)
抽象方法 test()
boolean test(T t)
案例:
import java.util.function.Predicate;
public class Demo15PredicateTest {
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() or() negate()
and()与 案例:
import java.util.function.Predicate;
public class Demo16PredicateAnd {
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()或 案例:
import java.util.function.Predicate;
public class Demo16PredicateAnd {
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()非 案例:
import java.util.function.Predicate;
public class Demo17PredicateNegate {
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);
}
}
Function接⼝(前者为前置条件,后者为后置条件)
抽象方法 R apply(T t)
根据类型T的参数获取类型R的结果。使⽤的场景例如:将 String 类型转换为 Integer 类型
import java.util.function.Function;
public class Demo11FunctionApply {
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()
该⽅法同样⽤于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多