JDK8新特性之Lambda表达式快速入门

7 篇文章 0 订阅

为什么使用 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>Tvoid对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier< T> 供给型接口T返回类型为T的对象,包含方法:T get();
Function< T, R> 函数型接口TR对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t);
Predicate< T> 断定型接口Tboolean确定类型为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新特性之方法引用与构造器引用快速入门,用法上大同小异,这里就不一一列举了。


  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值