java8(又称 jdk1.8)中的新增功能是自 java1.0 发布 18 年以来,java 发生的最大变化,可以说是 java 发展史上的重大变革。java8 在老版本的基础上,没有去掉任何东西,所以 java8 支持向前兼容,你现有的代码仍然能正常工作。但是 java8 增加的几个新特性,能帮助你编写更清楚、更简洁、更高效的代码。
Java8 区别于以前的 Java 版本的一个重要特点就是函数式编程的风格。
那什么是函数式编程呢?
函数式编程属于编程范式中的一种,下面我们讲解一下命令式编程和函数式编程的区别,能够帮助我们更好地理解函数式编程。
命令式编程:命令式编程传递的是数据,将数据作为方法的参数或者返回值。
函数式编程:函数式编程传递的是函数("行为"或者"动作"),将函数作为方法的参数或者返回值,称之为行为参数化。
需求:用程序计算数学表达式 (1 + 2) * 3 - 4 的结果。
- 命令式编程:
// 程序1
public int calculate(){
int a = 1 + 2;
int b = a * 3;
int c = b - 4;
return c;
}
public static void main(String[] args){
int result = calculate();
System.out.println(result);
}
- 函数式编程:
// 程序2
public static void main(String[] args){
int result = subtract(multiply(add(1,2), 3), 4);
System.out.println(result);
}
在程序 2 中,add 函数作为 multiply 函数的入参,multiply 函数作为 subtract 函数的入参,这就是函数式编程,函数可以作为参数。我们发现程序 1 需要三行代码,而程序 2 只需一行代码,所以相比于命令式编程,函数式编程会使代码更加简洁。
1、Lambda 表达式
(1)Lambda 表达式基本语法
Lambda 表达式可以取代大部分的匿名内部类,帮助我们写出更加简洁的代码,对照匿名内部类,讲解一下 Lambda 表达式的基本语法:
java8 引入一个新的操作符"->",该操作符称为箭头操作符或 Lambda 操作符。
">"的左侧:Lambda 表达式的参数列表,即匿名内部类接口中抽象方法的参数列表。
">"的右侧:Lambda 表达式所需执行的功能,称为 Lambda 体,即匿名内部类接口中抽象方法的具体实现。
以我的理解,Lambda 就是对匿名内部类做了升级,将匿名内部类中的核心代码抽取成一个表达式(即 Lambda 表达式),以此来减少不必要的重复代码。
- 语法格式 1:若 Lambda 表达式参数列表中无参数,无返回值,用一个小括号表示参数列表。
() -> {System.out.ptintln("Hello World!");}
- 语法格式 2:若 Lambda 表达式参数列表中有一个参数,参数小括号可以省略不写。
(x) -> System.out.ptintln(x);
x -> System.out.ptintln(x);
- 语法格式 3:若 Lambda 表达式参数列表中有两个及两个以上参数,参数小括号必须写。
(x, y) -> System.out.ptintln(x + y);
- 语法格式 4:若 Lambda 体只有一条语句,return 和大括号都可以省略不写。
(x, y) -> {return x + y;}
(x, y) -> x + y;
- 语法格式 5:若 Lambda 体中有多条语句,多条语句必须用大括号括起来。
(int x, int y) -> {int z = x + y;return z;}
- 语法格式 6:Lambda 表达式参数列表的数据类型可以省略不写,因为 JVM 编译器可以通过上下文推断推断出参数类型,这个过程我们称之为"类型推断"。如下所示我们讲解一下,JVM 如何做类型推断的。
// Lambda表达式
Comparator comparator = (x, y) -> Integer.compare(x, y);// Comparator接口源码@FunctionalInterfacepublic interface Comparator<T> {
int compare(T o1, T o2);// 其他代码
}
JVM 根据 Comparator这句话判断泛型 T 为 Integer 类型,所以 compare 方法的两个参数都是 Integer 类型,最终判断出 Lambda 参数列表的 x 和 y 都是 Integer 类型。既然 JVM 自己可以判断出来,我们又何必多次一举写上呢。
(2) 使用 Lambda 表达式对接口的要求
虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,即只有一个抽象方法。注意:这句话的意思不是说接口中只能有一个方法,这是因为 java8 的另一个新特性,接口中新增默认方法 default,被 default 修饰的方法会有默认实现,接口中有默认方法并不影响 Lambda 表达式的使用。上述所描述的接口称之为函数式接口。
@FunctionalInterface 注解:用来修饰函数式接口的,要求接口中的抽象方法只有一个,如果被@FunctionalInterface 注解修饰的接口中有两个及以上抽象方法,编译会报错,这个注解往往会和 Lambda 表达式一起出现。如下 MyPredicate 接口是满足 Lambda 表达式对接口的要求的。
@FunctionalInterface
public interface MyPredicate<T> {
// 接口中的方法,默认是public abstract,即抽象方法。
boolean test(T t);
// java8新增特性,接口中增加默认方法
default String getName(){
return "哈哈哈";
}
}
(3)Lambda 表达式的优缺点
优点:使用 Lambda 表达式,可以帮助我们写出更加简洁紧凑的代码,减少代码量,减轻开发人员的工作量。
缺点:可读性差,如果是没有使用过java8的开发人员接收一个使用java8开发的项目,上手较慢。
(4)演示 Lambda 表达式如何使 java 代码更简洁
案例1(源码见 com.zxj.Lambda.package1.TestAnonymousInnerClassAndLambda.java)
需求:对 ArrayList 中的元素进行排序
实现:首先我们需要创建一个比较器(Comparator),然后调用 Collections 工具类的 sort 方法,使用比较器对 list 中的元素进行排序。
- 使用匿名内部类
// 使用匿名内部类对List进行排序
@Test
public void anonymousInnerClass(){
// 使用匿名内部类创建一个比较器
Comparator comparator = new Comparator() {
@Overridepublic int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};// 使用Collections工具类的sort方法对list中的元素进行排序
Collections.sort(list, comparator);// 遍历list
CommonUtil.printIntegerList(list);
}
- 使用Lambda表达式
@Test
public void lambda(){
// 使用Lambda表达式创建一个比较器
Comparator comparator = (x, y) -> Integer.compare(x, y);
Collections.sort(list, comparator);
CommonUtil.printIntegerList(list);
}
我们发现使用匿名内部类创建比较器,需要写一大堆代码,而使用 Lambda 表达式只需要一行代码,好处显而易见。
案例2(源码见 com.zxj.Lambda.TestLambda2.java)
需求:获取当前公司中,员工年龄大于 35 的员工信息。
- 使用 if 进行比较(源码见 com.zxj.Lambda.package2.useIf包)
// 根据年龄过滤的方法
public List filterAgeByIf(List employees){
List emps = new ArrayList<>();for (Employee employee : employees){
if(employee.getAge() > 35){
emps.add(employee);
}
}return emps;
}
当需求变为"获取公司中,工资大于 5000 的员工信息",比较的基准变了,此时我们需要重新写一个方法。
// 根据工资过滤的方法
public List filterSalaryByIf(List employees){
List emps = new ArrayList<>();for (Employee employee : employees){
if(employee.getSalary() > 5000){
emps.add(employee);
}
}return emps;
}
总结:使用 if 进行比较,一旦需求变了,就要增加一个方法,每个方法的核心是 if 判断语句,其他都是相同的代码,造成了代码重复。
- 使用策略模式(源码见 com.zxj.Lambda.package2.useStrategy 包) 创建策略接口
// 策略接口
public interface MyStrategy<T> {
boolean filter(T t);
}
创建根据年龄过滤的具体策略类
// 根据年龄过滤的具体策略类
public class FilterEmployeeByAge implements MyStrategy<Employee> {
@Override
public boolean filter(Employee employee) {
return employee.getAge() > 35;
}
}
创建根据工资过滤的具体策略类
// 根据工资过滤的具体策略类
public class FilterEmployeeBySalary implements MyStrategy<Employee> {
@Override
public boolean filter(Employee employee) {
return employee.getSalary() > 5000;
}
}
根据具体策略对 employees 进行过滤
// 过滤方法,根据具体策略对employees进行过滤
public List filterByStategy(List employees, MyStrategy myStrategy){