Java 接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)
引自百度百科
在 JDK1.8 以前接口中是不允许存在具体的方法实现的,也就是说全部都是抽象方法。所以我们可以把接口看作是更为彻底的抽象类
接口更多的时候是一种公共的规范标准,只有符合标准才可以通用,其定义和使用如下
public interface UserDao {
}
public class UserDaoImpl implements UserDao{
}
接口和其成员的特点
接口本身不能实例化,同时要求其实现类必须实现接口中的抽象方法。一个类可以同时实现多个接口
成员变量默认使用的修饰符为 public static final 也就是常量
成员方法默认使用的修饰符为 public abstract
函数式接口在 Java 中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而 Java 中的函数式编程体现就是 Lambda,所以函数式接口就是可以适用于 Lambda 使用的接口。只有确保接口中有且仅有一个抽象方法,Java 中的 Lambda 才能顺利地进行推导。
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
由于接口当中抽象方法的 public abstract 是可以省略的,所以定义一个函数式接口很简单
public interface MyFunctionalInterface {
void myMethod();
}
与 @Override 注解的作用类似,Java 8 中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上:
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
Lambda 的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:
使用 Lambda 必须具有接口,且要求 接口中有且仅有一个抽象方法
使用 Lambda 必须具有 上下文推断 也就是方法的参数或局部变量类型必须为 Lambda 对应的接口类型,才能使用 Lambda 作为该接口的实例。
Lambda 表达式的 标准格式 :(参数类型 参数名称) -> { 代码语句 }
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
public class Demo1 {
public static void demo1(MyFunctionalInterface functionalInterface) {
functionalInterface.myMethod();
}
public static void main(String[] args) {
demo1(() -> {
System.out.println("HelloWorld");
});
}
}
在一些特定的情况下 lambda 表示可以进行简写(即省略某些声明),其简写规则如下:
小括号内参数的类型可以省略;
如果小括号内 有且仅有一个参 ,则小括号可以省略;
如果大括号内 有且仅有一个语句 ,则无论是否有返回值,都可以省略大括号、return 关键字及语句分号。
案例代码
@FunctionalInterface
public interface UserDao {
void add(String uname);
}
public class Demo2 {
public static void demo1(UserDao userDao){
userDao.add("小黑");
}
public static void main(String[] args) {
//->后面的代码就是 add 方法的具体实现
demo1(uname-> System.out.println(uname));
}
}
很明显,因为 Lambda 表达式需要借助于类型推导(也可以叫上下文推导),所以它只适用于接口中只有一个抽象方法的情况(即函数式表达式)。它无法完全代替内部类和匿名内部类,最多算是匿名内部在特定条件下的一种简写方式
方法引用应该是在 JDK1.8 新增的概念了,它采用的是操作符是::
双冒号运算操作符是类方法的句柄,Lambda 表达式的一种简写,这种简写的学名叫 eta-conversion 或者叫η-conversion。
感觉有点儿像其他语言中接口的委托实现
方法引用的格式为:<ClassName | instance>::<MethodName>
至于为什么要使用这个方法引用,我们来看一段代码,代码仅为演示使用没有实际的业务意义
@FunctionalInterface
public interface UserDao {
void add(String uname);
}
public class Demo {
public static void testQuote(UserDao userDao){
userDao.add("小明");
}
public static void main(String[] args) {
testQuote(uname->{
// 此处为新增用户的代码
// 数据校验
// 事务处理(新增用户可能用不到事务,这里仅仅是举例)
// 数据联动处理...... 以下省略
});
}
}
代码解析
在上面的代码片段中我们可以明确的看到 Lambda 表达式内有一大堆的代码,无论从阅读性还是从代码的简洁性上来讲都非常差
我们变成方法引用的方式再实现一遍
public class Demo3 {
public static void testQuote(UserDao userDao){
userDao.add("小明");
}
public static void quoteAdd(String uname){
// 此处为新增用户的代码
// 数据校验
// 事务处理(新增用户可能用不到事务,这里仅仅是举例)
// 数据联动处理...... 以下省略
}
public static void main(String[] args) {
testQuote(Demo3::quoteAdd);
}
}
代码解析
效果很明显,只是将实现的方法单独的抽离出去了
main 方法中引用 quoteAdd 方法的采用的是静态方法的处理
静态方法和实例方法的引用方式
@FunctionalInterface
public interface UserDao {
void add(String uname);
}
// 用于演示实例方法引用
public class Quote {
public void addImpl(String uname) {
System.out.println(uname);
}
}
// 用于演示静态方法引用
public class StaticQuote {
public static void addImpl(String uname) {
System.out.println(uname);
}
}
public class Demo2 {
public static void demo1(UserDao userDao) {
userDao.add("小黑");
}
public static void main(String[] args) {
// 具体引用代码
demo1(new Quote()::addImpl);
demo1(StaticQuote::addImpl);
}
}
下面的代码演示了由匿名内部类 -->Lambda 表达式 -->方法引用的各个方式
// 函数式接口
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
public class Demo1 {
public static void demo1(MyFunctionalInterface functionalInterface) {
functionalInterface.myMethod();
}
public static void main(String[] args) {
demo1(new MyFunctionalInterface() {
@Override
public void myMethod() {
System.out.println("匿名内部类");
}
});
demo1(()->{
System.out.println("Lambda 表达式");
});
demo1(Demo1::myMethod);
}
private static void myMethod() {
System.out.println("方法引用");
}
}