JDK1.8新特性:Lambda表达式语法和内置函数式接口

Source
版权声明:如需转载,请注明出处。 https://blog.csdn.net/caiqing116/article/details/85147458

前言:Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
首先我们来看下一个简单从匿名类到Lambda的例子,体会下Lambda的特点
在java8以前我们通过实行Runable接口创建线程

new Thread(new Runnable() {
	@Override
	public void run() {
		System.out.println("Hello Runnable Run");
	}
}).start();

利用java8的Lambad语法我们可以更方便的实现相同的功能

new Thread(() -> System.out.println("Hello Runnable Run")).start();

例2:在这里我们定义一个需求:通过年龄或者工资过滤员工信息
基于此业务场景我们先定义一个员工对象

package cq.java8.lambda;
public class Employ {
	private Integer age; //年龄
	private Double salary; //工资
	private String name; //姓名
	public Employ() {
		super();
	}
	public Employ(Integer age, Double salary, String name) {
		super();
		this.age = age;
		this.salary = salary;
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public Double getSalary() {
		return salary;
	}
	public void setSalary(Double salary) {
		this.salary = salary;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "Employ [age=" + age + ", salary=" + salary + ", name=" + name + "]";
	}
}

我们简单运用策略模式实现此类业务需求,首先定义一个公共的接口

//定义一个接口
public interface EmployStrategy {
	//定义一个筛选方法
	public boolean filterEmploy(Employ employ);
}

实现上面的公共接口过滤年龄大于25岁的员工

//定义一个根据年龄筛选的实现类
public class EmployFilterByAge implements EmployStrategy{
	@Override
	public boolean filterEmploy(Employ employ) {
		return  employ.getAge() > 25 ? true : false;
	}
}

实现上面的公共接口实现年龄工资大于6000的员工

//定义一个根据工资筛选的实现类
public class EmployFilterBySalary implements EmployStrategy{
	@Override
	public boolean filterEmploy(Employ employ) {
		return  employ.getSalary() > 6000 ? true : false;
	}
}

功能实现

public class TestLambda {
	
	List<Employ> employs = Arrays.asList(
		new Employ(18, 5555.55, "张三"),
		new Employ(22, 6666.66, "李四"),
		new Employ(33, 3333.33, "王五"),
		new Employ(44, 9999.99, "赵六"),
		new Employ(55, 8888.88, "田七"));
	
	//定义过滤员工返回新员工集合方法
	public List<Employ> getEmployByFilter(List<Employ> employs, EmployStrategy employStrategy){
		List<Employ> newEmploys = new ArrayList<Employ>();
		for(Employ employ : employs) {
			if(employStrategy.filterEmploy(employ)) {
				newEmploys.add(employ);
			}
		}
		return newEmploys;
	}
	
