新特性2-lambda表达式

时间:2020-12-06 19:13:28

最近几天学习了一下lambda表达式,看了不少博客,感觉有一篇博客总结的一句话总结的很好:lambda表达式是一段可以传递的代码,它的核心思想是将面向对象中的传递数据变成传递行为。其实以前也有传递行为的方式,c++里是函数指针,java中有一种神奇的东西叫做匿名内部类,而现在的lambda表达式,提供了一种取代匿名内部类的途径,并且将其更规范化的变成了一类接口——函数是接口。以下将分为三个大的部分进行介绍,第一部分将对lambda表达式的语法格式进行介绍,第二个部分将继续第一篇对函数式接口的介绍去大体介绍一下java.util.function,第三个部分会举例子

lambda表达式基本语法

lambda表达式的基本格式有两种:

(params)->expression 或者 (params)->{statements;}

对于这两种表达,有以下四条语法约束:

1、参数可选类型声明:可以不写出参数的类型,编译器会进行自动推断

2、可选参数圆括号:当只有一个从参数的时候,可以不加圆括号

3、可选大括号:当整个lambda函数体只有一行语句的时候,可以省略掉大括号

4、可选的返回关键字:当只有一条语句的时候,可以不显示的写出return

为了让大家明白上述规则,我们来看下面代码段(注意,当使用泛型的时候一定要传入类,不要传入基本类型

 

 1 public class LambdaExpTest {
 2     
 3     @FunctionalInterface
 4     interface LambdaFuncInterface<T> {
 5         public abstract T function(T x, T y);
 6     }
 7     
 8     @FunctionalInterface
 9     interface TestInterface<T> {
10         public abstract T function(T x);
11     }
12 
13     public static void main(String[] args) {
14         /**
15          * 我们可以看下面这个是最完整的lambda表达式,在上面的函数式接口中有一个两个参数的方法,所以这里也是声明了Integer类型的x和y
16          * 函数体有两句话,第一句两个数相减,第二句话返回这个值
17          */
18         LambdaFuncInterface<Integer> helper = (Integer x, Integer y)->{
19             int tmp = x - y;
20             return tmp;
21         };
22         System.out.println(helper.function(10, 15));
23         
24         /**
25          * 写成这个样子也是可以的,但是要注意的是一定要两个都不写,或者两个都写,如果写成(Integer x, y)这样子的话就会报错了
26          */
27         helper = (x, y)->{
28             int tmp = x - y;
29             return tmp;
30         };
31         
32         /**
33          * 写成这样也是可以的,就是只有一行函数体,然后就可以不写return和返回值
34          */
35         helper = (x, y)-> x - y;
36         
37         /**
38          * 当我们的函数接口只需要一个参数的时候,可以省略掉参数的括号,一下两种写法都对
39          */
40         TestInterface<Integer> helper2 = (x) -> -x;
41         helper2 = x -> -x;
42     }
43     
44 }

 

函数式接口java.util.function

java.util.function是jdk1.8新加入的一个函数式接口的包,在jdk1.8之前也有几个函数式接口,在这里给大家罗列一下

java.long.Runnable

java.util.concurrent.Callable

java.util.Comparator

java.io.FileFilter

 

java.security.PrivilegedAction

java.nio.file.PathMatcher

java.lang.reflect.InvocationHandler

java.beans.PropertyChangeListener

java.awt.event.ActionListener

javax.swing.event.ChangeListener

我们可以看一下java.util.function包下大概可以分成4大类接口:消费型接口、供给型接口、函数型接口和断言型接口

 

接口 参数 返回值 类别
Consumer T void 消费型接口
Supplier None T 供给型接口
Function T R 函数型接口
Predicate T boolean 断言型接口

 

 

 

 

 

消费型接口源码

 1 /**
 2  * 这个接口不像其他函数式接口,这个接口期待以副作用的形式参与操作
 3  * 这里的副作用是指函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果
 4  */
 5 @FunctionalInterface
 6 public interface Consumer<T> {
 7 
 8     /**
 9      * 本方法是传入lambda表达式覆盖的方法
10      */
11     void accept(T t);
12 
13     /**
14      * 这个方法给出是融合多个Consumer,按顺序组合成一个系列的消费型接口
15      */
16     default Consumer<T> andThen(Consumer<? super T> after) {
17         Objects.requireNonNull(after);
18         return (T t) -> { accept(t); after.accept(t); };
19     }
20 }

 

供给型接接口源码

 1     /**
 2      * 看这个接口的定位,一般作为一个简单的工厂
 3      */
 4     @FunctionalInterface
 5     public interface Supplier<T> {
 6 
 7         /**
 8          * 这里通过lambda表达式传入行为,注意没有参数
 9          */
10         T get();
11     }

 

函数型接口库

 1     @FunctionalInterface
 2     public interface Function<T, R> {
 3 
 4         /**
 5          * 这个是来接收lambda表达式的方法
 6          */
 7         R apply(T t);
 8 
 9         /**
10          * 这个是在运行apply方法之前可以执行的方法
11          */
12         default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
13             Objects.requireNonNull(before);
14             return (V v) -> apply(before.apply(v));
15         }
16 
17         /**
18          * 这个是在运行apply方法之后运行的方法
19          */
20         default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
21             Objects.requireNonNull(after);
22             return (T t) -> after.apply(apply(t));
23         }
24 
25         /**
26          * 这个方法将返回输入,在stream里面,生成List或者Map的时候会用到
27          */
28         static <T> Function<T, T> identity() {
29             return t -> t;
30         }
31     }

 

