【黑马程序员济南中心】JDK1.8新特性Lambda表达式和Fu
引言
大家的安卓基础班课程是基于jdk7的,但是在每个学生开班入学的时候电脑上几乎都是安装的jdk8,因为大家总觉得新版的就是最好的,但其实这种认识是错误的。一门技术从出现到成熟再到普遍使用是要经历很长时间的,比如jdk6是在2006年12月12日发布的,而它真正的被在开发当中广泛被采用也是近几年的事情了,因为毕竟刚刚出现的技术可能会存在很多的bug以及不兼容因素,需要经历一段时间去发现和解决,最终达到一个成熟的版本而被广大技术人员所应用。同样的道理,jdk8也是这样的,但是jdk8早晚会有被广泛应用到实际项目中去的一天,所以接下来我就为大家来讲解一下jdk8中最大的卖点 - Lambda表达式。
背景
Lamda是一种基于函数的编程语言(也就是咱们课上讲面想对象时候说的面向过程语言),对于这种类型的语言典型的代表就是Haskell,但是Java是面向对象编程语言,一切的操作都是必须有类,所有的功能都必须定义在类之中,所以这就造成了一点弊端,也就是很多习惯于函数编程的开发者就觉得Java不好用,但也并不是说Java中没有基于函数编程的语法体现,只不过稍微麻烦些,那么他的实现模式就是匿名内部类(就是咱们说的接口的匿名的子类对象,可以不用创建类,就能创建出一个接口的子类对象),于是在Java诞生20年之后,Java终于推出了Lamda表达式,其最要的目的是解决匿名内部类的书写麻烦问题,而且由于能够在纯Java语言环境中提供一种优雅的方式来支持函数式编程,也使它具有吸引越来越多程序员到Java平台上的潜力。
案例演示
首先我们在讲解Lamda表达式之前,先来回顾一下我们的匿名内部类如何定义?请看下面的代码。
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表达式来演示上面的案例:
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语句。下面两个代码片段是等价的:
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
}
}
上面的代码和下面的代码是等价的
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注解)。让我们看一下这种函数式接口的定义
package com.heima.lamda;
@FunctionalInterface
interface Inter{
public String getString(String str);
}
把这种注解写在接口上面,当开发人员想继续写抽象方法的时候,Eclipse就会报红色波浪线提示编译失败,这和@Override注解有些类似,下面我们也举例说明@Override注解:
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表达式专门设计的函数式接口。希望对大家有所帮助,如果大家对这篇教程感兴趣的话,敬请阅读大山哥哥的下一篇教程吧~