A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 大山哥哥 于 2016-7-29 20:42 编辑

  • 引言
    大家的安卓基础班课程是基于jdk7的,但是在每个学生开班入学的时候电脑上几乎都是安装的jdk8,因为大家总觉得新版的就是最好的,但其实这种认识是错误的。一门技术从出现到成熟再到普遍使用是要经历很长时间的,比如jdk6是在2006年12月12日发布的,而它真正的被在开发当中广泛被采用也是近几年的事情了,因为毕竟刚刚出现的技术可能会存在很多的bug以及不兼容因素,需要经历一段时间去发现和解决,最终达到一个成熟的版本而被广大技术人员所应用。同样的道理,jdk8也是这样的,但是jdk8早晚会有被广泛应用到实际项目中去的一天,所以接下来我就为大家来讲解一下jdk8中最大的卖点 - Lambda表达式。
  • 背景    
    Lamda是一种基于函数的编程语言(也就是咱们课上讲面想对象时候说的面向过程语言),对于这种类型的语言典型的代表就是Haskell,但是Java是面向对象编程语言,一切的操作都是必须有类,所有的功能都必须定义在类之中,所以这就造成了一点弊端,也就是很多习惯于函数编程的开发者就觉得Java不好用,但也并不是说Java中没有基于函数编程的语法体现,只不过稍微麻烦些,那么他的实现模式就是匿名内部类(就是咱们说的接口的匿名的子类对象,可以不用创建类,就能创建出一个接口的子类对象),于是在Java诞生20年之后,Java终于推出了Lamda表达式,其最要的目的是解决匿名内部类的书写麻烦问题,而且由于能够在纯Java语言环境中提供一种优雅的方式来支持函数式编程,也使它具有吸引越来越多程序员到Java平台上的潜力。
  • 案例演示
    首先我们在讲解Lamda表达式之前,先来回顾一下我们的匿名内部类如何定义?请看下面的代码。   
[Java] 纯文本查看 复制代码
package com.heima.lamda;
interface Inter{                                 //如果使用匿名内部类必须先存在一个接口
        public void print(String str);    //使用匿名内部类实现的接口,一般里面只有一个方法
}
public class Demo {
        public static void main(String[] args) {
                Inter it = new Inter() {           //使用匿名内部类,让父类的引用指向子类对象
                        public void print(String str) {    //重写接口中的方法
                                System.out.println(str);
                        }
                };  
                it.print("helloWorld");            //父类的引用调用子类重写后的方法
        }
}
以上代码就是在jdk1.8之前我们写匿名内部类的代码,其实上面的代码我们真正需要的就是一个输出语句,却写了这么一大堆的代码。 而且这也是我们写的最简单的代码了,因为要想利用一个接口里面的方法去打印一个HelloWorld,只能先创建一个类实现接口并重写其中的方法,然后创建该类对象,从而调用方法进行打印,这时候匿名内部类就是我们能写出的最精简的代码了,这是由于java之中类结构的强制限制,所以很多人就觉得代码过于麻烦了。但是Lambda表达式的出现很好的解决了这一问题,那么下面我们就用Lambda表达式来演示上面的案例:
[Java] 纯文本查看 复制代码
package com.heima.lamda;
interface Inter{                                      //Lambda表达式只试用与实现接口的匿名内部类
        public void print(String str);        //Lambda表达式要求接口里面必须必须只能有一个抽象方法
}
public class Demo {
        public static void main(String[] args) {
                Inter it = (s) -> System.out.println(s);//Lambda表达式 ,s就是接口里的方法参数              
                it.print("helloWorld");               //父类的引用调用子类重写后的方法
        }
}
       首先,先不看语法,至少通过上面的程序我们发现,使用了Lambda表达式,这个语句少了,更简单了,没有了类结构的控制。      那么上面整个实现的Lambda表达式语句是:  (s) -> System.out.println(s);   
  • Lambda格式
Lambda表达式的结构是:   (参数)  ->  函数体;
  • 参数:参数名可以随便起名字,毕竟是标示符嘛,他的类型与Inter接口定义的print(String str)方法里面的参数类型一致,但是此处没有必要进行声明,因为这个参数类型是由编译器推测出来的。同时,你也可以直接给出参数的类型,也就是:
           (String s) -> System.out.println(s);          
  • ->   :     是一个固定语法,表示将参数指向方法体。
  • 函数体:即方法体,就是以前编写在匿名内部类实现方法里面的方法体。
  • 注意事项:
  1.  在某些情况下lambda的函数体会更加复杂,这时可以把函数体放到在一对花括号中,就像在Java中定义普通函数一样。例如:
                                     (s) -> {
                                           System.out.println(s);
                                           System.out.println(s+"HelloWorld");
                                     };                               
  2.  Lambda表达式中的函数体也就是匿名内部类中的方法体, 所以在Lambda表达式的方法体中使用局部变量,局部变量前面也必须加final修饰,而再jdk8中如果这些变量不是final的话,它们会被隐含的转为final,这样效率更高
  3.  有时候接口中的抽象方法会有返回值,Lambda也就会返回一个值。返回值的类型也是由编译器推测出来的。如果lambda的函数体只有一行的话,那么没有必要显式使用return语句。下面两个代码片段是等价的:
