Java用一种特殊(
有
且
仅有
一个方法)的接口变量,作为lambda表达式的赋值对象/数据容器。
interface IMove{
double move(double speed, int seconds);
这种接口被称之为函数(式)/功能(性)接口。
@试一试@:接口中添加一个方法声明?
演示:F3到Comparator,以下方法不算作接口方法:
default void stop() {}
static void stop() {}
重写Object中已有的方法
String toString();
函数式接口也可以被当做普通接口使用。
PS:@FunctionalInterface注释
lambda表达式表示函数式接口的实例:
IMove m = (s, t) -> s * t;
Java中lambda使用的是单横线箭头(->)
System.out.println(m.move(2.5, 60)); //实例调用其接口方法
演示:其他规则和JavaScript箭头函数一样
一般情况下,lambda类型可以由编译器推断。@想一想@:为什么可以推断?
什么是特殊情况呢?
Lambda表达式最重要的作用就是作为函数参数传递。假设有两个重载的方法,
static void goSchool(IMove move) {
static void goSchool(IWalk walk) {
使用了类似的函数式接口:
interface IWalk { //和IMove类似
double move(float speed, long seconds);
演示:编译时错误
goSchool((s,t)->s*t);
解决方案:
显式声明参数类型
//要声明参数类型,所有参数的类型就都要声明
goSchool((double s,int t)->s*t);
先声明Lambda变量类型
IMove m = (s, t) -> s * t;
goSchool(m);
通过强制类型转换声明lambda表达式类型:
goSchool((IMove)(s, t) -> s * t);
作用域和闭包:移到函数式编程
final修饰
Java允许在lambda参数前加final,以禁止lambda表达式内部修改该参数。
但如果这样,需要显式的声明参数类型:
IMove m = (final double s, int t) -> {
s *= 2; //error
return s * t;
函数式接口可以是泛型的:
interface IMove<T> {
T move(T speed, int seconds);
PS:以上对应C#中delegate
Lambda表达式可以视为一种匿名函数。
那如果我们需要传递一个已有的命名函数呢?比如把这个方法(注意是方法本身,不是方法运行结果)传递给IMove,IMove move = ??
static double walk(double speed, int seconds) {
return speed * seconds;
这时候就需要操作符为双冒号(::)的方法引用了:
IMove move = Main::walk; //静态方法由类名调用
System.out.println(move.move(23.5, 2));
除了静态方法以外,还有:
实例方法和属性
class Person {
public double walk(double speed, int seconds) {
IMove m = new Person()::walk;
m.move();
class Person {
public Person() {
interface IMove {
Person getPerson();
IMove m = Person::new;
Person fg = m.getPerson();
interface IMove {
int[] get(int length); //注意这个length参数
IMove m = int[]::new;
int[] students = m.get(10); //得到一个长度为10的数组
类::实例方法
interface IMove{
void move(Person person); //第一个参数是Person
class Person{
public void walk() { //这是一个实例方法
IMove m = Person::walk; //但仍然可以用类名调用
//等价于:IMove m = p->p.walk();
m.move(new Person());
当(一定要注意这个当)接口方法有参数时,可以用它的第一个(且只能是第一个)参数类型,后加双冒号(::)指定它的某一个匹配实例方法。(我个人不喜欢这种写法,宁愿用箭头,避免和静态方法混淆)
System.out.println(greaterThan0.test(100));
另外三个default方法,套路和Consumer的andThen()一样。为了演示,添加一个:
Predicate<Integer> lessThan100 = i -> i < 100;
@想一想@:搞这么一个方法干嘛?直接用 == 不香么?
Predicate.<Integer>isEqual(32).or(greaterThan0).test(new Integer(22))
演示说明:and(Predicate<? super T> other)
Predicate<Student> greaterThan0 = i -> i.Age > 0;
Predicate<Person> lessThan100 = i -> i.Age < 100;
//假如这样可以允许的话
//Predicate<OnlineStudent> lessThan100 = i -> i.Fee < 100;
Student fg = new Student();
* 1. 在test的时候,greaterThan0和lessThan100都使用的fg
* 2. 我们只能控制and()中的lambda泛型参数,
* 3. 确保其至少是Student类,这样predict中使用的都是Student有的类成员
greaterThan0.and(lessThan100).test(fg);
Function
“标配的”apply()方法
两个default方法,在当前function
之后被调用:andThen(),以当前function的返回值为输入
之前被调用:compose(),运行结果作为以当前function的输入
Function<Person, Integer> getAge = p -> p.age;
System.out.println(getAge
//指定i 类型方案1: .compose((Integer i) -> new Person(i))
//指定i 类型方案2: .<Integer>compose(i -> new Person(i))
//还可以使用构造函数方法引用
.<Integer>compose(Person::new)
.andThen(a-> a*0.9)
.apply(40));
演示:执行顺序 apply() -> compose() -> getAge -> andThen()
注意练习阅读其源代码。
还有更多的function(函数接口名),可以适用于更多的情形:
参数或返回值为基本类型的:
添加输入参数类型前缀,比如:DoubleConsumer、IntConsumer、LongPredicate……
添加输出参数和返回类型前缀,比如:IntToDoubleFunction、DoubleUnaryOperator
作用:减少装箱拆箱,可以些许的提高性能
输入参数为2个的:
引用类型(泛型)添加bi前缀,比如:BiConsumer<T, U>、BiFunction<T, U, R>、BiPredicate<T, U>……
基本类型添加To前缀,比如:ToDoubleBiFunction、ToIntBiFunction<T, U>……