1. 问题引入
为了引入Lambda表达式这个主题,首先以一个常用的查询业务来开始主题。假设,我们设计了一个业务类Employee,来表征员工管理的简单业务。我们需要从姓名、年龄、收入来筛选不同的员工。
1.1 基础处理方式
可以发现filterEmployeeBySalary()方法是一个遍历Employee数组中的每一个对象并筛选Employee的业务逻辑。通过代码简单发现,这个方法筛选工资大于20000的Employee。但是这种方法的问题是,每增加一种筛选方式,需要创建一个新的方法,非常麻烦。
1.2 设计模式的改进方式
为了防止每次新增一种查询方式,就需要增加一个方法定义的开发,可以采用策略设计模式方式,新建一个接口,在接口实现中完成筛选Employee的业务逻辑。如方法filterEmployeeByImp()的定义,该方法具有一定的通用性,在方法参数中加入了接口FilterBusi这个参数,那么在调用这个方式时,只要传给这个方法具体的实现类(具体定义了业务筛选逻辑)即可。
List<Employee> emps1 =
test.filterEmployeeByImp(employees,new MyFilterEmployeeByAge());
但是,仔细分析,这种方式的缺点也是显而易见,那就是每实现一个业务筛选逻辑,需要增加一个方法定义。
1.3 使用匿名内部类进一步改进
因为,每次新增加一种业务筛选逻辑,需要自定义一个新的业务逻辑实现类,这种方式并不友好。为了减少新建类的数量,可以使用匿名内部类的方式实现。例如下例中使用匿名内部类实现接口FilterBusi<Employee>:
List<Employee> emps2 = test.filterEmployeeByImp(employees, new FilterBusi<Employee>() {
@Override
public boolean filterBusi(Employee employee) {
return employee.getSalary()<6000;
}
});
1.4 使用Lambda表达式
Lambda表达式可以极大减少匿名内部类的编码量,在上边的业务逻辑,使用Lambda表达式可以简单表示如下:
List<Employee> emps3 = test.filterEmployeeByImp(employees,(e) -> e.getSalary()<2000);
业务接口FilterBusi.java
package java8;
public interface FilterBusi<T> {
boolean filterBusi(T t);
}
接口FilterBusi的一个实现类MyFilterEmployeeByAge.java
package java8;
public class MyFilterEmployeeByAge implements FilterBusi<Employee> {
@Override
public boolean filterBusi(Employee employee) {
return employee.getAge()<35;
}
}
主要业务类: LambdaTest.java
package java8;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class LambdaTest {
/**
* 使用传统方法形式,该方法不能复用,一个业务逻辑需要创建一个方法。
* @param employee
* @return
*/
public List<Employee> filterEmployeeBySalary(List<Employee> employee){
List<Employee> emps = new ArrayList<Employee>();
for (Employee emp : employee){
if(emp.getSalary()>20000){
emps.add(emp);
}
}
return emps;
}
/**
* 使用实现接口定义的方式来实现,该方法可以进行复用。用接口 FilterBusi mg 来筛选。
* @param employee
* @param mg
* @return
*/
public List<Employee> filterEmployeeByImp(List<Employee> employee
, FilterBusi<Employee> mg){
List<Employee> emps = new ArrayList<Employee>();
for (Employee emp : employee){
if(mg.filterBusi(emp)){ //抽象出一个通用形式
emps.add(emp);
}
}
return emps;
}
public static void main(String[] args) {
LambdaTest test = new LambdaTest();
List<Employee> employees = Arrays.asList(
new Employee("Joe",28000,31),
new Employee("Lucy",5000,18),
new Employee("Robin",400,7),
new Employee("Cristina",40000,35),
new Employee("Fancy",18000,55),
new Employee("Lion",4000,26)
);
//列表全集
for(Employee emp:employees){
System.out.println(emp);
}
System.out.println("-------------------------------------------");
//传统方法,采取增加方法实现的方法来实现业务扩展
List<Employee> emps = test.filterEmployeeBySalary(employees);
for(Employee emp:emps){
System.out.println(emp);
}
System.out.println("-------------------------------------------");
//设计模式 策略模式 缺点是:每次我都需要事先创建好一个类似于MyFilterEmployeeByAge这样的类
List<Employee> emps1 = test.filterEmployeeByImp(employees,new MyFilterEmployeeByAge());
for(Employee emp:emps1){
System.out.println(emp);
}
System.out.println("-------------------------------------------");
//这次复用之前创建的方法filterEmployeeByImp(根据接口来筛选)
List<Employee> emps2 = test.filterEmployeeByImp(employees, new FilterBusi<Employee>() {
@Override
public boolean filterBusi(Employee employee) {
return employee.getSalary()<6000;
}
});
for(Employee emp:emps2){
System.out.println(emp);
}
System.out.println("-------------------------------------------");
//使用lambda表达式来实现
List<Employee> emps3 = test.filterEmployeeByImp(employees,(e) -> e.getSalary()<2000);
for(Employee emp:emps3){
System.out.println(emp);
}
System.out.println("-------------------------------------------");
}
}
2. Lambda表达式语法
Java8引入新的语法符号 -> ,该操作符称为箭头操作符或 Lambda 操作符。
箭头操作符将 Lambda 表达式拆分成两部分:
符号->左边: Lambda 表达式的参数列表
符号->右边: Lambda 表达式中所需执行的功能, 即 Lambda 体
Lambda表达式通常包含4种语法格式:
语法格式一: 没有参数,没有返回值。(Runnable接口为例)
() -> System.out.println("Lambda Expression without para!");
语法格式二: 一个参数,没有返回值。(java.util.function.Consumer接口为例)
(x) -> System.out.println(x);
如果只有一个参数,可以将括号去掉,x -> System.out.println(x);
语法格式三: 有两个以上的参数,有返回值,并且 Lambda 体中有多条语句 (java.util.Consumer接口为例)
Comparator<Integer> comp = (x,y) -> {
System.out.println("Comparing "+x+" "+y+":");
return Integer.compare(x, y);
};
语法格式四: 如果只有一条语句(有返回值),可以省略大括号 和 return
下面的例子 LambdaBasicPro.java 演示了4种表达方式:
package java8;
import java.util.Comparator;
import java.util.function.Consumer;
import org.junit.Test;
/***
* Lambda表达式的语法
* 函数式接口:只有一个方法定义的接口。Lambda表达式针对实现函数式接口。
*
* Java8引入新的语法符号 -> ,该操作符称为箭头操作符或 Lambda 操作符
* 箭头操作符将 Lambda 表达式拆分成两部分:
* 符号->左边: Lambda 表达式的参数列表
* 符号->右边: Lambda 表达式中所需执行的功能, 即 Lambda 体
*
* 语法格式一: 没有参数,没有返回值。(Runnable接口为例)
* () -> System.out.println("Lambda Expression without para!");
*
* 语法格式二: 一个参数,没有返回值。(java.util.function.Consumer接口为例)
* (x) -> System.out.println(x);
* 如果只有一个参数,可以将括号去掉,x -> System.out.println(x);
*
* 语法格式三: 有两个以上的参数,有返回值,并且 Lambda 体中有多条语句 (java.util.Consumer接口为例)
* Comparator<Integer> comp = (x,y) -> {
* System.out.println("Comparing "+x+" "+y+":");
* return Integer.compare(x, y);
* };
*
* 语法格式四: 如果只有一条语句(有返回值),可以省略大括号 和 return
*
*
* @author natty
*/
public class LambdaBasicPro {
@Test
public void test01(){
Runnable ra = () -> System.out.println("Lambda Expression without para!");
ra.run();
}
@Test
public void test02() {
Consumer<Integer> con = x -> System.out.println(x);
con.accept(20);
}
@Test
public void test03() {
Comparator<Integer> comp = (x,y) -> {
System.out.println("Comparing "+x+" "+y+":");
return Integer.compare(x, y);
};
System.out.println(comp.compare(1000, 100));
}
@Test
public void test04() {
Comparator<Integer> comp = (z,t) -> Integer.compare(z, t);
System.out.println(comp.compare(1000, 100));
}
@Test
public void test8() {
String str = "{\"category\":\"住宿服务;宾馆酒店;五星级宾馆\",\"idx\":0,\"queryId\":\"B00155MTKJ\",\"requestId\":\"8cf273c9c65f44f98c9d73f59ca1d0c7\",\"searchType\":2,\"source\":\"MAP_GD_SEARCH\",\"subIdx\":-1,\"timestamp\":1544025600000,\"type\":4003}";
System.out.println(str);
}
}
3. Lambda表达式练习
下面实现3个关于lambda表达式的练习,在上边的注释上写明了需求。
LambdaPractice.java
package com.pmpa.Collections;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
public class LambdaPractice {
List<Employee> emplist = Arrays.asList(
new Employee("Joe",4800,23)
,new Employee("Lucas",8200,32)
,new Employee("Cindy",5000,25)
,new Employee("Cristina",5000,34)
,new Employee("Lucifer",24000,28)
,new Employee("Jim",60000,37)
);
/**
* 首先根据工资排序,工资一样的根据名字排序。
*/
@Test
public void test1() {
Collections.sort(emplist, (e1,e2) -> {
if(e1.getSalary() == e2.getSalary())
{
return e1.getName().compareTo(e2.getName());
}
else
{
return Float.compare(e1.getSalary(), e2.getSalary()) ;
}
});
for(Employee emp:emplist) {
System.out.println(emp);
}
}
/*
* 声明函数式接口,接口中声明抽象方法,
* 使用函数式接口实现字符串转化为大写,并且截取字符串。
*/
@Test
public void test2() {
String str = "aboisfqf";
GetString gs = x -> {
String lucifer = x.toUpperCase();
return lucifer.substring(2, 4);
};
System.out.println(gs.getValue(str));
}
/**
* 创建函数式接口 fuc(T,R)
* 计算2个整数的和 和两个整数的乘积。
*/
@Test
public void test3() {
FunctionDemo<Integer,Integer> fd = (x,y) -> x + y;
System.out.println(fd.getVal(20, 340));
FunctionDemo<Integer,Integer> fd1 = (x,y) -> x*y;
System.out.println(fd1.getVal(90, 2));
}
}
声明函数式接口 FunctionDemo.java
package com.pmpa.Collections;
public interface FunctionDemo<T,R> {
public R getVal(T t1,T t2);
}
4. Java 8自带函数式接口
从上边的练习看出,在每实现一个需求时,都需要创建一个接口,这其实是很麻烦的事情。在java 8中, rt.jar中的java.util.function包中,给我们提供了需要的函数式接口,直接使用就行,不用再新声明函数式接口了。
其中最核心的4个函数式接口分别是:
1.消费型接口 Consumer<T>:有输入参数,但是无返回值,有来无回 void accept(T t)
2.供给型接口 Supplier<T>: 没有输入参数,但是有返回值 T get()
3.函数型接口 Function<T,R> : 有一个参数,有一个返回值 R apply(T t)
4.断言型接口 Predicate<T> :有一个参数,返回布尔值结果 boolean test(T t)
上边核心接口基本都提供了一个输入参数泛型,如果参数不够时,还可以使用类似BiFunction等扩展的接口,这个包内都是接口,可以查看源码来确定使用。下边的例子是说明函数式接口的使用方法:
MainFunctionInterface.java
package java8;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.junit.Test;
/**
*
* @author natty
* Java中内置了一些函数式接口 :
* 1. 消费型接口 Consumer<T>:消费型接口
* 有输入参数,但是无返回值,有来无回 void accept(T t)
*
* 2. 供给型接口 Supplier<T>: 供给型接口
* 没有输入参数,但是有返回值 T get() T
*
* 3. 函数型接口 Function<T,R>
* 有一个参数,有一个返回值 R apply(T t)
*
* 4. 断言型接口 Predicate<T>
* 有一个参数,返回布尔值结果 boolean test(T t)
*
*/
public class MainFunctionInterface {
//消费型接口测试。
public void testConsumer(String test, Consumer<String> con)
{
con.accept(test);
}
//测试供给型接口
public String testSupplier(Supplier<String> sup) {
return sup.get();
}
//函数型接口
public String testFunction(Integer ls , Function<Integer,String> func) {
return func.apply(ls);
}
//断言型接口
public List<String> testPredicate(List<String> li , Predicate<String> pre){
List<String> re = new ArrayList<String>();
for(String str: li ) {
if(pre.test(str)) re.add(str);
}
return re;
}
@Test
public void test1() {
testConsumer("vipkid",(x) -> System.out.print("The choise for " + x));
}
@Test
public void test2() {
System.out.println(testSupplier(() -> "abc"));
}
@Test
public void test3() {
String phnics = testFunction(2000,(x) -> "The dollar using"+x);
System.out.println(phnics);
}
@Test
public void test4() {
List<String> phs = Arrays.asList("US-Harvard", "UK-Cambrige" , "US-Yale" , "JP-Tokyo" , "US-Duke");
List<String> output = testPredicate(phs,(x) -> x.substring(0, 2).equals("US"));
output.forEach(System.out::println);
}
}
5. 方法引用和构造器应用
若Lambda体中的内容已经有方法实现了,就可以使用“方法引用”,也可以理解为: 方法引用是Lambda表达式的另外一种表达形式。
5.1 方法引用
主要有三种语法格式:
1.对象::实例方法名
2.类::静态方法名
3.类::实例方法名
使用方法引用需要有一个前提,前提就是Lambda表达式实现的接口的参数列表和返回值类型需要与Lambda表达式中的方法的参数列表和返回值一致。
需要注意以下2点:
1.Lambda体中调用方法的参数列表和返回值类型 要与函数式接口中抽象方法的函数列表和返回值类型保持一致。
2.若Lambda参数列表中的第一参数是实例方法(equals)的调用者,第二个参数是实例方法的参数时,可以使用ClassName::method
5.2构造器引用
语法: ClassName::new
需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致。
5.3 数组引用
语法:type::new ;
下边的类是一个实例代码
package java8;
import java.util.Comparator;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Supplier;
import org.junit.Test;
/**
* Lambda体中调用方法的参数列表和返回值类型 要与函数式接口中抽象方法的函数列表和返回值类型保持一致。
*
* @author natty
*
*/
public class MethodRef {
// 对象::实例方法 方法引用
@Test
public void test1() {
Employee emp = new Employee("Honda",23,5000);
Supplier<String> sup = () -> emp.getName();
// Lambda体中的内容emp的getName方法已经实现了,可以使用方法引用
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
}
// 类::静态方法 方法引用
@Test
public void test2() {
//compare()方法是Integer类的一个静态方法 lambda体的实现使用Integer.compare方法实现。
Comparator<Integer> com = Integer::compare;
Comparator<Integer> com1 = (x,y) -> Integer.compare(x, y);
System.out.println(com.compare(10, 200));
}
// 类:: 实例方法 方法引用
@Test
public void test3() {
BiPredicate<String,String> bip = (x,y) -> x.equals(y);
//使用类::实例方法 方式来简化
//若Lambda参数列表中的第一参数是实例方法(equals)的调用者,第二个参数是实例方法的参数时,可以使用ClassName::method
BiPredicate<String,String> bip2 = String::equals;
System.out.println(bip.test("Juventus", "Juventus"));
}
//构造器应用
@Test
public void test4() {
//使用函数式接口创建对象
Supplier<Employee> sup = () -> new Employee();
//因为Supplier接口的方法是无参有返回值,那么对应的Employee::new 也会使用Employee的无参构造器
Supplier<Employee> sup1 = Employee::new;
Employee emp1 = sup1.get();
System.out.println(emp1);
//func是有一个参数,一个返回值的函数式接口,那会调用Employee包含一个参数的构造方法。
Function<String,Employee> func = (c) -> new Employee(c);
Function<String,Employee> func2 = Employee::new;
Employee sg = func2.apply("Liverpool");
System.out.println(sg);
}
//数组应用
@Test
public void test5() {
Function<Integer,String[]> func = (x) -> new String[x];
Function<Integer,String[]> func2 = String[]::new;
String[] chelsea = func2.apply(20);
System.out.println(chelsea.length);
}
}
======== 返回目录 ========
》》》 下一篇 Stream API