	//通过策略模式实现
	@Test
	public void test() {
		//根据年龄过滤
		List<Employ> ageFilterEmploys = getEmployByFilter(employs, new EmployFilterByAge());
		System.out.println("年龄大于25的员工:");
		for (Employ e : ageFilterEmploys) {
			System.out.println(e);
		}
		
		//根据工资过滤
		List<Employ> salaryFilterEmploys = getEmployByFilter(employs, new EmployFilterBySalary());
		System.out.println("工资大于6000的员工:");
		for (Employ e : salaryFilterEmploys) {
			System.out.println(e);
		}
	}
}

运行结果如下:

年龄大于25的员工:
Employ [age=33, salary=3333.33, name=王五]
Employ [age=44, salary=9999.99, name=赵六]
Employ [age=55, salary=8888.88, name=田七]
工资大于6000的员工:
Employ [age=22, salary=6666.66, name=李四]
Employ [age=44, salary=9999.99, name=赵六]
Employ [age=55, salary=8888.88, name=田七]

此实现方式进行了解耦,弊端就是每次新增需求需要加新的接口实现。当然我们也可以用匿名内部类的方式实现,当然我们也可以通过匿名内部类的方式实现,如下:

//通过匿名内部类实现
@Test
public void test2() {
	//根据年龄过滤
	List<Employ> ageFilterEmploys = getEmployByFilter(employs, new EmployStrategy() {
		@Override
		public boolean filterEmploy(Employ employ) {
			return employ.getAge() > 25 ? true : false;
		}
	});
	System.out.println("年龄大于25的员工:");
	for (Employ e : ageFilterEmploys) {
		System.out.println(e);
	}
	//根据年龄过滤
	List<Employ> salaryFilterEmploys = getEmployByFilter(employs, new EmployStrategy() {
		@Override
		public boolean filterEmploy(Employ employ) {
			return  employ.getSalary() > 6000 ? true : false;
		}
	});
	System.out.println("工资大于6000的员工:");
	for (Employ e : salaryFilterEmploys) {
		System.out.println(e);
	}
}

从第一个创建线程的例子中,延用相同的思路我们利用java8的lambda语法实现上述功能如下(接口还是使用EmployStrategy)

//Lambda表达式实现
@Test
public void test3() {
	List<Employ> ageFilterEmploys = getEmployByFilter(employs, e -> e.getAge() > 25);
	System.out.println("年龄大于25的员工:");
	for (Employ e : ageFilterEmploys) {
		System.out.println(e);
	}
	
	List<Employ> salaryFilterEmploys = getEmployByFilter(employs, e -> e.getSalary() > 6000);
	System.out.println("年龄大于25的员工:");
	for (Employ e : salaryFilterEmploys) {
		System.out.println(e);
	}
}

从上文实现员工过滤的实例中我们可以看到Lambda表单式可以大大简化代码。在这里我们也可以对Lambda表达式有个简单的了解和定义:可以将lambda表达式定义为一种 简洁、可传递的匿名函数,首先我们需要明确lambda表达式本质上是一个函数,虽然它不属于某个特定的类,但具备参数列表、函数主体、返回类型,以及能够抛出异常;其次它是匿名的,lambda表达式没有具体的函数名称;lambda表达式可以像参数一样进行传递,从而极大的简化代码的编写。上文所阐述的其实就是在告诉大家,我们为什么要使用Lambda表达式,接下来我们详细了解下Lambda语法。

1.表达式介绍
Lambda表达式在Java语言中引入了新的语法元素和操作符。这个操作符为 “ ->” , 该操作符被称为 Lambda 操作符或剪头操作符。它将 Lambda 分为两个部分:
左侧: 指定了 Lambda 表达式需要的所有参数
右侧: 指定了 Lambda 体,即 Lambda 表达式要执行的功能。

2.语法介绍
(2.1)语法格式一:无参,无返回值,Lambda体只需要一条语句
语法格式及案例:() -> System.out.println("无参无返回值Lambda表达式")

@Test
public void test4() {
	new Thread(() -> System.out.println("无参无返回值Lambda表达式")).start();
}

(2.2)语法格式二:一个参数,无返回值,此时参数的小括号可省略
语法格式及案例:args -> System.out.println(args)

@Test
public void test5() {
	Consumer<String> con = x -> System.out.println(x);
	con.accept("新年好");
}

(2.3)语法格式三:一个参数,有返回值,当Lambda体只有一条语句是return与大括号可省略
语法格式及案例:(args) -> return args参数处理后的结果

@Test
public void test6() {
	//接收一个参数返回其2的倍数
	Function<Integer, Integer> fun = (x) -> 2*x;
	fun.apply(5);
}

(2.4)语法格式四:两个参数,无返回值,当参数的数据类型可由编译器通过上下文推荐出来时,可省
语法格式及案例:(args1,args2) -> {两个参数处理,无返回值}

@Test
public void test7() {
	//求两个数的和并打印
	BiConsumer<Integer, Integer> biConsumer = (x, y) -> System.out.println(x+y);
	biConsumer.accept(10, 20);
}

(2.5)语法格式五:两个参数,有返回值,Lambda体只有1条语句
语法格式及案例:(args1, args2) -> return args1和args2处理后返回

@Test
public void test8() {
	Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
	int r = com.compare(5, 10);
	System.out.println(r);
}

(2.6)语法格式五:两个参数,有返回值,Lambda体有多条语句
语法格式及案例:
(args1, args2) -> { System.out.println("输入两个参数"); return args1和args2处理后返回 }

@Test
public void test9() {
	Comparator<Integer> com = (x, y) ->{
		System.out.println("输入两个参数");
		return Integer.compare(x, y);
	} 
	int r = com.compare(5, 10);
	System.out.println(r);
}

3.JDK1.8提供的内置函数式接口
上文中我们定义了EmployStrategy 接口,并且只包含一个抽象方法的接口,可以称之为函数式接口,我们可以在任意的函数式接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口,我们可以通过Lambda表达式创建该接口对象。
Java 内置四大核心函数式接口

接口类型 函数式接口 参数类型 返回值 用途
消费型 Consumer<T> T 对类型为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)

