前言
在讲一下内容之前,我们需要引入函数式接口的概念
什么是函数式接口呢?
函数式接口:有且仅有一个抽象方法的接口
java中函数式编程的体现就是Lambda表达式,你可以认为函数式接口就是适用于Lambda表达式的接口.
也可以加上注解来在编译层次上限制函数式接口
- @Functionallnterface
- 放在 接口定义的上方:如果接口是函数式接口,编译通过;如果不是,编译失败
- 注:自定义的函数式接口可以加上这个表示是函数式接口,不加也可以,但是建议添加
常见的函数式接口有如下四种
接口 抽象方法 Consumer<T> 消费型接口 void accept(T t) Supplier<T> 供给型接口 T get() Function<T>函数型 R apply(T t) Predicted<T>判断型接口 boolean test(T t)
以下对Lambda表达式的说明都是基于函数式接口的
为什么需要Lambda表达式?
本身我们最原始的方法定义类来实现接口,在用类来实例化对象调用方法显得冗余且重,我们简化到匿名内部类的时候也显得冗余,主要目的是为了简化代码,并提供更加简洁和灵活的函数式编程方式。也与后面要说的stream api有关,这里不做过多赘述.
1.Lambda表达式
Lambda表达式的本质其实是一个接口实现类的对象,也是一个匿名函数.
下面我们就谈谈几种lambda表达式的应用场景
1.实现Runnable接口,注意此处不涉及到线程!!
Lambda表达式的思想就是能省略的就省略,不产生歧义即可
于是就有了这样的写法
@Test public void test1() { //语法格式1:无参数,无返回值 Runnable r1 = new Runnable() { @Override public void run() { System.out.println("我爱北京天安门"); } }; r1.run(); System.out.println("*******************"); Runnable r2 = ()->{ System.out.println("我爱北京天安门");}; r2.run(); }
2.当lanbda表达式中的参数类型确定时,参数类型也可以省略
@Test public void test3() { //数据类型可以省略,因为可以由编译器进行推断 Consumer<String> con1 = (String s)->{ System.out.println(s); }; con1.accept("如果大学可以重来,你最想重来的事是啥?"); System.out.println("****************"); Consumer<String> con2 = (s)->{ System.out.println(s); }; con2.accept("如果大学可以重来,你最想重来的事是啥?"); }
3.只有一个参数的时候,小括号可以省略
@Test public void test5() { //当只有一个参数的时候,参数的小括号可以省略 Consumer<String> con1 = s->{System.out.println(s);}; con1.accept("世界那么大,我想去看看"); }
4.只有一条语句时,return语句和大括号可以省略(必须一起省略)
Comparator<Integer> com1 = (o1,o2) ->{ return o1.compareTo(o2); }; System.out.println(com1.compare(12,6)); System.out.println("***********************"); Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);
总结:
格式
->:箭头操作符 ->的左边:Lambda形参列表:对应着要重写的接口中要重写的形参列表 ->的右边:Lambda体,对应着接口的实现类要重写的方法体 Lambda形参列表 ->Lambda体
细节注意
->的左边 :lambda 形参列表 :参数类型可以省略,如果形参列表只有一个,小括号也可以省略
->的右边:lambda体: 对应着重写方法的方法体,如果方法体中只有一条执行语句,则大括号可以省略,有return关键字,则return需要一并省略
2.方法引用
可以看做是Lambda表达式的进一步延伸
使用说明
情况1: 对象 :: 实例方法(非静态方法) 要求:函数式接口的抽象方法a与其内部实现时调用的某个方法b的形参列表和返回值类型都相同(或一致), 我们就可以考虑用方法b对方法a进行替换,此替换或覆盖称为方法引用 注:此时b是非静态的方法,需要对象来调用 情况2: 类 :: 静态方法 要求:函数式接口的抽象方法a与其内部实现时调用的某个方法b的形参列表和返回值类型都相同, 我们就可以考虑用b对方法a进行替换,此替换或覆盖称为方法引用 注:此时b是静态的方法,需要类来调用 情况3: 类 :: 实例方法 要求:函数式接口的抽象方法a与其内部实现时时调用的对象的某个方法b的返回值类型相同 同时,抽象方法a中有n个参数,方法b中有n-1个参数,且抽象方法a的第一个参数作为方法b的调用者,且抽象方法a 的后n-1个参数与方法b的n-1个参数类型相同或一致,则可以使用方法引用 注意:此方法b是静态方法,需要对象调用,但是形式上,写出a所属的类.
举例说明
1.对象::方法类型
此时我们发现get方法是空参方法,返回值是String,emp.getName()方法返回值是String形参也为空,这样就可以用这个实现的方法来覆盖原有的get方法,于是可以写作
emp::getName()
注意:这里的相同可以理解为满足多态即可.
@Test public void test2() { //供给型 Supplier中的T() get //Employee 中的String getName() Employee emp = new Employee(1001,"马化腾",34,6000.38); Supplier<String> sup1 = new Supplier<String>() { @Override public String get() { return emp.getName(); } }; System.out.println(sup1.get()); //Lambda表达式写法 Supplier<String> sup2 = ()-> emp.getName(); System.out.println(sup2.get()); //3.方法引用 Supplier<String> sup3 = emp :: getName; System.out.println(sup3.get()); }
2.类::静态方法举例
这里我们发现compare方法和实现中的Integer的compare方法参数和返回值一直,就可以使用方法引用,只不过这里的compare方法是静态方法,要使用类来调用,看做对原本抽象方法的一个覆盖,写作Integer :: compare;
@Test public void test3() { //类 :: 静态对象 Comparator<Integer> com1 = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return Integer.compare(o1,o2); } }; Comparator<Integer> com2 = (o1,o2) ->Integer.compare(o1,o2); System.out.println(com2.compare(12,21)); } Comparator<Integer> com3 = Integer :: compare;
3.类 :: 实例方法
这个的理解就想对困难一点点,本质和之前一样
这里我么假设抽象方法的形参有n个,实现的语句是形参1为调用者的语句
这里就可以把形参1抽象为其对应的类,剩余的返回值和形参都与原抽象方法一致
这样就可以用这种形式的方法引用代替
@Test public void test5() { //情况3 类 :: 实例方法(难) Comparator<String> com1 = new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }; //2.Lambda表达式 Comparator<String> com2 = (o1,o2) ->o1.compareTo(o2); //满足参数是 重写的函数的参数是需要调用的函数的参数的n+1个,也可以使用方法引用的方式 Comparator<String> com3 = String :: compareTo; }
3.构造器引用/数组引用
实际上是对方法引用的一种特殊操作
就是抽象方法里面返回一个构造器,如果把构造器看做一个方法,其实本质上就一样了
Supplier<Employee> sup1 = new Supplier<Employee>() { @Override public Employee get() { return new Employee(); } }; Supplier<Employee> sup2 = Employee :: new;
也可以是多参数的构造器引用,因为前面的泛型参数可以直接推断你的构造器类型
public void test2() { Function<Integer,Employee> func1 = new Function<Integer, Employee>() { @Override public Employee apply(Integer id) { return new Employee(id); } }; System.out.println(func1.apply(12)); //构造器引用 Function<Integer,Employee> func2 = Employee :: new; //调用的是Employee类中参数是Integer类型的构造器 func2.apply(11); }
三个,四个也是可以的
数组引用
和上面类似,不做过多解释
Function<Integer,Employee[]> func1 = new Function<Integer, Employee[]>() { @Override public Employee[] apply(Integer length) { return new Employee[length]; } }; Function<Integer,Employee[]> func2 = Employee[] :: new;
总结
Lambda表达式可以对函数式接口的实现代码进行精简,(满足一定条件)进一步引出了方法引用/构造器引用/数组引用等...
秋秋语录:今日事今日毕,语法基础一定要打扎实,大处着眼,小处着手,多看多练.