[Java] 纯文本查看 复制代码
package com.heima.lamda;
interface Inter{                                                                
        public String getString(String str);                                
}
public class Demo {
        public static void main(String[] args) {
                Inter it = (s) -> s;               
                String str = it.getString("hello");
                System.out.println(str);    //打印出 hello
        }
}
上面的代码和下面的代码是等价的
[Java] 纯文本查看 复制代码
package com.heima.lamda;
interface Inter{                                                                
        public String getString(String str);                                
}
public class Demo {
        public static void main(String[] args) {
                Inter it = new Inter() {                
                        public String getString(String str) {        
                                return str;
                        }
                };                
                String str = it.getString("hello");
                System.out.println(str);    //打印出 hello
        }
}
   4.   Lambda表达式只适用于 接口的匿名内部类,而且接口中必须有且只有一个抽象方法

  • Functional接口
       我们了解到Lambda表达式只适用于接口中有且只有一个抽象方法的情况,那如果再开发过程中有开发者不小心给接口添加了多个抽象方法,那么就不再支持Lambda了,语言设计者投入了大量精力来思考如何使现有的函数友好地支持lambda。最终采取的方法是:增加函数式接口的概念。函数式接口就是一个具有一个方法的普通接口。像这样的接口,可以被隐式转换为lambda表达式。java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的两个例子。在实际使用过程中,函数式接口是容易出错的:如有某个人在接口定义中增加了另一个方法,这时,这个接口就不再是函数式的了,并且编译过程也会失败。为了克服函数式接口的这种脆弱性并且能够明确声明接口作为函数式接口的意图,Java 8增加了一种特殊的注解@FunctionalInterface(Java 8中所有类库的已有接口都添加了@FunctionalInterface注解)。让我们看一下这种函数式接口的定义
[Java] 纯文本查看 复制代码
package com.heima.lamda;
@FunctionalInterface
interface Inter{                                                                
        public String getString(String str);        
}
把这种注解写在接口上面,当开发人员想继续写抽象方法的时候,Eclipse就会报红色波浪线提示编译失败,这和@Override注解有些类似,下面我们也举例说明@Override注解:
[Java] 纯文本查看 复制代码
interface Inter{                                                                
        public String getString(String str);        
}
public class Demo implements Inter{
        @Override
        public String getString(String str) {
                return str;
        }        
}
将@Override注解写在重写的方法的上面,如果你在书写方法声明的时候有一点和被重写的方法不一样的地方,Eclipse也会立刻报出红色波浪线提示编译失败。
  • 总结
毫无疑问,jdk8发行版是自jdk5(发行于2004,已经过了相当一段时间了)以来最具革命性的版本。jdk8为Java语言、编译器、类库、开发工具与JVM(Java虚拟机)带来了大量新特性。在这篇教程中,我只介绍了jdk8最大的亮点Lambda表达式,以及为Lambda表达式专门设计的函数式接口。希望对大家有所帮助,如果大家对这篇教程感兴趣的话,敬请阅读大山哥哥的下一篇教程吧~










点评

给力,基础班学习中,向你看齐  发表于 2016-8-2 22:47

评分

参与人数 2技术分 +20 黑马币 +20 收起 理由
济南王昭珽 + 10 + 10 很给力!
小齐姐姐 + 10 + 10 很给力!

查看全部评分

31 个回复

倒序浏览
cat73 黑马帝 2016-7-29 21:29:37
沙发
本帖最后由 cat73 于 2016-7-29 21:53 编辑

抢沙发0.0
已报名济南 JavaEE 基础班0.0

其实例子里的 print 还可以写的更简单一些,局部变量可以省略,参数类型的括号也可以省略。
[Java] 纯文本查看 复制代码
((Inter) s -> System.out.println(s)).print("helloWorld");


附上 Oracle 的 Java 规范中对 Lambda 表达式的说明供大家参考:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27


