Lambad表达式与函数式接口
1.Lambda概述
Lambda是一个匿名函数,是java8的一个新特性,可以对函数式接口进行非常简洁的实现。
2.Lambda基础语法
2.1无参无返回值
接口:
@FunctionalInterface public interface MyLambda { void test(); }
调用:
public static void main(String[] args) { //创建对象 MyLambda lambda = () -> { System.out.println("我是无参无返回值的方法"); }; //调用方法 lambda.test(); }
2.2无参有返回值
接口:
@FunctionalInterface public interface MyLambda { int test(); }
调用:
public static void main(String[] args) { //创建对象 MyLambda lambda = () -> { System.out.println("我是无参有返回值的方法"); return 1; }; //调用方法 System.out.println(lambda.test()); }
2.3单参无返回值
接口:
@FunctionalInterface public interface MyLambda { void test(int a); }
调用:
public static void main(String[] args) { //创建对象 MyLambda lambda = (int a) -> { System.out.println("我是单参无返回值的方法,参数是:"+a); }; //调用方法 lambda.test(10); }
2.4多参无返回值
接口:
@FunctionalInterface public interface MyLambda { void test(int a,int b,int c); }
调用:
public static void main(String[] args) { //创建对象 MyLambda lambda = (int a,int b,int c) -> { System.out.println("我是多参无返回值的方法,参数是:"+a+","+b+","+c); }; //调用方法 lambda.test(10,20,15); }
2.5单参有返回值
接口:
@FunctionalInterface public interface MyLambda { int test(int a); }
调用:
public static void main(String[] args) { //创建对象 MyLambda lambda = (int a) -> { System.out.println("我是单参有返回值的方法,参数是:"+a); return a*3; }; //调用方法 System.out.println(lambda.test(10)); }
2.6多参有返回值
接口:
@FunctionalInterface public interface MyLambda { int test(int a,int b,int c); }
调用:
public static void main(String[] args) { //创建对象 MyLambda lambda = (int a,int b,int c) -> { System.out.println("我是多参有返回值的方法,参数是:"+a+","+b+","+c); return a+b+c; }; //调用方法 System.out.println(lambda.test(10, 20, 15)); }
2.7语法简化
1)参数类型。由于在接口的抽象方法中已经定义了参数的类型,那么在lambda中就可以省略参数。需要注意的是,如果要省略参数类型,就必须省略全部的参数类型。
2)参数小括号。如果参数只有一个时,可以省略小括号。
3)参数大括号。如果方法体中只有一条语句,可以省略大括号。如果需要返回值,则要同时省略return关键字。
实例1:参数类型省略
public static void main(String[] args) { //创建对象 MyLambda lambda = (a) -> { return a*3; }; //调用方法 System.out.println(lambda.test(10)); }
实例2:参数小括号省略
public static void main(String[] args) { //创建对象 MyLambda lambda = a -> { return a*3; }; //调用方法 System.out.println(lambda.test(10)); }
实例3:参数大括号省略
public static void main(String[] args) { //创建对象 MyLambda lambda = a -> a * 3; //调用方法 System.out.println(lambda.test(10)); }
2.8方法的引用
它的作用是把一个lambda表达式实现指向一个已经实现的方法。
语法:方法的隶属者::方法名
先看下面的一个实例:接口参考2.6。
public static void main(String[] args) { //创建对象 MyLambda lambda = (a,b,c) -> a+b+c; //创建对象 MyLambda lambda2 = (a,b,c) -> a+b+c; //调用方法 System.out.println(lambda.test(10,30,20)); }
如果在一个方法中对接口进行多次的实现,方法体的内容是一样的,写的也是比较麻烦的,那么此时就可以把方法体的内容写到一个新的方法中,然后去调用方法即可,再次调用时使用方法的引用就好。
public class LambdaTest { public static void main(String[] args) { //创建对象 MyLambda lambda = (a,b,c) -> add(a,b,c); //方法的引用,类名::方法名 MyLambda lambda2 = LambdaTest::add; //调用方法 System.out.println(lambda.test(10,30,20)); System.out.println(lambda2.test(10,30,20)); } private static int add(int a,int b,int c){ return a+b+c; } }
3.函数式接口
函数式接口指的是有且仅有一个抽象方法的接口。
3.1说明
函数式接口要求接口中只能有一个公共抽象方法,原因是lambda只能实现一个方法。另外,可以在接口上添加注解 @FunctionalInterface
,此注解是用于标记此接口类是一个函数式接口,但是这个注解并非是实现函数式接口的必须项。换句话说,一方面可以方便代码的理解,告知这个代码是按照函数式接口来定义实现的,另一方面也是供编译器协助检查,如果此方法不符合函数式接口的要求,会直接编译失败,方便处理。
比如常见的多线程中Runable接口,就是一个函数式接口
但查看jdk源码中Comparator就会发现,其有两个抽象方法和一些默认的方法,举例如下:
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
//省略方法中的代码...
}
//省略其他方法...
}
可以看出,此函数式接口有两个抽象函数compare()和equals(),还有一些默认的方法,这些默认的方法是java8接口的新特性,不再多说。那么为什么此接口可以拥有两个抽象方法?用idea仔细观察两个方法会发现不同
区别在于equals()方法有一个向上的箭头,点击后会进入到Object类equals()方法,也就是说这个方法是对Object类(所有类的父类)中equals()方法的重写。因此equals()方法只能算是对父类接口的一个覆写,而不算是此接口类自己的抽象方法,所以此方法里面实际上还是只有 1个抽象方法,并没有违背函数式接口的约束条件。
可能不太好理解,新建一个函数式接口进行说明:
这样是不是就能更好的理解了。同样的,若把equals()方法换个名字,编译器就会直接提示警告
上述两个例子很好的验证了函数式接口限制了此接口只能有一个抽象方法,如果有多个就会报错。
那么函数式接口在实际场景中有什么作用呢?就是处理某种业务逻辑时,不同的场景拥有不同的处理方式,而这些方式又不是固定的,需要根据场景去动态处理,此时通用的业务处理方法已不适用,要是每个地方都做自己的处理,又显得代码冗余,此时可以定义一个接口,不同的地方做不同的业务实现即可,那么这个接口就叫做函数式接口。
在java中有四大内置的函数式接口,为接口的实现提供了方便,见下表:
接口名 | 方法 | 参数 | 返回值 | 说明 | 举例 |
Predicate
|
boolean test(T t)
|
T | boolean | 断言型接口。对给定的参数进行判断,返回一个布尔值 | Stream的filter() |
Consumer
|
void accept(T t)
|
T | 无 | 消费型接口。对给定的参数执行指定的操作(消费数据) | Stream的forEach() |
Function
|
R apply(T t)
|
T、R | R | 函数型接口。根据一个类型的数据得到另一个类型的数据 | Stream的map() |
Supplier
|
T get()
|
无 | T |
供给型接口。按某种实现逻辑并返回一个结果(生产数据) |
Stream的collect() |
这些接口的使用下面详细介绍。
3.2 Predicate -test布尔判断
//将满足条件的字符串,放到集合中 public List<String> filterStr(List<String> list, Predicate<String> pre){ List<String> newList=new ArrayList<>(); for (String str:list) { if(pre.test(str)){ newList.add(str); } } return newList; } @Test public void test(){ List<String> list= Arrays.asList("idea","eclipse","predicate","function"); List<String> returnList= filterStr(list,(str)->str.length()>4); for (String str:returnList) { System.out.println(str); } }
3.3 Consumer -accept消费数据
//对接口进行实现 public void shopping(double money, Consumer<Double> con) { con.accept(money); } @Test public void test() { shopping(1000, x -> System.out.println("今天购物花了" + x + "元")); }
3.4 Function -apply数据处理
//处理字符串 public String handlerStr(String str, Function<String,String> fun){ return fun.apply(str); } @Test public void test(){ String newStr= handlerStr("hello world,lambda",(str)->str.substring(3,6)); System.out.println(newStr); }
3.5 Supplier -get生产数据
//产生指定个数的整数,放到集合中并返回 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; } @Test public void test() { List<Integer> numList = getNumList(3, () -> (int) (Math.random() * 10)); for (Integer n : numList) { System.out.println(n); } }
3.6 编程范式
主流编程范式有命令式编程与声明式编程,而函数式编程是声明式编程思想的具体实践。
1)命令式编程就是让代码按照一定的顺序和逻辑去执行,是具体的。(如Java常规的编程)
2)声明式编程是只需要声明,无需指定具体的处理逻辑。(如正则表达式)
下面使用一个例子来说明两者的区别:从一个集合中获取所有的偶数
命令式编程写法
List<Integer> list = Arrays.asList(1, 5, 34, 9, 99, 33, 56, 78); List<Integer> result = new ArrayList<>(); for (Integer n : list) { if (n % 2 == 0) result.add(n); }
声明式编程写法
List<Integer> list = Arrays.asList(1, 5, 34, 9, 99, 33, 56, 78); List<Integer> result = new ArrayList<>(); result = list.stream().filter(n -> n % 2 == 0).collect(Collectors.toList());
对比起来,是不是声明式更简洁?比如filter方法,指定过滤条件即可,不需要关心它内部是如何过滤的。
4.lambda综合应用
4.1forEach遍历集合
Map<String, Integer> map = new HashMap<>(); map.put("zhangs", 20); map.put("lisi", 10); map.put("wangwu", 40); map.put("liliu", 30); map.forEach((key, value) -> System.out.println(key + "," + value));
集合可以使用forEach方法遍历,这里主要介绍遍历map。
4.2集合list排序
List<Integer> list = Arrays.asList(10, 50, 30, 20, 15, 80, 90); list.sort((o1, o2) -> o1 - o2); System.out.println(list);
4.3集合treeSet排序
TreeSet<Integer> set = new TreeSet<>((o1, o2) -> { if (o1 <= o2) { return -1; } else { return 1; } }); set.add(10); set.add(15); set.add(20); set.add(10); set.add(50); set.add(30); set.add(9); System.out.println(set);
4.4removeIf()
删除集合中符合条件的方法
List<Integer> list = new ArrayList<>(); list.add(10); list.add(50); list.add(30); list.add(20); list.add(15); list.add(80); list.add(90); //删除小于50的元素 list.removeIf(o -> o < 50); System.out.println(list);
4.5开启线程
Thread thread=new Thread(()->{ for (int i = 0; i < 20; i++) { System.out.println(i); } }); thread.start();