Lambda表达式
Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。
一、Java 8新特性简介
(1)Lambda表达式
Lambda允许把函数作为一个方法的参数(函数作为一个参数传递进方法中)。
(2)方法引用
方法引用提供了非常有用的语法,可以直接引用已有Java类或者对象(实例)的方法或构造器。与Lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。方法引用使用一对冒号 “::”
(3)函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为Lambda表达式。
(4)默认方法
简单的说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。我们只需要在方法名前面加个 default 关键字即可实现默认方法。
(5)Stream API
Java 8 API 添加了一个新的抽象称为流 Stream ,可以让你以一种声明的方式处理数据。Stream 使用一种雷士用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达式的高阶抽象。Stream API 可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的集合看作一种流,流在管道中传输,并且可以在管道中的节点上进行处理,比如筛选,排序,聚合等。元素流在管道中经过中间操作(Intermediate operation)的处理,最后由最终操作(Terminal operation)得到前面处理的结果。
(6)Optional类
Optional 类是一个可以为空的容器对象。如果值存在则isPresent()方法返回 true ,调用get() 方法返回该对象。Optional 是一个容器:它可以保存类型 T 的值,或者仅仅保存 null 。Optional 提供了很多有用的方法,这样我们就不用显示地进行空值检测。Optional 类的引入很好的解决空指针异常。
(7)Nashorn,JavaScript引擎
Nashorn 一个 javascript 引擎。
从JDK 1.8开始,Nashorn取代Rhino(JDK 1.6, JDK1.7)成为Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。它使用基于JSR 292的新语言特性,其中包含在JDK 7中引入的 invokedynamic,将JavaScript编译成Java字节码。
与先前的Rhino实现相比,这带来了2到10倍的性能提升。
(8)新的日期时间API
Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。
在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:
-
非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
-
设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
-
时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
-
Local(本地) − 简化了日期时间的处理,没有时区的问题。
-
Zoned(时区) − 通过制定的时区处理日期时间。
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
(9)Base64
在Java 8中,Base64编码已经成为Java类库的标准。
Java 8 内置了 Base64 编码的编码器和解码器。
Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:
- 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
- URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
- MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用'\r'并跟随'\n'作为分割。编码输出最后没有行分割。
Lambda表达式和Stream API 是重点,方法引用和新日期API要求会用,函数式接口、默认方法、Optional类为了解内容,Nashorn和Base64见过就好。
二、Lambda表达式
1、Lambda表达式简化匿名内部类
package lambda.StuLambda1;
public class StuLambda1 {
public static void main(String[] args) {
//匿名内部类
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类的run方法执行了。");
}
});
thread.start();
//Lambda表达式
Thread thread1 = new Thread(()-> System.out.println("Lambda表达式的run方法执行了。"));
thread1.start();
}
}
2、使用Lambda表达式自定义比较器完成ThreSet集合排序
package lambda.StuLambda1;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
public class StuLambda2 {
public static void main(String[] args) {
//自定义比较器
Comparator<Integer> comparator=new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
};
//Lambda表达式
Comparator<Integer> comparator2= (o1, o2)->o1-o2;
TreeSet<Integer> treeSet=new TreeSet<>(comparator2);
//TreeSet<Integer> treeSet=new TreeSet<>((o1, o2)->o1-o2);
treeSet.add(12);
treeSet.add(10);
treeSet.add(9);
Iterator<Integer> iterator = treeSet.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
三、实用案例
有这样一个需求:有一个员工集合,获取年龄大于25的员工信息 。
1、传统模式
(1)员工类Employee
package lambda.StuLambda1;
import java.util.Objects;
/*
* wgy 2019/8/17 9:49
*/
public class Employee {
private String name;
private int age;
private double salary;
private String gender;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee(String name, int age, double salary, String gender) {
this.name = name;
this.age = age;
this.salary = salary;
this.gender = gender;
}
public Employee() {
}
//省略getterhe和setter
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
", gender='" + gender + '\'' +
'}';
}
}
(2)主类(测试类)
package lambda.StuLambda1;
import java.util.ArrayList;
import java.util.List;
public class StuLambda3 {
public static void main(String[] args) {
List<Employee> employees=new ArrayList<>();
employees.add(new Employee("少泊", 26, 32000));
employees.add(new Employee("发海", 28, 30000));
employees.add(new Employee("真真", 18, 28000));
employees.add(new Employee("本伟", 27, 25000));
employees.add(new Employee("坤坤", 21, 18000));
System.out.println("--------获取年龄大于25的员工信息----------");
List<Employee> list = filterByAge(employees, 25);
for (Employee e : list) {
System.out.println(e.toString());
}
System.out.println("--------获取工资大于20000的员工信息----------");
List<Employee> list2 = filterBySalary(employees, 20000);
for (Employee e : list2) {
System.out.println(e.toString());
}
}
//(1)获取年龄大于25的员工信息
public static List<Employee> filterByAge(List<Employee>employees,int age){
List<Employee> list=new ArrayList<>();
for (Employee employee : employees) {
if(employee.getAge()>=25){
list.add(employee);
}
}
return list;
}
//(2)获取工资大于20000的员工信息
public static List<Employee> filterBySalary(List<Employee>employees,double salary){
List<Employee> list=new ArrayList<>();
for (Employee employee : employees) {
if(employee.getSalary()>=salary){
list.add(employee);
}
}
return list;
}
}
如果再添加类似需求,就需要再添加一个方法,而且这些方法都是一些相似的方法,代码重复很严重。如何解决解决这个问题?那就是策略模式。策略模式会在设计模式学习系列详细介绍。
2、策略模式:
(1)首先定义一个断言接口:用于比较
package lambda.StuLambda1;
public interface MyPredicate<T> {
boolean test(T e);
}
(2)Employee类不变,测试类如下
我们在测试类中定义一个过滤器方法,传入需要过滤的集合和我们自定义的断言类型的对象,在调用断言方法时,实现我们自定义的断言接口即可。
package lambda.StuLambda1;
import java.util.ArrayList;
import java.util.List;
public class StuLambda3 {
public static void main(String[] args) {
List<Employee> employees=new ArrayList<>();
employees.add(new Employee("少泊", 26, 32000));
employees.add(new Employee("发海", 28, 30000));
employees.add(new Employee("真真", 18, 28000));
employees.add(new Employee("本伟", 27, 25000));
employees.add(new Employee("坤坤", 21, 18000));
System.out.println("-------------工资大于20000-----------------");
List<Employee> list3= filter(employees, new MyPredicate<Employee>() {
@Override
public boolean test(Employee e) {
return e.getSalary()>=20000;
}
});
for (Employee e : list3) {
System.out.println(e.toString());
}
System.out.println("-------------年龄大于25-----------------");
List<Employee> list4= filter(employees, new MyPredicate<Employee>() {
@Override
public boolean test(Employee e) {
return e.getAge()>=25;
}
});
for (Employee e : list4) {
System.out.println(e.toString());
}
}
//(3)过滤员工信息的方法
public static List<Employee> filter(List<Employee> employees, MyPredicate<Employee> p){
List<Employee> list=new ArrayList<>();
for (Employee e : employees) {
if(p.test(e)){
list.add(e);
}
}
return list;
}
}
3、使用Lambda表达式化简策略模式
package lambda.StuLambda1;
import java.util.ArrayList;
import java.util.List;
public class StuLambda3 {
public static void main(String[] args) {
List<Employee> employees=new ArrayList<>();
employees.add(new Employee("少泊", 26, 32000));
employees.add(new Employee("发海", 28, 30000));
employees.add(new Employee("真真", 18, 28000));
employees.add(new Employee("本伟", 27, 25000));
employees.add(new Employee("坤坤", 21, 18000));
System.out.println("------------策略模式(lambda表达式,工资大于25000)-------------------");
List<Employee> list4=filter(employees, e->e.getSalary()>=25000);
for (Employee employee : list4) {
System.out.println(employee.toString());
}
System.out.println("------------策略模式(lambda表达式吗,年龄大于25)-------------------");
List<Employee> list3=filter(employees, e->e.getAge()>=25);
for (Employee employee : list3) {
System.out.println(employee.toString());
}
}
//(3)过滤员工信息的方法
public static List<Employee> filter(List<Employee> employees, MyPredicate<Employee> p){
List<Employee> list=new ArrayList<>();
for (Employee e : employees) {
if(p.test(e)){
list.add(e);
}
}
return list;
}
}
(3)事实上,我们还可以使用Stream API 对Lambda表达式进行优化。
其他类不变,修改主类(测试类)如下:
package lambda.StuLambda1;
import java.util.ArrayList;
import java.util.List;
public class StuLambda3 {
public static void main(String[] args) {
List<Employee> employees=new ArrayList<>();
employees.add(new Employee("少泊", 26, 32000));
employees.add(new Employee("发海", 28, 30000));
employees.add(new Employee("真真", 18, 28000));
employees.add(new Employee("本伟", 27, 25000));
employees.add(new Employee("坤坤", 21, 18000));
System.out.println("---------Stream API优化Lambda(工资大于25000)-------------");
employees.stream()
.filter(e->e.getSalary()>=25000)
.forEach(System.out::println);
}
}
使用Stream API 我们无需自己定义 断言接口并实现,Stream API 为我们提供很多方法简化我们的开发,有关Stream API的内容,后续会详细介绍。
四、注意事项
使用Lambda表达式需要注意以下几点:
- Lambda引入了新的操作符:“->“ (箭头操作符),->将表达式分成两部分:
- 左侧:(参数1,参数2…)表示参数列表;
- 右侧:{}内部是方法体 ;
- 形参列表的数据类型会自动推断;
- 如果形参列表为空,只需保留();
- 如果形参只有1个,()可以省略,只需要参数的名称即可;
- 如果执行语句只有1句,且无返回值,{}可以省略,若有返回值,则若想省去{},则必须同时省略return,且执行语句也保证只有1句;
- lambda不会生成一个单独的内部类文件;
- lambda表达式若访问了局部变量,则局部变量必须是final的,若是局部变量没有加final关键字,系统会自动添加,此后在修改该局部变量,会报错。