回复 使用道具 举报
给力
回复 使用道具 举报
zhaosenyang 来自手机 黑马粉丝团 2016-7-29 21:40:43
板凳
赞一个^_^
回复 使用道具 举报
大贤哥就是如此给力!已收藏,期待续集。。。
回复 使用道具 举报
cat73 发表于 2016-7-29 21:29
抢沙发0.0
已报名济南 JavaEE 基础班0.0

谢谢哈~。这样写的话 ((Inter) s -> System.out.println(s)).print("helloWorld");,只要是怕学生们看不太懂,毕竟这只是针对没有学完或者刚刚学完java基础的同学写的,我想尽可能的易懂一些,谢谢您附上的链接~也替学员们谢谢您~
回复 使用道具 举报
cat73 发表于 2016-7-29 21:29
抢沙发0.0
已报名济南 JavaEE 基础班0.0

谢谢哈~。这样写的话 ((Inter) s -> System.out.println(s)).print("helloWorld");,只要是怕学生们看不太懂,毕竟这只是针对没有学完或者刚刚学完java基础的同学写的,我想尽可能的易懂一些,谢谢您附上的链接~也替学员们谢谢您~
回复 使用道具 举报
cat73 黑马帝 2016-7-29 22:04:41
8#
大山哥哥 发表于 2016-7-29 22:01
谢谢哈~。这样写的话 ((Inter) s -> System.out.println(s)).print("helloWorld");,只要是怕学生们看不 ...

其实一直想去研究下 Java8 的Lambda 表达式怎么用 0.0
然而一直就没去。。。
借着这个贴做引子跑去看了看 Java 规范 0.0
然后就把这个学惹 0.0

讲的挺容易懂的 0.0
成功把窝忽悠入坑惹 0.0

回复 使用道具 举报
cat73 黑马帝 2016-7-29 22:42:53
9#
本帖最后由 cat73 于 2016-7-29 23:21 编辑
大山哥哥 发表于 2016-7-29 22:01
谢谢哈~。这样写的话 ((Inter) s -> System.out.println(s)).print("helloWorld");,只要是怕学生们看不 ...

可以顺便写写 :: 用法的说明咩 0.0
感觉不太能理解这种用法 0.0

可以看到下面的测试代码里,test 方法的第一个参数要的是 Print,然而我传进去的东西跟 Print 接口一点关系都没有。
然而结果为,编译正常,运行正常,成功输出了 test。
要非得说有什么相同点,那大概就是参数完全一致了。
[Java] 纯文本查看 复制代码
public class LambdaTest {
    public static void main(String[] args) {
        test(PrintObj::renamePrint, "test");
    }
    
    public static void test(Print print, String s) {
        print.print(s);
    }
    
    public static class PrintObj {
        public static void renamePrint(String str) {
            System.out.println(str);
        }
    }

    @FunctionalInterface
    public static interface Print {
        void print(String str);
    }
}

=========== 更新 ===========
看 javap 的结果,似乎是跟 Lambda 表达式的编译结果是一样的:
[Java] 纯文本查看 复制代码
invokedynamic #2,  0

它似乎是用这个静态方法的方法体去构造了一个 Lambda 表达式。

我连续调用了两次 test(PrintObj::renamePrint, "test"),并在 test 里增加了输出 print 的 hashCode 的代码,发现两次调用的输出是不同的,也就是说它构造了两个对象。

相关规范似乎在这里:https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13
困了。。明个看看吧。。。
回复 使用道具 举报
大赞,给力!
回复 使用道具 举报
太棒了,对于俺这样的小白真的是福音
回复 使用道具 举报
cat73 发表于 2016-7-29 22:42
可以顺便写写 :: 用法的说明咩 0.0
感觉不太能理解这种用法 0.0

你发的有点不懂。。。你这么牛为啥还要报基础班,技术分都这么高了
回复 使用道具 举报
该用户名已注册 来自手机 中级黑马 2016-7-30 08:57:14
13#
这么好的知识点,我们保存一下吧
回复 使用道具 举报
清风幕竹 来自手机 中级黑马 2016-7-30 09:02:12
14#
哎呦。不错哦!
回复 使用道具 举报
棒棒哒。。。。大赞
回复 使用道具 举报
josecheng 来自手机 初级黑马 2016-7-30 10:00:11
16#
楼主好人
回复 使用道具 举报
好好好好好
回复 使用道具 举报
贤哥666666
回复 使用道具 举报
顶一下,大山哥哥,加油吧
回复 使用道具 举报
马克一下
回复 使用道具 举报
12下一页
您需要登录后才可以回帖 登录 | 加入黑马