一、为什么使用Lambda表达式?
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
二、Lambda表达式初识
下面我们来看一下几个Lambda表达式的例子:
-
从匿名类到Lambda的转换
Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello World!" ); } };
-
Lambda表达式
Runnable r1 = () -> System.out.println("Hello Lambda!");
-
原来使用匿名内部类作为参数传递
TreeSet<String> ts2 = new TreeSet<>(new Comparator<String>(){ @Override public int compare(String o1, String o2) { return Integer.compare(o1.length(), o2.length()); } });
Lambda表达式作为参数传递
-
-
TreeSet<String> ts2=new TreeSet<>(
(o1,o2) -> Integer.compare(o1.lenrth(),o2.length())
);
三、Lambda表达式引入点
要求:现在有一个员工集合,要求按照员工的薪资、员工的年龄得到符合要求的员工集合。
首先,我们定义一个员工类,Employee.java
public class Employee {
private int id;
private String name;
private int age;
private double salary;
public Employee() {
}
public Employee(String name) {
this.name = name;
}
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public Employee(int id, String name, int age, double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
}
}
第一种解决方案:也是我们比较常规思维,就是定义一个方法,对应要求得到指定的员工集合,这里我们就只看“获取公司中工资大于 5000 的员工信息”定义的方法如下:
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;
}
然后每一种要求对应一种方法,但是这种会很冗余,本来就一个条件不同,但是要写这么多相同的代码。这样,我们就要来优化了,我们更好的思维就是通过设计模式来操作了。
第二种解决方案:利用设计模式中的策略模式来优化。
首先,我们定义一个接口
public interface MyPredicate<T> {
public boolean test(T t);
}
然后只要有一个要求,我就定义一个对应的类实现这个接口,这里依然是使用上述的要求:获取公司中工资大于 5000 的员工信息。
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> mp){
List<Employee> list = new ArrayList<>();
for (Employee employee : emps) {
if(mp.test(employee)){
list.add(employee);
}
}
return list;
}
最后在测试代码里面匿名内部类。
@Test
public void test5(){
List<Employee> list = filterEmployee(emps, new MyPredicate<Employee>() {
@Override
public boolean test(Employee t) {
return t.getId() <= 103;
}
});
for (Employee employee : list) {
System.out.println(employee);
}
}
这样就能得到要求的员工集合了。
但是这样看来,还是有点冗余,因为每一个要求我都要创建一个类。所以,Lambda表达式就引入了。
第三种方案:引进Lambda表达式
@Test
public void test6(){
List<Employee> list = filterEmployee(emps, (e) -> e.getAge() <= 35);
list.forEach(System.out::println);
System.out.println("------------------------------------------");
List<Employee> list2 = filterEmployee(emps, (e) -> e.getSalary() >= 5000);
list2.forEach(System.out::println);
}
四、Lambda表达式语法
Lambda表达式在Java语言中引入了一个新的语法元素和操作符。这个操作符为“->”,该操作符被称为Lambda操作符或箭头操作符。它将Lambda分为两个部分:
左侧:指定了Lambda表达式需要的所有参数。
右侧:指定了Lambda体,即Lambda表达式要执行的功能。
1、语法格式一:无参数,无返回值
() -> System.out.println("Hello Lambda!");
示例代码:
@Test
public void test1(){
int num = 0;//jdk 1.7 前,必须是 final
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!" + num);
}
};
r.run();
System.out.println("-------------------------------");
//无参无返回值
Runnable r1 = () -> System.out.println("Hello Lambda!");
r1.run();
}
2、语法格式二:有一个参数,并且无返回值
(x) -> System.out.println(x)
示例代码:
@Test
public void test2(){
Consumer<String> con = (x) -> System.out.println(x);
con.accept("你是大傻子!");
}
3、语法格式三:若只有一个参数,小括号可以省略不写
x -> System.out.println(x)
示例代码:
@Test
public void test2(){
Consumer<String> con = x -> System.out.println(x);
con.accept("你是大傻子!");
}
4、语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator<Integer> com = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
示例代码:
@Test
public void test3(){
Comparator<Integer> com = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
}
5、语法格式五:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
示例代码:
@Test
public void test4(){
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
}
6、 语法格式六:Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
(Integer x, Integer y) -> Integer.compare(x, y);
示例代码:
@Test
public void test5(){
// String[] strs;
// strs = {"aaa", "bbb", "ccc"};
//字符串数组定义时不能分两步写,这样写是因为类型推断。所以编译才能通过的。
List<String> list = new ArrayList<>();
// ArrayList<>的类型可以不用写,这样也是因为类型推断的。
show(new HashMap<>());
}
public void show(Map<String, Integer> map){
}
语法小总结:
上联:左右遇一括号省
下联:左侧推断类型省
横批:能省则省
五、类型推断(比较重要)
上述的语法六,Lambda表达式中的参数类型都是由编译器推断得出的。Lambda表达式中无需指定类型,程序依然可以编译,这是因为javac根据程序的上下文,在后台推断出了参数的类型。Lambda表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
六、函数式接口
这里就稍微讲一下,下面的文章会详细讲解
1、什么是函数式接口?
(1)、只包含一个抽象方法的接口,称为函数式接口。
(2)、你可以通过Lambda表达式来创建该接口的对象。(若Lambda表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
(3)、我们可以在任意函数式接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
2、自定义函数式接口
示例代码:
@FunctionalInterface
public interface MyFun {
public Integer getValue(Integer num);
}
七、Lambda练习
1、调用Collecions.sort()方法,通过定制排序比较两个Employee(先按年龄比,年龄相同按姓名比),使用Lambda作为参数传递。
步骤一:先创建一个pojo
package com.nieshenkuan.pojo;
public class Employee {
private int id;
private String name;
private int age;
private double salary;
public Employee() {
}
public Employee(String name) {
this.name = name;
}
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public Employee(int id, String name, int age, double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String show() {
return "测试方法引用!";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
long temp;
temp = Double.doubleToLongBits(salary);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (age != other.age)
return false;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (Double.doubleToLongBits(salary) != Double.doubleToLongBits(other.salary))
return false;
return true;
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
}
}
步骤二:事先声明一个emps员工集合
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)
);
步骤三:测试实现要求
@Test
public void test1(){
Collections.sort(emps, (e1, e2) -> {
if(e1.getAge() == e2.getAge()){
return e1.getName().compareTo(e2.getName());
}else{
return -Integer.compare(e1.getAge(), e2.getAge());
}
});
for (Employee emp : emps) {
System.out.println(emp);
}
}
2、(1)、声明函数式接口,接口中声明抽象方法,public String getValue(String str);
(2)、声明类TestLambda,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值。
(3)、再将一个字符串的第2个和第4个索引位置进行截取子串。
步骤一:先声明一个函数式接口MyFunction,并接口中声明抽象方法,public String getValue(String str);
@FunctionalInterface
public interface MyFunction {
public String getValue(String str);
}
步骤二:编写一个方法处理字符串,这个方法在Lambda类中定义的。
//需求:用于处理字符串
public String strHandler(String str, MyFunction mf){
return mf.getValue(str);
}
步骤三:测试,实现具体的要求
@Test
public void test2(){
//这个是测试去除字符串的前后空格
String trimStr = strHandler("\t\t\t 你是大傻逼 ", (str) -> str.trim());
System.out.println(trimStr);
//这个是测试将字符串转换成大写的
String upper = strHandler("abcdef", (str) -> str.toUpperCase());
System.out.println(upper);
//截取字符串的指定索引的字符串
String newStr = strHandler("我大望江县威武", (str) -> str.substring(2, 5));
System.out.println(newStr);
}
3、(1)、声明一个带两个泛型的函数式接口,泛型类型为<T,R>T为参数,R为返回值。
(2)、接口中声明对应抽象方法
(3)、在TestLambda类中声明方法,使用接口作为参数,计算两个long型参数的和。
(4)、在计算两个long型参数的乘积。
步骤一:声明一个带两个泛型的函数式接口,泛型类型为<T,R>T为参数,R为返回值
public interface MyFunction2<T, R> {
public R getValue(T t1, T t2);
}
步骤二:编写一个方法,计算两个long型的数据
//需求:对于两个 Long 型数据进行处理
public void op(Long l1, Long l2, MyFunction2<Long, Long> mf){
System.out.println(mf.getValue(l1, l2));
}
步骤三:测试,并实现要求。
@Test
public void test3(){
//实现两个long型的数值的和
op(100L, 200L, (x, y) -> x + y);
//实现两个long型的数值的积
op(100L, 200L, (x, y) -> x * y);
}