Java 函数式编程
简单来说,函数式编程就是被注解@FunctionalInterface修饰的接口。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
// 以jdk中的LongToIntFunction,相当于就是一个接口
@FunctionalInterface
public interface LongToIntFunction {
/**
* Applies this function to the given argument.
*
* @param value the function argument
* @return the function result
*/
int applyAsInt(long value);
}
// 这个接口可以被直接传入到函数中直接使用
// 以jdk中的LongPipeline为例
@Override
public final IntStream mapToInt(LongToIntFunction mapper) {
Objects.requireNonNull(mapper);
return new IntPipeline.StatelessOp<Long>(this, StreamShape.LONG_VALUE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink<Long> opWrapSink(int flags, Sink<Integer> sink) {
return new Sink.ChainedLong<Integer>(sink) {
@Override
public void accept(long t) {
downstream.accept(mapper.applyAsInt(t));
}
};
}
};
}
那么,函数是编程的interface怎么实现呢?由之前讲注解的时候我们提到了,jdk内置的元注解,编译器是会识别并给被该注解修饰的对象赋予编译器预先定义好的行为的。
但是,接口始终是接口,不拥有实际的行为,实际的行为还是需要具体的实现类来定义,那么我们来看看,函数式接口应该如何implement。以Springboot中的函数式接口TargetServerConnection为例
@FunctionalInterface
public interface TargetServerConnection {
/**
* Open a connection to the target server with the specified timeout.
* @param timeout the read timeout
* @return a {@link ByteChannel} providing read/write access to the server
* @throws IOException in case of I/O errors
*/
ByteChannel open(int timeout) throws IOException;
}
// 实现类 SocketTargetServerConnection.java 中的open方法
@Override
public ByteChannel open(int socketTimeout) throws IOException {
SocketAddress address = new InetSocketAddress(this.portProvider.getPort());
logger.trace(LogMessage.format("Opening tunnel connection to target server on %s", address));
SocketChannel channel = SocketChannel.open(address);
channel.socket().setSoTimeout(socketTimeout);
return new TimeoutAwareChannel(channel);
}
// 在 HttpTunelServer中使用
protected ServerThread getServerThread() throws IOException {
synchronized (this) {
if (this.serverThread == null) {
ByteChannel channel = this.serverConnection.open(this.longPollTimeout);
this.serverThread = new ServerThread(channel);
this.serverThread.start();
}
return this.serverThread;
}
}
可以看到,这种写法就是传统的接口实现方法,所以我们讲,函数式接口就是一个接口,不是什么高级语法糖,@FunctionalInterface注解也并不会提供额外的功能,只是通过该注解修饰的接口,让编译器知道,然后去判定该接口是否定义得合乎函数式接口的定义规则。
那么,到底函数式接口该如何使用呢?
要回答这个问题可不简单,先来看看下面的代码
@FunctionalInterface
public interface ExitCodeExceptionMapper {
int getExitCode(Throwable exception);
}
这个是springboot框架中定义获取退出码的函数式接口
// SpringApplicationTest.java
@Bean
ExitCodeExceptionMapper exceptionMapper() {
return (exception) -> {
if (exception instanceof IllegalStateException) {
return 11;
}
return 0;
};
}
// SpringApplication.java
private int getExitCodeFromMappedException(ConfigurableApplicationContext context, Throwable exception) {
if (context == null || !context.isActive()) {
return 0;
}
ExitCodeGenerators generators = new ExitCodeGenerators();
Collection<ExitCodeExceptionMapper> beans = context.getBeansOfType(ExitCodeExceptionMapper.class).values();
generators.addAll(exception, beans);
return generators.getExitCode();
}
该函数式接口被使用的地方,从第一个例子我们可以知道,函数式接口所诠释的类型等价于其唯一方法的返回值所代表的类型。第二个例子告诉我们,函数式接口不一定非要被实现才能获得实际行为,也可以通过反射得到。
源码分析结束了,我们来实现一个例子
// 定义一个函数式接口 Demo1.java
@FunctionalInterface
public interface Demo1 {
String getName(Integer code);
}
// 实现类 Demo2.java
public class Demo2 implements Demo1{
@Override
public String getName(Integer code) {
StringBuilder builder = new StringBuilder();
return builder.append("my name is ").append(code).append("!").toString();
}
}
// 调用方 Demo3.java
public class Demo3{
public static void main(String[] args){
// 在使用过程中实例化
Demo1 demo1 = new Demo1() {
@Override
public String getName(Integer code) {
return new StringBuilder().append("my name is ").append(code).append("!").toString();
}
};
System.out.println(demo1.getName(1));
System.out.println(demo1.getName(2));
// 普通接口
Demo1 demo2 = new Demo2();
System.out.println(demo2.getName(1));
System.out.println(demo2.getName(2));
// 简单的函数式编程
Demo1 demo3 = code -> {
return new StringBuilder().append("my name is ").append(code).append("!").toString();
};
System.out.println(demo3.getName(1));
System.out.println(demo3.getName(2));
}
}
上面三个打印结果是一样的,也就是说,对于函数式接口,我们可以直接用这样的语法
Demo1 demo3 = code -> {
return new StringBuilder().append("my name is ").append(code).append("!").toString();
};
来定义出其函数体。
带来的好处
简洁和稳定