断言型接口源代码

 1     @FunctionalInterface
 2     public interface Predicate<T> {
 3 
 4         /**
 5          * 通过lambda表达式来实现这个方法,注意lambda表达式要求一个参数,返回值为Boolean类型
 6          */
 7         boolean test(T t);
 8 
 9         /**
10          * 和上面的test方法进行与操作
11          */
12         default Predicate<T> and(Predicate<? super T> other) {
13             Objects.requireNonNull(other);
14             return (t) -> test(t) && other.test(t);
15         }
16 
17         /**
18          * test方法进行非操作
19          */
20         default Predicate<T> negate() {
21             return (t) -> !test(t);
22         }
23 
24         /**
25          * 和test方法进行或操作
26          */
27         default Predicate<T> or(Predicate<? super T> other) {
28             Objects.requireNonNull(other);
29             return (t) -> test(t) || other.test(t);
30         }
31 
32         /**
33          * 注意,这里返回的不是Boolean,而是一个Predicate
34          */
35         static <T> Predicate<T> isEqual(Object targetRef) {
36             return (null == targetRef)
37                     ? Objects::isNull
38                     : object -> targetRef.equals(object);
39         }
40     }

在这里其实想补充一下断言型接口的那个静态方法的用法,第一次遇到这样的写法不太好理解

 1     public static void main(String[] args) {
 2         /**
 3          * 以下三种写法是等价的,第一种和第二种相比的优势在在于,多了一个isEqual可以传的参数,不会把程序写死,这里可以传递一个上下文的变量
 4          * 而第三种呢,其实就是isEqual这个静态方法返回了一个已经被lambda表达式赋值的Predicate接口,所以可以直接调用test
 5          */
 6         //第一种
 7         String s = "test";
 8         Predicate<String> p = Predicate.isEqual(s);
 9         System.out.println(p.test("test"));
10         //第二种
11         Predicate<String> p2 = (null == "test") ? Objects::isNull : object -> "test".equals(object);
12         System.out.println(p2.test("test"));
13         //第三种
14         System.out.println(Predicate.isEqual("test").test("test"));
15     }

 

