为什么使用 Lambda 表达式
示例一:先看一个常用排序类Comparator的示例
Comparator<String> com = new Comparator<String>(){
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
}
};
TreeSet<String> ts = new TreeSet<>(com);
代码再简化一点,匿名内部类的写法:
TreeSet<String> ts = new TreeSet<>(new Comparator<String>(){
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
}
});
但实际有用的代码就“Integer.compare(o1.length(), o2.length())” 这一行。能不能再简化一点,
这时Lambda 表达式闪亮登场:
Comparator<String> com = (o1, o2) -> Integer.compare(o1.length(), o2.length());
TreeSet<String> ts = new TreeSet<>(com);
代码是不是简洁很多了。
示例二:筛选员工数据的示例
员工类:
public class Employee {
private int id;
private String name;
private int age;
private double salary;
// get()、set()、hashCode()、equals()、toString()省略
}
现在有一批员工数据:
List<Employee> emps = Arrays.asList(
new Employee(101, "张三", 18, 9999.99),
new Employee(102, "李四", 59, 6666.66),
new Employee(103, "王五", 28, 3333.33),
new Employee(104, "赵六", 8, 7777.77),
new Employee(105, "田七", 38, 5555.55)
);
传统方式实现的示例
有以下筛选员工数据的业务需求,通过传统方式实现如下:
/**
* 需求1:获取公司中年龄小于 35 岁的员工信息
* @param emps
* @return
*/
public List<Employee> filterEmployeeAge(List<Employee> emps){
List<Employee> list = new ArrayList<>();
for (Employee emp : emps) {
if(emp.getAge() <= 35){
list.add(emp);
}
}
return list;
}
/**
* 需求2:获取公司中工资大于 5000 的员工信息
* @param emps
* @return
*/
public List<Employee> filterEmployeeSalary(List<Employee> emps){
List<Employee> list = new ArrayList<>();
for (Employee emp : emps) {
if(emp.getSalary() >= 5000){
list.add(emp);
}
}
return list;
}
仔细观察以上方法,发现只有if()判断一行代码不一样,其他代码都相同。出现了大量冗余代码。
优化思路:提取封装变化的部分,相同代码复用,另外还要考虑再有类似需求的扩展性,比如按性别过滤、按姓氏过滤等。此时我们会想到一种设计模式:策略模式。
策略模式实现的示例
定义顶层接口:
@FunctionalInterface
public interface MyPredicate<T> {
boolean test(T t);
}
根据业务需求,定义接口的实现类,实现业务代码中变化的部分:
/**
* 按年龄过滤的类
*/
public class FilterEmployeeForAge implements MyPredicate<Employee>{
@Override
public boolean test(Employee t) {
return t.getAge() <= 35;
}
}
/**
* 按工资过滤的类
*/
public class FilterEmployeeForSalary implements MyPredicate<Employee> {
@Override
public boolean test(Employee t) {
return t.getSalary() >= 5000;
}
}
复用相同的部分:
public List<Employee> filterEmployee(List<Employee> emps, MyPredicate<Employee> predicate){
List<Employee> list = new ArrayList<>();
for (Employee employee : emps) {
if(predicate.test(employee)){ //这一行代码可以根据业务需求灵活扩展
list.add(employee);
}
}
return list;
}
策略模式实现的示例:
//需求1:获取公司中年龄小于 35 的员工信息
List<Employee> list = filterEmployee(emps, new FilterEmployeeForAge());
for (Employee employee : list) {
System.out.println(employee);
}
//需求2:获取公司中工资大于 5000 的员工信息
List<Employee> list2 = filterEmployee(emps, new FilterEmployeeForSalary());
for (Employee employee : list2) {
System.out.println(employee);
}
这样,满足了按不通条件过滤员工信息的业务需求,也满足了代码的开闭原则。
比如再有按性别过滤的需求,我们再新建一个过滤实现类即可。
但如果有很多过滤场景,那么就要新建很多个只有几行代码的过滤实现类。可以不新建那么多实现类吗?可以。
方式一:匿名内部类
List<Employee> list = filterEmployee(emps, new MyPredicate<Employee>() {
@Override
public boolean test(Employee t) {
return t.getSalary() >= 5000;
}
});
for (Employee employee : list) {
System.out.println(employee);
}
方式二:Lambda表达式
List<Employee> list = filterEmployee(emps, (e) -> e.getSalary() >= 5000);
list.forEach(System.out::println);
对比一下,Lambda表达式的方式,代码更简洁更清晰。
我们还可以进一步简化代码,连自定义的接口MyPredicate都不用写,采用JDK8内置的Predicate函数式接口:
public List<Employee> filterEmployee2(List<Employee> emps, Predicate<Employee> predicate){
List<Employee> list = new ArrayList<>();
for (Employee employee : emps) {
if(predicate.test(employee)){
list.add(employee);
}
}
return list;
}
List<Employee> list = filterEmployee2(emps, (e) -> e.getSalary() >= 5000);
list.forEach(System.out::println);
Lambda 基础语法
Java8中引入了一个新的操作符 “->”, 该操作符称为箭头操作符或 Lambda 操作符。
箭头操作符将 Lambda 表达式拆分成两部分:
- 左侧:Lambda 表达式的参数列表
- 右侧:Lambda 表达式的方法体, 即接口实现类的方法体
语法格式一:无参数,无返回值
() -> System.out.println(“Hello Lambda!”);
int num = 0;//jdk1.7及以前必须加final
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!" + num);
}
};
r.run();
Runnable r1 = () -> System.out.println("Hello Lambda!" + num);
r1.run();
语法格式二:有一个参数,并且无返回值
Consumer<String> con = (x) -> System.out.println(x);
con.accept("Hello Lambda!");
语法格式三:若只有一个参数,小括号可以省略不写
Consumer<String> con = x -> System.out.println(x);
con.accept("Hello Lambda!");
语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
有多条语句时,方法体加**{},有返回值加return**。
Comparator<Integer> com = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
语法格式五:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
语法格式六:Lambda 表达式的参数列表的数据类型可以省略不写
因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
Comparator<Integer> com = (Integer x, Integer y) -> Integer.compare(x, y);
//根据前面Comparator<Integer>里指定的Integer推断的
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
String[] arr = {“aaa”, “bbb”, “ccc”}; //值简写,也是根据前面String类型推断的
List< String> list = new ArrayList<>(); //ArrayList<>里没写String,也是根据List< String>里推断出来的
Lambda 表达式需要“函数式接口”的支持
函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。
可以使用注 @FunctionalInterface注解修饰,该注解可以检查是否是函数式接口,比如接口类里定义了2个接口方法,就会提示错误。
JDK8四大内置的核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer< T> | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t) |
Supplier< T> 供给型接口 | 无 | T | 返回类型为T的对象,包含方法:T get(); |
Function< T, R> 函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t); |
Predicate< T> 断定型接口 | T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法boolean test(T t); |
Consumer接口的示例
示例一:
@Test
public void test1(){
happy("张三", (e) -> System.out.println("欢迎" + e + "光临"));
}
public void happy(String str, Consumer<String> con){
con.accept(str);
}
示例二:
@Test
public void test2(){
List<String> strList = Arrays.asList("Hello", "atguigu", "Lambda", "www", "ok");
strList.forEach(System.out::println);//这一行等同于下面for循环
//或
for (String str : strList) {
System.out.println(str);
}
}
Supplier接口的示例
@Test
public void test1(){
List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));
for (Integer num : numList) {
System.out.println(num);
}
}
//需求:产生指定个数的整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> sup){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
Function接口的示例
示例一:
@Test
public void test1(){
Integer num = operation(100, (x) -> x * x);
System.out.println(num);
System.out.println(operation2(200, (y) -> y + 200));
}
//需求:数字计算
public Integer operation(Integer num, Function<Integer, Integer> fun){
return fun.apply(num);
}
示例二:
@Test
public void test2(){
String newStr = strHandler("\t\t\t 我大中国威武 ", (str) -> str.trim());
System.out.println(newStr);
String subStr = strHandler("我大中国威武", (str) -> str.substring(2, 6));
System.out.println(subStr);
}
//需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun){
return fun.apply(str);
}
从示例中,我们可以看出Lambda表达式的另一个特性:可以作为方法参数进行传递。
JDK8以前只有具体值可以作为方法参数传递,JDK8以后可以通过Lambda表达式把一段代码片段作为方法参数进行传递。
Predicate接口的示例
@Test
public void test1(){
List<String> list = Arrays.asList("Hello", "atguigu", "Lambda", "www", "ok");
List<String> strList = filterStr(list, (s) -> s.length() > 3);
for (String str : strList) {
System.out.println(str);
}
}
//需求:将满足条件的字符串,放入集合中
public List<String> filterStr(List<String> list, Predicate<String> pre){
List<String> strList = new ArrayList<>();
for (String str : list) {
if(pre.test(str)){
strList.add(str);
}
}
return strList;
}
其他内置的函数式接口
Runnable接口的示例
@Test
public void test(){
Runnable r1 = () -> System.out.println(Thread.currentThread().getName()+": Hello Lambda!");
Thread thread = new Thread(r1);
thread.start();
}
Comparator接口的示例
//需求:对员工列表排序,先按年龄排序,年龄相等的话再按姓名排序
@Test
public void test(){
Comparator<Employee> comparator = (e1, e2) -> {
if (e1.getAge() == e2.getAge()) {
return e1.getName().compareTo(e2.getName());
} else {
return Integer.compare(e1.getAge(), e2.getAge());
}
};
Collections.sort(emps, comparator);
emps.forEach(System.out::println);
}
JDK8里还有很多这种函数式接口
BiFunction、BiPredicate可以参考JDK8新特性之方法引用与构造器引用快速入门,用法上大同小异,这里就不一一列举了。