java 8新特性lambda表达式优劣浅谈

最近学习了lambda表达式的用法,就把自己的小项目中所有用到接口回调的地方全都用上了lambda表达式,代码的确精简了不少,不仅是接口回调处,由于其参数类型推断,还减少了不少import语句。

虽然让代码风格更趋向极简,但是很难说lambda表达式就一定优于传统的接口回调语法。理由如下:

1.接口回调为什么而产生?是因为我们要在代码执行的特定时候,调用方要动态的插入一段代码在调用的方法中间而用。这在事件监听中用的极其广泛,在Android中最典型的例子就是View的点击事件监听。所以说,创建接口,在方法的定义中用到接口的实例作为形式参数,以及在调用方法时把接口的实例作为实际参数传入,并重写接口的方法,这一整套步骤是密不可分的,而lambda表达式并不是利用了一个新的方式实现了上述功能,而仅仅只是把最后一步的使用接口的实例作为实参传入并重写接口方法这一个步骤简写,显然,这就导致了整个过程的不统一,这么说有点抽象,具体来收就是,使用lambda表达式,一直作为重要角色的接口名就变得不可见了,接口内部的抽象方法名也变的不可见了,lambda表达式的使用者仍然需要知道接口名和其接口方法名,但是不用写出来,这就变的有点奇怪,因为你在使用lambda表达式时仍然需要前一个步骤,所以从步骤上来看,lambda表达式让理解变的复杂了,使思维步骤多了一步,而减少的仅仅是表现出来的代码量。

2.类型推断让代码可读性变差,举个例子,Android中的View点击事件监听,需要传入OnClickLinstener接口的实例作为参数,这个接口中仅有一个抽象方法需要重写,这个抽象方法也仅仅只有一个参数,于是代码就变成了如下形式:

mImageView.setOnClickListener(v -> {

        });

由于抽象方法只有一个参数,所以v也不需要放在括号中,加上类型推断机制,v的类型也可以省去,这样一看,就有些不明所以,我们在这里一看就知道v的类型是View那是因为这是Android自己提供的API,所有Android开发者都烂熟于心,假如说你写了一个这样的代码,让一个完全不了解你的人去看,对方很可能对v的类型懵逼,因为对方刚拿到你的代码,并不知道你的代码这里是想做什么,因此,不去查找这个接口的定义,很难一眼看出来v的类型到底是什么。虽然lambda表达式的参数类型推断是可选的,也就是它允许你写上参数类型,但是既然它提供了推断的功能,我相信使用它的人为了尽可能好的使用它的特性,很多人都会不写参数类型。虽然影响并没有那么大,但大体来收参数类型推断功能确实在一定程度上降低了代码的可读性。

说到参数类型推断,大家有可能想到java 7中关于泛型的新语法,如下所示:

//java 7中的写法:
List<String> list1 = new ArrayList<>;
//java 7之前的写法:
List<String> list1 = new ArrayList<String>;

这里是类型推断的非常好的体现,在java 7以前的写法中,在构造方法中再写一遍String无疑是重复工作,这里将其去除,但丝毫不会影响代码的可读性。并且还有一个小小的一点大家可能没有察觉,在编译器看来,ArrayList<String>和ArrayList<Intger>是同一类型(这涉及到类型擦除),大家如果觉得有疑惑,可以用RTTI或者反射机制去验证这一点。

在java 7的写法中,List<String>和List<Intger>的对象实例化的时候,调用的是相同的构造方法。这就很好的反应了了上面这一点,不然如果一个调用new ArrayList<String>一个调用new ArrayList<Intger>,会产生这两个类型是不同的歧义。

虽然扯的有些远,但是大体上就是说,lambda表达式的类型推断和以前类型推断的应用相比显得不那么高明。

3.用途有些局限,lambda表达式只能用于一种情况,那就是仅能用在接口中,且仅仅只在接口中只有一个接口方法的时候有用。这显得有些鸡肋。比如说,匿名内部类其实也本来应该是一个实用lambda表达式的好地方,如下所示:

private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == FINISH) {
                sendPro();
            }
        }
    };

这是一个使用Handler的典型情况,由于它并不是一个接口,所以它就不能用lambda表达式的写法,不过这是情有可原的,因为普通的类,哪怕是抽象类中,往往不止一个方法可以被重写(无论是普通方法还是抽象方法)。