手把手的来几发lambda表达式练习(这里借鉴了http://www.importnew.com/16436.html点击传送门过去)

1、用lambda表达式实现Runnable接口

 

 1     public static void main(String[] args) {
 2         //before java 8
 3         new Thread(new Runnable(){
 4 
 5             @Override
 6             public void run() {
 7                 // TODO Auto-generated method stub
 8                 System.out.println("before java 8");
 9             }
10             
11         }).start();
12         //after java 8
13         new Thread(()->System.out.println("after java 8")).start();
14     }

 

2、使用lambda表达式进行事件处理

 1     public static void main(String[] args) {
 2         //before java 8
 3         JButton jb = new JButton();
 4         jb.addActionListener(new ActionListener() {
 5 
 6             @Override
 7             public void actionPerformed(ActionEvent e) {
 8                 // TODO Auto-generated method stub
 9                 System.out.println("before java 8");
10             }
11             
12         });
13         //after java 8
14         jb.addActionListener(e -> System.out.println("after java 8"));
15     }

3、使用lambda表达式进行迭代

1     public static void main(String[] args) {
2         //before java 8
3         List<String> abc = Arrays.asList("abc", "def", "jhi");
4         for(String s : abc) {
5             System.out.println(s);
6         }
7         //after java 8
8         abc.forEach(System.out::println);
9     }

这里我们来看一下List::forEach源码

1     /**
2      * forEach的参数是一个消费者接口,需要传入一个lambda表达式
3      */
4     default void forEach(Consumer<? super T> action) {
5         Objects.requireNonNull(action);
6         for (T t : this) {
7             action.accept(t);
8         }
9     }

我们来继续做一些复杂的操作(注意:这里其实还是用java7以前的东西,否则这里应该用流)

 1     public static void main(String[] args) {
 2         List<String> abc = Arrays.asList("abc", "def", "jhi");
 3         List<String> t = new ArrayList<String>();
 4         abc.forEach(e -> {
 5             if(e.contains("a")) {
 6                 t.add(e);
 7             }
 8         });
 9         System.out.println(t);
10     }

4、使用lambda表达式和断言式接口

 1     public static void main(String[] args) {
 2         List<String> languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp", "JS");
 3         System.out.println("start with J");
 4         filter(languages, s -> s.startsWith("J"));
 5         System.out.println("start with J and contains a");
 6         andFilter(languages, s -> s.startsWith("J"), s -> s.contains("a"));
 7         System.out.println("start with J or contains a");
 8         orFilter(languages, s -> s.startsWith("J"), s -> s.contains("a"));
 9         
10     }
11     
12     public static void filter(List<String> name, java.util.function.Predicate<? super String> condition) {
13         name.stream().filter(condition).forEach(System.out::println);;
14     }
15     
16     public static void andFilter(List<String> name, Predicate<String> condition1, Predicate<String> condition2) {
17         name.stream().filter(condition1.and(condition2)).forEach(System.out::println);
18     }
19     
20     public static void orFilter(List<String> name, Predicate<String> condition1, Predicate<String> condition2) {
21         name.stream().filter(condition1.or(condition2)).forEach(System.out::println);
22     }

5、lambda表达式与map和reduce

 1     public static void main(String[] args) {
 2         List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
 3         costBeforeTax.stream().map((cost) -> cost + .12*cost).forEach(System.out::println);
 4         //注意到这里的reduce中的sum等于map后的第一个值,cost等于map后的第二个值,但是之后的行为是 ,将返回值赋给sum
 5         //而map中继续传进来的值赋给cont
 6         double d = costBeforeTax.stream().map((cost) -> cost + .12*cost).reduce((sum, cost) -> {
 7             System.out.println("sum=" + sum);
 8             System.out.println("cost2=" + cost);
 9             return sum + cost;
10         }).get();
11         System.out.println(d);        
12     }

6、用lambda表达式写一个简单的 AOP

 1     public static void main(String[] args) {
 2         AOP(x -> {
 3             System.out.println("deal x"); 
 4             return x;
 5             }, 
 6             x -> {
 7             System.out.println("before deal x"); 
 8             return x;
 9             }, 
10             x -> {
11             System.out.println("after deal x");
12             return x;
13             });        
14     }
15     
16     public static void AOP(Function<Object, Object> aim, Function<Object, Object> before, Function<Object, Object> after) {
17         aim.compose(before).andThen(after).apply(1);
18     }

 

总结

本文重要描述了lambda表达式的用法,但是并没有对其原理进行探究,不过还是继续说一些我的感受吧,lambda表达式看起来确实比从前的代码更简练,可读性更高一些,但是这都是建立在行为比较短暂的基础上,基本上三五行就描述清楚了,如果一个行为需要大量的语句去描述,我还是建议大家写成匿名内部类的形式,或者写一个类去实现接口,让自己的代码看起来更干净一些。第二部分介绍的几个函数式接口只是java.util.function中最基础的四个接口,其他接口基本上都是从这四个接口衍生出来的,大家可以自行阅读Bi开头的接口,就是变成了两个参数。第三部分举例子的时候不可比免的使用了stream,我们第三篇新特性的讲解,就会介绍stream