其他函数式接口

接口类型 函数式接口 参数类型 返回值 用途
函数型 BiFunction<T,U,R> T,U R 对类型为T,U的对象应用操作,返回R类型的结果方法:R apply(T t,U u)
函数型 UnaryOperator<T> T T 对类型为T的对象应用操作(一元运算),返回T类型的结果,方法:T apply(T t),是Function的子类
函数型 BinaryOperator<T> T,T T 对类型为T的应用操作(二元运算),并返回T类型结果。结果是R类型的对象,方法R apply(T t1, T t2),是BiFunction的子类
消费型 BiConsumer<T,U> T,U 对类型为T,U的对象应用操作,方法void accept(T t, U u)

在这里我们着重介绍四大核心函数式接口的使用
(3.1) Consumer<T> 方法 void accept(T t)
在这里我们定义一个需求:执行一段业务逻辑无参数无返回值,用此内置函数式接口实现如下:

public class TestConsumer {
	//定义一个花钱的消费方法,参数:钱、消费函数接口
	public void costMoney(double money, Consumer<Double> con) {
		//花钱
		con.accept(money);
	}
	@Test
	public void test() {
		//参数m,无返回值
		costMoney(1000, m -> System.out.println("花了"+m+"元钱,很开心!"));
	}
}

(3.2) Supplier<T> 方法 T get()
在这里我们定义一个需求作为实例:获取指定数量的随机数,并放入集合,用此内置函数式接口实现如下:

public class TestSupplier {
	//获取指定数量的随机数,并放入集合返回
	public List<Integer> getRandList(int num, Supplier<Integer> sup){
		List<Integer> randList = new ArrayList<>();
		for(int i = 0; i < num; i++) {
			//获取随机数并放入集合
			randList.add(sup.get());
		}
		return randList;
	}
	
	@Test
	public void test() {
		List<Integer> randList = getRandList(10, () -> (int)(Math.random()*100));
		System.out.println(randList);
		//打印结果:[76, 43, 23, 12, 42, 96, 84, 82, 4, 93]
	}
}

(3.3) Function<T, R> 方法 R apply(T t)
在这里我们定义一个需求作为实例:去除字符串空格,用此内置函数式接口实现如下:

public class TestFunction {
	//定义获取字符串方法
	public String getStr(String string, Function<String, String> fun) {
		return fun.apply(string);
	}
	@Test
	public void test() {
		String string = "   中国人民万岁,中华人名共和国万岁";
		//去掉空格
		string = getStr(string, (t) -> t.trim());
		System.out.println(string);
		//打印结果:中国人民万岁,中华人名共和国万岁
	}
}

(3.4) Predicate<T> 方法 boolean test(T t)
在这里我们定义一个需求:从集合中获取部分满足条件的元素放入新集合并返回,用此内置函数式接口实现如下:

public class TestPredicate {
	//从集合中获取部分满足条件的元素放入新集合并返回
	public List<Integer> getList(List<Integer> list, Predicate<Integer> pre){
		List<Integer> listNew = new ArrayList<>();
		for(Integer v : list) {
			if(pre.test(v)) {
				listNew.add(v);
			}
		}
		return listNew;
	}
	@Test
	public void test() {
		List<Integer> list = Arrays.asList(39,8,12,99,22);
		//获取值小于30,并返回新集合
		List<Integer> listNew = getList(list, (value) -> value < 30);
		System.out.println("小于30:"+listNew);
		//获取值大于40,并返回新集合
		listNew = getList(list, value -> value > 40);
		System.out.println("大于40:"+listNew);
		//打印结果
		//小于30:[8, 12, 22]
		//大于40:[99]
	}
}