Java8-Lambda表达式
一、Lambda 表达式的基础语法:
Java8中引入了一个新的操作符 “->” 该操作符成为”箭头操作符”或者 Lambda 表达式”箭头操作符” 将Lambda 表达式拆成了两部分
- 左侧:Lambda 表达式的参数列表
- 右侧:Lambda 表达式中所需要执行的功能即 Lambda 体
Lambda表达式的几种格式
语法格式一:
无参数,无返回值
() -> System.out.println("hello");
@Test
public void test1(){
Runnable r = () -> System.out.println("hello!");
r.run();
}
语法格式二:
有参数,无返回值
(e) -> System.out.println("hello");
@Test
public void test2(){
Consumer<Integer> c = (e) -> System.out.println("hello!");
c.accept(1000);
}
语法格式三:
如果只有一个参数小括号可以不用写
e -> System.out.println("hello");
语法格式四:
有两个以上的参数,有返回值,并且 Lambda 体重有多条语句
Comparator<Integer> c = (x,y) -> {
return Integer.compare(x, y);
};
@Test
public void test3(){
Comparator<Integer> c = (x,y) -> {
return Integer.compare(x, y);
};
System.out.println(c.compare(100, 99));
}
语法格式五:
如果 Lambda 体中只有一条语句,return 和 {} 都可以省略不写
Comparator<Integer> c = (x,y) -> return Integer.compare(x, y);
语法格式六:
Lambda 表达式的参数列表的数据类型可以省略不谢,因为JVM编译器能够通过上下文推断出数据类型
即”类型推断”
(Integer x,Integer y) -> Integer.compare(x,y);
相当于
(x,y) -> Integer.compare(x,y);
二、 Lambda 表达式和“函数式接口”
Lambda 表达式需要“函数式接口”的支持
- 函数式接口:接口中只有一个抽象方法的接口,成为函数是接口。可以使用注解
@FuncationalInterface
修饰
一个函数式接口的例子
// 函数式接口
@FunctionalInterface
interface FuncInter {
int deal(Integer x,Integer y);
}
/** 测试“函数式接口” */
@Test
public void test4(){
FuncInter fi = (x,y) -> {
return x+y;
};
System.out.println(fi.deal(100, 900));
showMap(new HashMap<>()); // jdk1.8之后才支持函数的类型推断
}
Lambda的四大函数式接口
通常配合策略模式使用,设计模式参考相关博文。
消费性接口
Consumer<T> : 消费型接口
void accept(T t);
//Consumer<T> 消费型接口 :
@Test
public void test1(){
happy(10000, (m) -> System.out.println("消费:" + m + "元"));
}
public void happy(double money, Consumer<Double> con){
con.accept(money);
}
供给型接口
Supplier<T> : 供给型接口
T get();
//Supplier<T> 供给型接口 :
@Test
public void test2(){
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<T, R> : 函数型接口
R apply(T t);
//Function<T, R> 函数型接口:
@Test
public void test3(){
String newStr = strHandler("\t\t\t 天气 ", (str) -> str.trim());
System.out.println(newStr);
String subStr = strHandler("天气", (str) -> str.substring(2, 5));
System.out.println(subStr);
}
//需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun){
return fun.apply(str);
}
断言型接口
Predicate<T> : 断言型接口
boolean test(T t);
//Predicate<T> 断言型接口:
@Test
public void test4(){
List<String> list = Arrays.asList("Hello", "world", "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;
}
总结
查看源码可以看到这些接口都使用了@FuncationalInterface
注解做修饰,同时只有一个抽象方法。
使用这些函数式接口的本质实际上就是使用Lambda表达式实现这些函数式接口的唯一的抽象方法。
Java8的语法会自动的将我们写的Lambda表达式当做一个匿名内部类实现这些函数式接口
Lambda的方法引用
我们之前在书写Lambda函数体的时候发现大多数函数的实现在JavaSE的官方API中已经实现过了,我们可以直接引用这些实现的方法而不需要自己再次书写lambda的实现体,该方式就叫做函数引用
三种使用方式
首先搞一个测试类:
/** 测试类 */
class Employee{
private int id;
private String name;
private double salary;
Employee(){}
Employee(int id){
this.id = id;
}
Employee(String name){
this.name =name;
}
Employee(int id,String name){
this.id = id;
this.name =name;
}
Employee(int id,String name,double salary){
this.id = id;
this.name = name;
this.salary = salary;
}
// 省略 getter 和 setter 和toString 方法
}
对象::实例方法
/** 对象::实例方法
* System.out 返回的是一个 PrintStream 对象,该对象有一个 println(x)
* 方法和Consumer 函数接口的唯一抽象方法 <参数列表> 和 <返回值> 一样
* 所以可以指向该方法,类似于"函数指针"
*/
@Test
public void test1() {
Consumer<String> con = (x) -> System.out.println(x);
con.accept("123");
Consumer<String> con2 = System.out::println;
con2.accept("789");
Employee emp = new Employee();
Supplier<String> sup = emp::getName;
System.out.println(sup.get()); // null
}
类::静态方法
/**
* 类::静态方法
*/
@Test
public void test2() {
Comparator<Integer> com = Integer::compare;
System.out.println(com.compare(100, 99));
}
类::实例方法
/**
* 类::实例方法
*/
@Test
public void test3() {
BiPredicate<String , String> bi = (x,y) -> x.equals(y);
System.out.println(bi.test("123", "123")); // true
BiPredicate<String , String> bi2 = String::equals;
System.out.println(bi2.test("123", "123")); // true
/**
* 注意这里使用 类::实例方法
* 真正的含义是:
* [1] 类的成员方法不能是静态的,而这个情况其实和静态方法类似,
* 区别是,Lambda表达式的参数个数需要等于所调用方法的入参个数加一。
* 为什么要加一?
*
* [2] 因为类的成员方法不能通过类名直接调用,只能通过对象来调用,
* 也就是Lambda表达式的第一个参数,是方法的调用者,
* 从第二个开始的参数个数要和需要调用方法的入参个数一致即可。
*/
// 此处本应该是两个参数 (x,y) -> x.aaa(y)
// 之所以可以这样使用相当于 (e,空) -> e.getName(空) 这里的空就相当于第二个参数
// 注意此处的空不是 null
Function<Employee,String> fun = Employee::getName;
System.out.println(fun.apply(new Employee("Bart")));//Bart
}
关于类::实例方法参考该博客,讲的比较详细:
小总结
注意:
- [1] Lambda 体中调用方法的参数列表与返回值类型要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
- [2] Lambda 参数列表中的第一参数是实例方法的调用者,二第二个参数是该实例方法的参数是,可以使用 类::实例方法
构造器引用
格式:
ClassName::new
/**
* 构造器::new
*/
@Test
public void test4() {
Supplier<Employee> su = () -> new Employee();
System.out.println(su.get());
// 构造器引用方式
Supplier<Employee> su2 = Employee::new; // 引用无参构造器
System.out.println(su2.get());
Function<Integer,Employee> fun = Employee::new; // 引用一个参数构造器
System.out.println(fun.apply(20));
}
发现根据实现的函数式接口的抽象类的不同可以自动映射到不同的构造方法。
数组引用
数组引用:
格式:
Type[]::new
/**
* 数组类型[]::new
*/
@Test
public void test5() {
Function<Integer,String[]> fun = (x) -> new String[x];
System.out.println(fun.apply(20).length);//20
Function<Integer,String[]> fun2 = String[]::new;
System.out.println(fun2.apply(20).length);//20
}