JDK8绝对可以算是1.6之后的又外一个里程碑式的版本更新,有很多惊艳的更新。今天我们就来聊聊JDK8带来的接口设计上的变动。
概述
JDK8之后,interface接口在语法上做了大改动,注意是3个方面:
1>默认实现
2>静态方法
3>函数接口
默认实现
所谓的默认实现就是接口中不再固定要求只能存在抽象的方法,也快存在非抽象实例方法。
public interface MyInterface {
// default修饰符定义默认方法
default void defaultMethod() {
System.out.println("接口中的默认方法");
}
}
实现类:
public class MyInterfaceImpl implements MyInterface{
//可以重写defaultMethod方法,也可以不重写
}
从上面代码上看,实现类即使不重写defaultMethod 方法依然可以正常编译。
此时,肯定会有朋友问,这设计能带来说好处呢?个人看法,主要体现在下面几个方面:
1>减少抽象适配器类
这个不好描述,以例子说明:
例子:Spring拦截器接口:HandlerInterceptor,自定义拦截器需要实现该接口,JDK8之前必须全部重写3个方法,即使大部分业务操作只需要关注preHandler方法的实现。
public class MyHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
上面实现类其实只需要关注preHandler方法即可,但是接口规则限制,后面的2个方法不得不空实现。为应对这种非必要的接口方法实现,JDK8出来之前应对方案:
抽象适配器类(HandlerInterceptorAdapter):对HandlerInterceptor接口进行空实现,自定义拦截器需要继承该类,并重写需要接口方法。
public class MyHandlerInterceptorAdapter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
public class MyHandlerInterceptor extends MyHandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//业务逻辑
return false;
}
}
}
上面解决方案特点:费时又费力,代码结构还复杂,麻烦。
JDK8后引入接口默认实现,自带适配器逻辑,后续自定义拦截器只需要实现HandlerInterceptor接口,然后按需重写接口方法即可
//都是默认实现方法
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
自定义拦截器
public class MyHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
}
简单明了
2>兼容以前旧接口的
从接口兼容问题上看,接口默认实现绝对是巧妙至极。
举个例子:
JDK8之前,Collection接口没有以下方法
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
JDK8之后,引入的lambda表达式与Stream操作,一些接口就需要升级啦。如果没有默认实现这骚操作,那之前写过的所有代码就必须改动,重写因为引入lambda表达式与Stream操作而新增的方法。而现在,你会发现,可以无缝兼容,之前代码0改动,同时自动新增了lambda表达式与Stream操作逻辑。
上面例子,所有使用Collection接口的类/子接口,都不需要做任何改动,即可支持Lambda与Stream操作。
静态方法
JDK8之前的接口可以定义变量和方法,变量必须是public、static、final的,方法必须是public、abstract的。JDK8之后可以在接口添加了静态方法。
public interface MyInterface {
static void dowork(){
System.out.println("......");
}
}
测试
public static void main(String[] args) {
MyInterface.dowork();
}
这时候,我们又得问啦,接口里面添加静态方法,目的是为啥,很简单:快速实现
以Spring-data体系中的Page接口为例子
public interface Page<T> extends Slice<T> {
static <T> Page<T> empty() {
return empty(Pageable.unpaged());
}
static <T> Page<T> empty(Pageable pageable) {
return new PageImpl<>(Collections.emptyList(), pageable, 0);
}
int getTotalPages();
long getTotalElements();
<U> Page<U> map(Function<? super T, ? extends U> converter);
}
Page接口是一个分页信息封装对象,对分页数据与分页条数据进行封装,正常实现应该:PageImpl
public class PageImpl<T> extends Chunk<T> implements Page<T> {
//....
}
但实际操作中可能存在这种情况:分页总数据为0, 那么就没必要进行分页数据封装了。那么分页逻辑直接返回
Page.empty(); //方便,简单明了,更不需要额外实现类
函数接口
JDK8之后,引入一个新的概念:函数接口
定义:有且仅有一个抽象方法的接口。
public interface MyInterface {
void dowork();
}
为了明确标记函数接口,使用了标记注解:@FunctionalInterface
@FunctionalInterface
public interface MyInterface {
void dowork();
}
这里注意,这种写法也算是一个函数接口
@FunctionalInterface
public interface MyInterface {
void dowork();
default void defaultMethod(){
System.out.println("default....");
}
}
默认实现不算是抽象方法。函数接口引入目的是配合Lambda表达跟Stream操作,具体实现后续再展开讲,这里说一个简单例子
线程接口-JDK之后的改造
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
JDK8之前最简单实现
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(".....");
}
}).start();
JDK8之后,再进一步简化
new Thread(()-> System.out.println("....")).start();
好了,JDK8之后接口变动就聊到这,下一篇我们来聊聊函数接口。