什么是Lambda表达式
Lambda表达式是Java 8的新特性,是函数式接口的实例。使用Lambda表达式可以写出更简洁、更灵活的代码。
Lambda表达式语法
Lambda语法格式:
(parameters) -> expression
// 或
(parameters) -> { statements; }
->
:叫做Lambda操作符
或箭头操作符
。
->
左边:Lambda形参列表(其实就是借口中的抽象方法的形参列表)
->
右边:Lambda体(其实就是重写的抽象方法的方法体)
Lambda表达式的使用
这里我们将Lambda表达式的使用细分为6中情况,下面结合例子进行介绍:
-
情况一:无参,无返回值
public class LambdaTest { //语法格式一:无参,无返回值 @Test public void test1() { //普通写法 Runnable r1 = new Runnable() { @Override public void run() { System.out.println("公众号:程序员汪汪------关注我"); } }; r1.run(); System.out.println("**************************"); //Lambda表达式的写法 Runnable r2 = () -> System.out.println("公众号:程序员汪汪------关注我"); r2.run(); } }
-
情况二:Lambda表达式需要一个参数,但是没有返回值。
//语法格式二:Lambda需要一个参数,但是没有返回值 @Test public void test2() { //普通写法 Consumer
con = new Consumer () { @Override public void accept(String s) { System.out.println(s); } }; con.accept("公众号:程序员汪汪------关注我"); System.out.println("**************************"); //Lambda表达式的写法 Consumer con1 = (String s) -> {System.out.println(s);}; con1.accept("公众号:程序员汪汪------关注我"); } -
情况三:数据类型可以省略,因为可由编译器推断得出,称为
类型推断
//语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断” @Test public void test3() { Consumer
con1 = (String s) -> { System.out.println(s); }; con.accept("公众号:程序员汪汪------关注我"); System.out.println("**************************"); //Lambda表达式的写法 Consumer con1 = (String s) -> {System.out.println(s);}; con1.accept("公众号:程序员汪汪------关注我"); } -
情况四:Lambda表达式若只需要一个参数时,参数的小括号可以省略
//语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略 @Test public void test4() { Consumer
con1 = (s) -> { System.out.println(s); }; con1.accept("hhhhh"); System.out.println("***************"); Consumer con2 = s -> { System.out.println(s); }; con2.accept("hhhhhhhhhhhh"); } 注意:无参数时,小括号是一定不能省略的。
-
情况五:Lambda表达式需要两个或两个以上的参数,有多条执行语句,并且可以有返回值
//语法格式五:Lambda表达式需要两个或两个以上的参数,有多条执行语句,并且可以有返回值 @Test public void test5() { Comparator
com1 = new Comparator () { @Override public int compare(Integer o1, Integer o2) { System.out.println(o1); System.out.println(o2); return o1.compareTo(o2); } }; System.out.println(com1.compare(12, 21)); System.out.println("***********************"); Comparator com2 = (o1, o2) -> { System.out.println(o1); System.out.println(o2); return o1.compareTo(o2); }; System.out.println(com2.compare(12, 6)); } -
情况六:当Lambda体只有一条语句时,return 与大括号若有,都可以省略
//语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略 @Test public void test6() { Comparator
com1 = (o1, o2) -> { return o1.compareTo(o2); }; System.out.println(com1.compare(12, 6)); System.out.println("****************"); Comparator com2 = (o1, o2) -> o1.compareTo(o2); System.out.println(com2.compare(12, 26)); }
总结
->
左边:Lambda表达式的形参列表的参数类型可以省略(类型推断);如果Lambda形参列表只有一个参数,其一对()
可以省略,没有参数和有多个参数的情况下()
不能省略。
->
右边:Lambda体应该使用一对{}
包裹;如果Lambda体只有一条执行语句(可能是return语句),其一对{}
可以省略,如果是return语句,想要省略一对{}
,那么return
关键字也必须省略;有多条语句时,不能省略。
Lambda表达式的本质:作为函数式接口的实例。函数式接口就是只声明了一个抽象方法的接口。
函数式接口
函数式接口就是只声明了一个抽象方法的接口。
在Java 8中,java.util.function
包下定义了丰富的函数式接口,这些接口上都使用了@FunctionalInterface
注解,这样可以检查它是否是一个函数式接口。我们也可以使用该注解定义自己的函数式接口。
想要使用Lambda表达式,那么就一定需要函数式接口,Lambda就是函数式接口的实例。
java内置四大核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer 消费型接口 |
T | void | 对类型为T的对象应用操作。包含方法:void accept(T t) |
Supplier 供给型接口 |
无 | T | 返回类型为T的对象,包含方法:T get() |
Function 函数型接口 |
T | R | 对类型为T的对象应用操作,并返回结果。结果类型是R类型的对象。方法包含:R apply(T t) |
Predicate 断定型接口 |
T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean值。包含方法:boolean test(T t) |
其他接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction |
T, U | R | 对类型为T, U参数应用操作,返回R类型的结果。包含方法:R apply(T t, U u) |
UnaryOperator (Function子接口) |
T | T | 对类型为T的对象进行一元运算,并返回T类型的结果。包含方法:T apply(T t) |
BinaryOperator (BiFunction子接口) |
T, T | T | 对类型为T的对象进行二元运算,并返回T类型的结果。包含方法:T apply(T t1, T t2) |
BiConsumer |
T, U | void | 对类型为T, U参数应用操作。包含方法:void accept(T t, U u) |
BiPredicate |
T, U | boolean | 包含方法:boolean test(T t, U u) |
ToIntFunction ToLongFunction ToDoubleFunction |
T | int long double |
分别计算int、long、double值得函数 |
IntFunction LongFunction DoubleFunction |
int long double |
R | 参数分别为int、long、double类型的函数 |
Java提供的函数式接口还有很多,上表只列举了一部分函数式接口,在以后的开发中可能会遇到。凡是开发中遇到了函数式接口,都可以运用Lambda表达式。
函数式接口的基本命名准则
Java提供的函数式接口遵循以下基本命名准则:
- 如果只处理对象而非基本类型,名称则为
Function
,Consumer
,Predicate
等。参数类型通过泛型添加。 - 如果接收的参数是基本类型,则由名称的第一部分表示,如
LongConsumer
,DoubleFunction
,IntPredicate
等,但返回基本类型的Supplier
接口例外。 - 如果返回值为基本类型,则用
To
表示,如ToLongFunction
和IntToLongFunction
。 - 如果返回值类型与参数类型相同,则是一个
Operator
:单个参数使用UnaryOperator
,两个参数使用BinaryOperator
。 - 如果接收参数并返回一个布尔值,则是一个 断言 (
Predicate
)。 - 如果接收的两个参数类型不同,则名称中有一个
Bi
。
这里使用java内置的四大核心函数式接口举两个例子:
public class LambdaTest2 {
//消费型接口Consumer
public void happyTime(double money, Consumer consumer) {
consumer.accept(money);
}
@Test
public void test() {
happyTime(500, new Consumer() {
@Override
public void accept(Double aDouble) {
System.out.println("学习好累啊,出去嗨一下,花了" + aDouble + "元");
}
});
System.out.println("******************************");
happyTime(400, money -> {
System.out.println("学习好累啊,出去嗨一下,花了" + money + "元");
});
}
//根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定
public List filterString(List list, Predicate predicate) {
ArrayList filterList = new ArrayList<>();
for (String s : list) {
if (predicate.test(s)) {
filterList.add(s);
}
}
return filterList;
}
//断定型接口 Predicate
@Test
public void test2(){
List list = Arrays.asList("北京", "南京", "天津", "东京", "西京", "普京");
//普通写法
List filterList = filterString(list, new Predicate() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});
System.out.println(filterList);
System.out.println("******************************");
//Lambda表达式写法
List filterList2 = filterString(list, s -> s.contains("京"));
System.out.println(filterList2);
}
}
/**
test1 运行结果:
学习好累啊,出去嗨一下,花了500.0元
******************************
学习好累啊,出去嗨一下,花了400.0元
test2 运行结果:
[北京, 南京, 东京, 西京, 普京]
******************************
[北京, 南京, 东京, 西京, 普京]
*/
方法引用
方法引用本质上就是Lambda表达式,而Lambda是函数式接口的实例,所以方法引用也是函数式接口的实例
使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。
方法引用的格式:
类(或对象) :: 方法名
具体可以分为如下的三种情况:
- 对象 :: 非静态方法
- 类 :: 静态方法
- 类 :: 非静态方法
案例
Employee工具类:
public class Employee {
private int id;
private String name;
private int age;
private double 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 Employee() {
}
public Employee(int id) {
this.id = id;
}
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public Employee(int id, String name, int age, double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Employee employee = (Employee) o;
if (id != employee.id)
return false;
if (age != employee.age)
return false;
if (Double.compare(employee.salary, salary) != 0)
return false;
return name != null ? name.equals(employee.name) : employee.name == null;
}
@Override
public int hashCode() {
int result;
long temp;
result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
temp = Double.doubleToLongBits(salary);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
测试类:
public class MethodRefTest {
// 情况一:对象 :: 实例方法
//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
@Test
public void test1() {
//普通写法
Consumer con = new Consumer() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("hello!");
System.out.println("*****************************");
//Lambda表达式写法
Consumer con1 = str -> System.out.println(str);
con1.accept("北京");
System.out.println("*****************************");
//方法引用的写法
PrintStream ps = System.out;
Consumer con2 = ps::println;
con2.accept("天安门");
/**
test1 运行结果:
hello!
*****************************
北京
*****************************
天安门
*/
}
//Supplier中的T get()
//Employee中的String getName()
@Test
public void test2() {
//普通写法
Employee emp = new Employee(1001, "Tom", 20, 5000);
Supplier sup = new Supplier() {
@Override
public String get() {
return emp.getName();
}
};
System.out.println(sup.get());
System.out.println("***************************************");
//Lambda表达式写法,emp对象不变
Supplier sup1 = () -> emp.getName();
System.out.println(sup1.get());
System.out.println("***************************************");
//方法引用的写法
Supplier sup2 = emp::getName;
System.out.println(sup2.get());
/**
test2 运行结果:
Tom
***************************************
Tom
***************************************
Tom
*/
}
// 情况二:类 :: 静态方法
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test3() {
//普通写法
Comparator com = new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
System.out.println(com.compare(12, 20));
System.out.println("***************************************");
//Lambda 表达式的写法
Comparator com1 = (o1, o2) -> Integer.compare(o1, o2);
System.out.println(com1.compare(12, 20));
System.out.println("***************************************");
//方法引用
Comparator com2 = Integer::compare;
System.out.println(com2.compare(12, 20));
/**
test3 运行结果:
-1
***************************************
-1
***************************************
-1
*/
}
//Function中的R apply(T t)
//Math中的Long round(Double d)
@Test
public void test4() {
//普通写法
Function fun = new Function() {
@Override
public Long apply(Double aDouble) {
return Math.round(aDouble);
}
};
System.out.println(fun.apply(12.3));
System.out.println("***************************************");
//Lambda表达式写法
Function fun1 = (aDouble) -> Math.round(aDouble);
System.out.println(fun1.apply(12.6));
System.out.println("***************************************");
//方法引用的写法
Function fun2 = Math::round;
System.out.println(fun2.apply(12.5));
/**
test4 运行结果:
12
***************************************
13
***************************************
13
*/
}
// 情况三:类 :: 实例方法
// Comparator中的int compare(T t1,T t2)
// String中的int t1.compareTo(t2)
@Test
public void test5() {
//普通方法
Comparator com = new Comparator() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
};
System.out.println(com.compare("abc", "abe"));
System.out.println("***************************************");
//Lambda 表达式的方式
Comparator com1 = (o1, o2) -> o1.compareTo(o2);
System.out.println(com1.compare("abc", "abe"));
System.out.println("***************************************");
//方法引用的方式
Comparator com2 = String::compareTo;
System.out.println(com2.compare("abc", "abe"));
/**
test5 运行结果:
-2
***************************************
-2
***************************************
-2
*/
}
//BiPredicate中的boolean test(T t1, T t2);
//String中的boolean t1.equals(t2)
@Test
public void test6() {
//普通写法
BiPredicate bip = new BiPredicate() {
@Override
public boolean test(String s, String s2) {
return s.equals(s2);
}
};
System.out.println(bip.test("abc", "abd"));
System.out.println("***************************************");
//Lambda表达式写法
BiPredicate bip1 = (s1, s2) -> s1.equals(s2);
System.out.println(bip1.test("abc", "abc"));
System.out.println("***************************************");
//方法引用
BiPredicate bip2 = String::equals;
System.out.println(bip2.test("abc", "abc"));
/**
test6 运行结果:
false
***************************************
true
***************************************
true
*/
}
// Function中的R apply(T t)
// Employee中的String getName();
@Test
public void test7() {
//普通写法
Employee emp = new Employee(1001, "Tom", 20, 5000);
Function func = new Function() {
@Override
public String apply(Employee employee) {
return employee.getName();
}
};
System.out.println(func.apply(emp));
System.out.println("***************************************");
//Lambda 表达式的写法 emp不变
Function func1 = employee -> employee.getName();
System.out.println(func1.apply(emp));
System.out.println("***************************************");
//方法引用写法
Function func2 = Employee::getName;
System.out.println(func2.apply(emp));
/**
test7 运行结果:
Tom
***************************************
Tom
***************************************
Tom
*/
}
}
方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同,如果是成员方法(非静态方法),会在方法的参数列表的第一个位置增加this
的类型。
分析
首先我们来分析一下test1的例子ps::println
,在本例中,实际上调用的是void println(String str)
,它是非静态方法,也就是成员方法,按照上述的要求,需要在参数列表的第一个位置补上this
也就是PrintStream
,参数列表就变成了这样void println(PrintStream ps String str)
,但是因为::
前是一个PrintStream
实例ps
,因此,方法引用的第一个参数this
被绑定给了ps
,所以最终的参数列表又变回了void println(String str)
,所以就符合了要求中所说的参数列表和返回值类型都与方法引用的方法的形参列表和返回值类型相同的要求,那么就和Consumer
接口的抽象方法void accept(T t)
的返回值和参数列表一致了,所以就可以使用方法引用。
再来看test3的例子,test3是情况2的例子,调用的是静态方法,静态方法没有this
,所以只要满足参数列表和返回值相同的条件就可以使用方法引用。
最后看test5的例子String::compareTo
,在这个例子中,int compareTo(String str)
是一个成员方法(非静态方法),按照上述的方法引用使用的要求,需要在参数列表的第一个位置上补上this
(在这里,this
的类型就是String
),为什么是String?因为compareTo
是String
类的成员方法,this
当然就是指的是String
。添加this
后,新的参数列表就变成了int compareTo(String s, String str)
,此时就符合Comparator
接口中的int compare(T t1,T t2)
的要求,所以方法引用就能够使用了。
总结
- 成员方法(非静态方法)的参数列表,前面会追加
this
的类型。 - 静态方法因为没有
this
,所以参数列表不会追加任何东西 - 当
::
前是一个实例时,本应追加到参数列表中的this
会绑定给这个实例。
构造器引用与数组引用
构造器引用:构造器引用和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致,抽象方法的返回值类型即为构造器所属的类的类型。
数组引用:把数组看成是一个特殊的类,则写法就与构造器引用一致了。
案例:
案例中仍需使用上面的Employee工具类。
public class ConstructorRefTest {
//构造器引用
//Supplier中的T get()
@Test
public void test1(){
//普通写法
Supplier sup = new Supplier() {
@Override
public Employee get() {
return new Employee();
}
};
System.out.println(sup.get());
System.out.println("***************************************");
//Lambda 表达式写法
Supplier sup1 = () -> new Employee();
System.out.println(sup1.get());
System.out.println("***************************************");
//构造器引用写法
Supplier sup2 = Employee::new;
System.out.println(sup2.get());
/**
运行结果:
Employee{id=0, name='null', age=0, salary=0.0}
***************************************
Employee{id=0, name='null', age=0, salary=0.0}
***************************************
Employee{id=0, name='null', age=0, salary=0.0}
*/
}
//Function中的R apply(T t)
@Test
public void test2(){
//普通写法
Function func = new Function() {
@Override
public Employee apply(Integer id) {
return new Employee(id);
}
};
System.out.println(func.apply(1001));
System.out.println("***************************************");
// Lambda
Function func1 = id -> new Employee(id);
System.out.println(func1.apply(1002));
System.out.println("***************************************");
//构造器引用
Function func2 = Employee::new;
System.out.println(func2.apply(1003));
/**
运行结果:
Employee{id=1001, name='null', age=0, salary=0.0}
***************************************
Employee{id=1002, name='null', age=0, salary=0.0}
***************************************
Employee{id=1003, name='null', age=0, salary=0.0}
*/
}
//BiFunction中的R apply(T t,U u)
@Test
public void test3(){
//普通写法
BiFunction func = new BiFunction() {
@Override
public Employee apply(Integer id, String name) {
return new Employee(id, name);
}
};
System.out.println(func.apply(1001, "Tom"));
System.out.println("***************************************");
//Lambda
BiFunction func1 = (id, name) -> new Employee(id, name);
System.out.println(func1.apply(1002, "Jerry"));
System.out.println("***************************************");
//构造器引用
BiFunction func2 = Employee::new;
System.out.println(func2.apply(1003, "Jack"));
/**
运行结果:
Employee{id=1001, name='Tom', age=0, salary=0.0}
***************************************
Employee{id=1002, name='Jerry', age=0, salary=0.0}
***************************************
Employee{id=1003, name='Jack', age=0, salary=0.0}
*/
}
//数组引用
//Function中的R apply(T t)
@Test
public void test4(){
//普通写法 Integer代表数组长度
Function func1 = new Function() {
@Override
public String[] apply(Integer length) {
return new String[length];
}
};
String[] arr1 = func1.apply(5);
System.out.println(Arrays.toString(arr1));
System.out.println("***************************************");
//Lambda
Function func2 = length -> new String[length];
String[] arr2 = func2.apply(8);
System.out.println(Arrays.toString(arr2));
System.out.println("***************************************");
//数组引用
Function func3 = String[]::new;
String[] arr3 = func3.apply(10);
System.out.println(Arrays.toString(arr3));
/**
运行结果:
[null, null, null, null, null]
***************************************
[null, null, null, null, null, null, null, null]
***************************************
[null, null, null, null, null, null, null, null, null, null]
*/
}
}
结语:
Lambda表达式,还是需要多练多写,如果实在不会写,可以使用IDEA的提示功能,在可以使用Lambda表达式的地方,可以通过提示,自动转换成Lambda表达式。建议还是自己多写多练。