最近几天学习了一下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