除此之外的缺点就是,当接口中有不止一个抽象方法的时候,lambda表达式也是不可用的。因为lambda表达式无法判断你的代码是要写在哪一个重写的抽象方法里。

综上所述,lambda表达式被这几条规矩一限定,可用的范围就非常的有限。

4.这条说一个lambda表达式的优点,那就是在lambda表达式中使用外部定义的局部变量的时候,局部变量不再必须是final型的,这大大提高了灵活性,值得赞扬。我还是举个例子:

//使用lambda表达式写法
int userId = comment.getUid();
mImageView.setOnClickListener(v -> {
            Intent intent;
            if (userId == preferences.getInt("uid", 0)) {
                intent = new Intent(mContext, MyInformationActivity.class);
            } else {
                intent = new Intent(mContext, UserInformationActivity.class);
                intent.putExtra("uid", userId);
            }
            mContext.startActivity(intent);
        });

//使用接口回调写法
final int userId = comment.getUid();
mImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent;
            if (userId == preferences.getInt("uid", 0)) {
                intent = new Intent(mContext, MyInformationActivity.class);
            } else {
                intent = new Intent(mContext, UserInformationActivity.class);
                intent.putExtra("uid", userId);
            }
            mContext.startActivity(intent);
            }
        });

这是一段真实的代码,userId不是一个全局变量,仅仅是一个定义在一个方法中的局部变量,如果要在重写的接口的抽象方法中使用它,在传统写法中,必须标注为final类型,但是lambda表达式就无需这样做,在有些时候,我们可能要重新给userId赋值,这在lambda表达式中就显得尤为有用。

通过以上的总结,lambda表达式有优点也有缺点,优点在于大大简化代码行数,使代码在一定程度上变的简洁干净,但是同样的,这可能也会是一个缺点,由于省略了太多东西,代码可读性有可能在一定程度上会降低,这个完全取决于你使用lambda表达式的位置所设计的API是否被你的代码的其他阅读者所熟悉。另外的优点,也是lambda表达式比较显眼的优点就是对外部定义的局部变量的使用更加灵活,想象一种极端情况,你的代码中有地方需要接口回调套接口回调,有可能套了好几层,虽然这种情况出现的概率比较低,但是一旦出现这种代码,lambda表达式的这个优点就到了大显身手的时机。虽然我说了,lambda表达式能用的地方非常有限,但是不得不否认,接口中只有一个抽象方法这种情况在接口回调中发生的概率绝对比接口中有多个抽象方法的概率高的多,所以,虽然使用情况很单一,但是能用到的次数却足够的多,如果你决定用lambda表达式替换你项目中接口回调的传统写法,你会发现,这样的情况非常多。

总而言之,接口回调和lambda表达式这两种写法各有优劣,java 8在出现lambda表达式以后不代表原先的写法不能再用了,所以如何选择适合项目的写法,全看各位开发者如何自己选择,现在多了一种写法可选,总归是一件好事。

后续:快过了一年了,这几天回过头来看看自己以前写的文章,感觉自己当初理解有限,过了一年许多想法都略显浅薄,不过博客作为记录学习之路上的一种载体,我也本来无意修改旧文章,之前看见通知里有一条评论说:lambda表达式可读性差是个笑话,但不知道为何点开文章却看不到评论。这里就当回复一下吧。

当初并不理解FP,而只是用lambda表达式,强行将lambda表达式使用在了OOP的程序当中,感到可读性变成是自然而然的,因为整个程序都不是函数式的。但自从使用了Kotlin以后,现在如果我重新编写一个程序,我会从一开始就利用函数式的整套思想来编写程序,例如原先的接口回调,我会在一开始就写成高阶函数,然后也会利用其它lamnbda的特性,例如将lambda表达式保存在变量当中传递。除此之外还有很多FP的新概念,如果程序一开始就是这样设计的,并且你使用的语言从一开始就支持函数式编程,例如Kotlin,Scala,那一切都将变得很自然,但是纯粹为了尝试这个新特性而将它强行加入到OOP的程序之中,可读性的降低也会变的自然而然。

    原文作者:Preacher_Qiao
    原文地址: https://blog.csdn.net/qq_28899635/article/details/53691986
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