4

1、前言

在java8中,增加了函数接口、Lambda和方法引用,使得创建函数对象变得很容易高效,本文通过情景引入,具体说明一下使用Lambda表达式创建接口对象是如何提高编程效率的。当然文中的所涉及的并非Lambda的全部使用场景。

2、代码情景导入

我们已有共识:Java是面向对象的编程语言,如果我们调用任何一个对象的方法,并往方法中传递参数,参数必须是类或接口的对象或者是基本类型变量。
按照这个共识,我们看下面的代码:

//定义一个接口
interface Example{
    //接口中只定义了一个抽象方法
    void add(Integer a,Integer b);
}
//接口的实现类
class ExampleImpl implements Example {
    @Override
    public void add(Integer a, Integer b) {
        System.out.println("a+b=" + (a + b));
    }
}

//测试类
public class TestLambda {
    //测试类中定义了一个方法,并规定了接收参数的类型
    public void test(Example example) {
        example.add(1, 1);
    }
    public static void main(String[] args) {
        //声明一个接口类型的变量并通过接口实现类实例化
        Example example = new ExampleImpl();
        //main方法中调用自己类的其他方法必须先创建该类的对象,通过对象调用
        TestLambda testLambda = new TestLambda();
        //调用test,并把实例好的对象传给test
        testLambda.test(example);    
    }
}

本地运行,控制台显示结果为:a+b=2。当然结果不重要。看懂了上述代码,思考一下,代码可否精简,提高执行效率。
观察得出:

  1. 接口的意义好像不是很大,能否去掉,直接定义Example类?呵呵,去掉了我们后面的研究没意义了。
  2. 实现类在本例中只用了一次,可否用匿名类替换?——可以

接下来在上面的代码基础上,我们使用匿名类替换实现类,使得代码进一步精简,代码如下:

//定义一个接口
interface Example{
    //接口中只定义了一个抽象方法
    void add(Integer a,Integer b);
}
//去掉了接口的实现类

//测试类
public class TestLambda {
    //测试类中定义了一个方法,并规定了接收参数的类型
    public void test(Example example) {
        example.add(1, 1);
    }
    public static void main(String[] args) {
        //main方法中调用自己类的其他方法必须先创建该类的对象,通过对象调用
        TestLambda testLambda = new TestLambda();
        //调用test,并把Example类型的匿名类对象传给方法
        testLambda.test(new Example(){        
            @Override
            public void add(Integer a, Integer b) {
                System.out.println("a+b=" + (a + b));
                
            }
        });    
    }
}

使用匿名类,我们精简了代码,还能不能更加精简呢?
匿名类满足了传统面向对象的设计模式对函数对象的需求,但是,匿名类的繁琐使得Java中进行函数编程的前景变得十分黯淡。我们平时的方法函数如同简单函数f(x),像这种f(t(x))的复合函数不常见。所谓函数式编程就是将函数(一段操作)作为一个基本单位进行传递。以前的Java中参数只能是具体的变量,函数式编程打破这一规范,可以将整个方法作为一个参数传递。
下面我们使用Lambda表达式,进一步精简代码:

//定义一个接口
interface Example{
    //接口中只定义了一个抽象方法
    void add(Integer a,Integer b);
}
//去掉了接口的实现类

//测试类
public class TestLambda {
    //测试类中定义了一个方法,并规定了接收参数的类型
    public void test(Example example) {
        example.add(1, 1);
    }
    public static void main(String[] args) {
        //main方法中调用自己类的其他方法必须先创建该类的对象,通过对象调用
        TestLambda testLambda = new TestLambda();
        //调用test,并把Lambda表达式传给方法
        testLambda.test((a,b)->System.out.println("a+b=" + (a + b)));    
    }
}

Lambda表达式代码:
(a,b)->System.out.println("a+b=" + (a + b))
最终执行效果同上。这样我们通过使用Lambda表达式达到了代码干净简洁,执行效率高效的目的。

3、反思

我们先后通过匿名类和Lambda表达式对代码重构,使得代码精简,使用匿名类容易理解,但是使用Lambda表达式感觉与我们开头的共识向左,原因是什么呢?这个表达式难道就是符合类型要求的对象?这个表达式究竟是如何起作用的?我想大家一定存在很多疑问。
注意观察:定义的接口只有一个抽象方法。
疑问答案:在Java8中(向后兼容),形成了“带有单个抽象方法的接口是特殊的,值得特殊对待”的观念,这些接口被称为函数接口,Java允许利用Lambda表达式创建这些接口的实例。Lambda类似于匿名类的函数,但是比它简洁更多。Lambda表达式被称为函数对象。
类型推导:编译器利用一个被称为类型推导的过程,根据上下文推断出类型。首先代码案例中,testLambda.test()方法唯一(TestLambda类中没有重载的test方法),编译器会强制认为testLambda.test()方法中的实际参数类型即Lambda表达式类型为定义的形式参数类型Example,又由于接口只有一个抽象方法,Lambda表达式自然只能实现该抽象方法,Lambda表达式表达式中的参数类型自然就是接口中抽象方法的参数类型。

4、总结

能写成Lambda的接口的条件:接口中有且仅有一个抽象方法。如本案例中的接口Example,如果出现多个抽象方法,编译器的类型推导会出错。

函数式接口可以使用(不强求)注解@FunctionalInterface 进行强制规范
image.png
更多Lambda语法知识,可参考官方文档。


BotBot
162 声望8 粉丝

时机未遇,耐心等待,厚积薄发。