目录
什么是函数式接口(传递行为,而不仅仅是值):
- 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口。
- 我们在某个接口上申明了 java.lang.FunctionalInterface注解,那么编译器会按照函数式接口的定义来要求该接口。
- 如果某个接口只有一个抽象方法,但是我们并没有给该接口申明 java.lang.FunctionalInterface注解,那么编译器依然会将该接口看作是函数式接口。
函数式接口申明注解(java.lang.FunctionalInterface)。这是一种声明式注解,用于去表示某一个接口的类型的申明用来表示函数式接口,该函数式接口是由java语言来规范定义的。
从概念上来说,函数式接口只有一个抽象方法。因为 java.lang.reflect.Method#isDefault() 方法有一个默认的实现,所以它们不是抽象的,如果接口声明了一个抽象方法,并且重写了 java.lang.Object 的任何一个 public 的方法,则该方法也不计入接口的抽象方法计数,因为接口的任何实现都会有 java.lang.Object 类的实现或其他地方的实现。
需要注意的是,函数式接口的实例可以使用 lambda 表达式,方法引用或者构造方法引用来创建。
如果使用了该注解类型对一个类或接口进行标记,如果不满足以下条件,则编译器需要生成错误信息:
- 类型是一个接口类型,但是并不是注解类型,枚举或者类。
- 被注解的类型满足了函数式接口的要求。
但是,编译器将把满足函数式接口定义的任何接口都视为函数式接口,而不管接口中声明是否存在该注解。lambda 是一个直接继承至object的一个类。是由动态字节码生成的,有点类似于动态代理。
自定义函数式接口的申明方式:
// 如果接口声明了一个抽象方法,并且重写了 java.lang.Object
// 的任何一个 public 的方法,则该方法也不计入接口的抽象方法计数
// 对于该说明的演示内容
// 这是一个标准的函数式接口, 这里如果不声明 @FunctionalInterface 注解,它依然也会被编译器认为
// 是一个函数式接口
@FunctionalInterface
public interface TestInterface {
void test();
}
// 编译报错, 报错的内容为:
//Multiple non-overriding abstract methods found in interface TestInterface
@FunctionalInterface
public interface TestInterface {
void test();
void test2();
}
// 需要注意这种情况的存在,如果接口中重写了 java.lang.Object 的任何一个 public
// 的方法,那么该方法不会被纳入计数的,因为子类会实现接口中的所有抽象方法,
// 在java语言中,如果接口中的方法与 object 的 public 方法名字一样的话,默认是
// 认为直接继承至 object 的,所以不会被纳入计数
@FunctionalInterface
public interface TestInterface {
void test();
String toString();
}
lambda表达式的组成:
lambda 由 参数、箭头、主体组成,即结构为:
lambda表达式的结构:
- 一个 lambda 表达式可以拥有零个或多个参数
- 参数类型可以明确声明,也可以根据上下文来进行类型的推断。
- 所有参数需要包含在圆括号内,参数与参数之间用逗号相隔。
- 空的圆括号代表参数集为空
- 当只有一个参数,且其类型可以推断的时候,圆括号可以省略。
- lambda 表达式的主体可以包含零个或多个语句。
- 如果 lambda 表达式的主体只有一条语句,那么花括号也可以省略。 匿名函数的返回类型与该主体表达式一致。
- 如果 lambda 表达式的主体包含一条以上的语句,则表达式需要包含在花括号之中,形成代码块。匿名函数的返回类型与代码块返回类型一致,如果没有则不返回。
lambda 最基本的声明方式
public class Demo {
public static void main(String[] args) {
// lambda 表达式的演变过程
// 一个函数式接口可以通过匿名的方式进行声明, 当然这样的方式过于笨重
TestInterface test = new TestInterface() {
@Override
public void test() {
System.out.println("456");
}
};
// 需要注意的是匿名函数可以通过这种方式重写任意多个方法, 而 lambda 只能处理函数式接口
// 下方代码中, 等号右边的第一个小括号代表的是参数, 因为函数式接口并不需要接收参数, 所以这里是个空的小括号
// 主体为箭头右边的内容, 这里只是一行输出语句
TestInterface testInterface = () -> {
// 主体是需要包含在两个大括号之中的, 当然主体如果只有一行, 那么可以有简写形式
System.out.println("456");
};
m(testInterface);
}
// 定义一个方法, 接收参数为一个行为
public static void m(TestInterface testInterface) {
testInterface.test();
}
}
@FunctionalInterface
interface TestInterface {
void test();
}
关于表达式结构的一些示例说明
public class Demo {
public static void main(String[] args) {
// 一个 lambda 表达式可以拥有零个或多个参数, 当然参数的个数是根据函数式接口中函数式方法参数个数来决定的
TestInterfaceByOne one = (String o1) -> {
return o1;
};
System.out.println(one.test("hello world"));
TestInterfaceByTwo two = (String o1, String o2) -> {
return o1 + o2;
};
System.out.println(two.test("hello world", "123"));
// 参数类型可以通过上下文进行推断, 也就是说下面这种方式也是可以被正确识别的, 因为参数类型是是固定的 string
two = (o1, o2) -> {
return o1 + o2;
};
// 可是这么书写就会报错, 因为无法通过上下文信息去推断出 o1 o2 到底是什么类型
// (o1, o2) -> { return o1 + o2; };
// 如果是只有一个参数, 那么可以以这种方式进行书写, 也就是说可以省略小括号(但是多个参数不行)
one = o1 -> {
return o1;
};
// 如果 lambda 表达式的主体只有一条语句,那么花括号也可以省略。 匿名函数的返回类型与该主体表达式一致。
// 也就是说, 上方的 one 和 two 可以改造为以下形式
two = (o1, o2) -> o1 + o2;
one = o1 -> o1;
}
}
@FunctionalInterface
interface TestInterfaceByOne {
String test(String one);
}
interface TestInterfaceByTwo {
String test(String one, String two);
}
在 jdk 的内置工具中,有四大内置最核心的函数式接口以及基于他们衍生的一些很常用的函数式接口。jdk 内置工具会大量用到这些函数式接口。详情请看下一篇文章: