公子奇带你一步一步了解Java8中Lambda表达式
Posted 公子奇的博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了公子奇带你一步一步了解Java8中Lambda表达式相关的知识,希望对你有一定的参考价值。
在上一篇《公子奇带你一步一步了解Java8中行为参数化》中,我们演示到最后将匿名实现简写为
1 (Police police) -> "浙江".equals(police.getPoliceNativePlace());
这是一个带有箭头的函数,这种写法在Java8中即为Lambda表达式。那么我们就来好好的讲讲Lambda表达式。
一 、什么是Lambda表达式
首先我们需要知道Lambda表达式时JDK8中提出的编码风格,它可以简洁地表示可作为参数传递的匿名函数的一种方式,也可以理解为匿名实现的一种,关于匿名对象的特征它也是有的,例如:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。基本语法可以使用以下两种方式表示:
1 (parameters) -> expression 2 或 3 (parameters) -> { statements; }
由上可知Lambda表达式有三个部分:
1、参数列表
2、箭头 “ -> ” 用来将参数和主体连接到一起
3、Lambda主体
以下我们通过一些案例来表示一个有效的Lambda表达式
1 () -> "ABC"; //没有参数但有返回值ABC,return 关键字自动忽略 2 () -> {}; //没有参数,方法体不执行任何操作 3 () -> {return "ABC"}; //没有参数,返回ABC,有大括号,需要显示return 4 (String s) -> s.length(); //有一个参数,并对参数进行操作 5 (int x, int y) -> { //有两个参数,并做复杂操作,需要大括号 6 System.out.println("result:"); 7 System.out.println(x + y); 8 } 9 (String s1, String s2) -> s1.compareTo(s2) //对两个参数操作
二、什么是函数式接口
在上一篇文章中,我们最后的Lambda表达式是作为一个参数传递给filter方法的,同时在filter方法中的第二个参数是一个接口Predicate<T>,这个接口在JDK8中我们就将其称为函数式接口,回看这个接口只有一个抽象方法。即函数式接口就是只定义一个抽象方法的接口。同时在JDK8中也使用了注解 @FunctionInterface 将一个接口标注为函数式接口,当然没有添加该注解也可为函数式接口,只是添加后程序在运行时会进行检测,否则会抛出异常。同时为了实现更灵活的操作,接口中可以添加静态方法和默认方法(即JDK8之后,接口中是可以定义方法实现的)。
1 package com.hz; 2 3 /** 4 * 在1.8之前,我们一直强调接口中不可有方法实现 5 * 1.8之后是可以在接口中定义默认方法和静态方法 6 */ 7 public interface InterfaceMethod { 8 9 default void method1() { 10 System.out.println("接口的默认方法实现..."); 11 } 12 13 static void method2() { 14 System.out.println("接口的静态方法实现..."); 15 } 16 17 public static void main(String[] args) { 18 InterfaceMethod.method2(); 19 20 new InterfaceMethod() {}.method1(); 21 } 22 } 23 24 //官方提供的一个函数式接口 25 package java.util.function; 26 27 import java.util.Objects; 28 29 @FunctionalInterface 30 public interface Predicate<T> { 31 boolean test(T t); 32 33 default Predicate<T> and(Predicate<? super T> other) { 34 Objects.requireNonNull(other); 35 return (t) -> test(t) && other.test(t); 36 } 37 38 default Predicate<T> negate() { 39 return (t) -> !test(t); 40 } 41 42 default Predicate<T> or(Predicate<? super T> other) { 43 Objects.requireNonNull(other); 44 return (t) -> test(t) || other.test(t); 45 } 46 47 static <T> Predicate<T> isEqual(Object targetRef) { 48 return (null == targetRef) 49 ? Objects::isNull 50 : object -> targetRef.equals(object); 51 } 52 } 53 54 //说明:从官网的Predicate接口中我们可以发现除了多了注解和一些方法实现,与我们上一讲自己定义的Predicate接口很类似
三、为什么提出函数式接口
可能到这里你也发现了,既然Lambda表达式已经很简洁的表达了实现,我们为什么还需要引入函数式接口的概念呢?为了简化代码和灵活运用,我们提出了Lambda表达式的概念,Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。由此即可理解:Lambda表达式是函数式接口的一种实现。
在JDK8中我们会发现大部分函数式接口都添加了 @FunctionInterface 注解,但我们不能仅仅理解为只有添加了该注解的才为函数式接口,我们应该理解的是只有一个抽象方法的接口才为函数式接口。
四、应用场景
既然Lambda表达式这么好,那么我们应该在哪里去使用呢?下面介绍一些常用的:
1、列表循环操作
1 package com.hz; 2 3 import java.util.Arrays; 4 import java.util.List; 5 import java.util.function.Consumer; 6 7 public class LambdaTest { 8 public static void main(String[] args) { 9 List<String> ss = Arrays.asList("gong", "zi", "qi", "666"); 10 11 //打印列表中每个值长度 12 //---匿名实现 13 ss.forEach(new Consumer<String>() { 14 @Override 15 public void accept(String s) { 16 System.out.println(s.length()); 17 } 18 }); 19 20 System.out.println("------------------"); 21 22 //--遍历取值 23 for (String s : ss) { 24 System.out.println(s.length()); 25 } 26 27 System.out.println("---------"); 28 29 //--Lambda表达式 30 ss.forEach((String s) -> System.out.println(s.length())); 31 } 32 } 33 //哪种方式简洁、容易理解很明显
2、事件监听
1 //监听实现一 2 Button button = new Button("提交"); 3 button.addActionListener(new ActionListener() { 4 @Override 5 public void actionPerformed(ActionEvent e) { 6 System.out.println("用户点击了提交按钮"); 7 } 8 }); 9 10 //监听实现二 11 button.addActionListener(e -> { 12 System.out.println("用户点击了提交按钮"); 13 });
3、函数式接口(回看上一篇文章)
更多应用场景,我们在后续文章中再整理。
五、引出方法引用
我们继续回看上一篇,发现在最后调用表达式是何其的相似
1 Police police) -> police.getPoliceAge() > 30; 2 3 System.out.println("---------------"); 4 5 (Police police) -> "浙江".equals(police.getPoliceNativePlace());
那么在JDK8中还可以再次简化吗?答案是肯定的,这就是方法引用。即
1 Police :: getPoliceAge; 2 String :: equals;
方法引用的加入可以让我们重复使用现有的方法定义,并像Lambda一样传递它们。我们同时可理解为方法引用是针对仅仅涉及单一方法的Lambda的语法糖。
那么什么情况下,我们可以及如何构建方法引用?
1、指向静态方法的方法引用。
2、指向任意类型实例方法的方法引用。
3、指向现有对象的实例方法的方法引用。
六、复合Lambda表达式组装
在实际开发中,我们不可能只操作一种或一个Lambda表达式,一个表达式的输出可能会是另一个表达式的输入,两个条件的同时满足(例如上一篇中籍贯为浙江年龄大于30的民警),或多个条件只要有一个合适即命中(例如上一篇中籍贯为浙江或年龄大于30的民警)等。如此我们将其分为3类。
1、比较器复合
继续回到上一篇文章,如何对民警年龄进行排列。
1 public static void main(String[] args) { 2 List<Police> polices = Arrays.asList(new Police("P001", "余警官", 27, "浙江"), 3 new Police("P002", "李警官", 32, "安徽"), 4 new Police("P003", "程警官", 25, "安徽"), 5 new Police("P004", "杨警官", 35, "浙江"), 6 new Police("P005", "杨警官", 31, "上海")); 7 8 polices.sort(comparing(Police :: getPoliceAge).reversed()); 9 10 System.out.println("结果1: " + polices); 11 }
2、谓词复合
1 Predicate<Police> p = (Police police) -> police.getPoliceAge() > 30; 2 Predicate<Police> p2 = p.and((Police police) -> "浙江".equals(police.getPoliceNativePlace())); 3 List<Police> result = filter(polices, p2); 4 System.out.println(result); 5 6 //当然 除了 and 还有 or方法
3、函数复合
1 Function<Integer, Integer> f = x -> x + 1; 2 Function<Integer, Integer> g = x -> x * 2; 3 Function<Integer, Integer> h = f.andThen(g); 4 int result = h.apply(1); 5 System.out.println(result);
七、一个实例
回到上一篇文章场景,将按照一定条件得到的民警按照年龄、籍贯排序。
1 package com.hz; 2 3 import java.util.ArrayList; 4 import java.util.Arrays; 5 import java.util.List; 6 import java.util.function.Predicate; 7 8 import static java.util.Comparator.comparing; 9 10 public class PoliceMain { 11 public static void main(String[] args) { 12 List<Police> polices = Arrays.asList(new Police("P001", "余警官", 27, "浙江"), 13 new Police("P002", "李警官", 32, "安徽"), 14 new Police("P003", "程警官", 25, "安徽"), 15 new Police("P004", "杨警官", 35, "浙江"), 16 new Police("P005", "张警官", 31, "上海"), 17 new Police("P006", "王警官", 42, "浙江"), 18 new Police("P007", "赵警官", 31, "浙江"), 19 new Police("P008", "刘警官", 49, "浙江"), 20 new Police("P009", "周警官", 32, "浙江")); 21 22 //问题:找出籍贯为浙江并且年龄大于30岁的民警或者籍贯为安徽的民警,按照民警年龄排序,若年龄相同按照籍贯排序 23 Predicate<Police> p1 = (Police police) -> police.getPoliceAge() > 30; 24 25 Predicate<Police> p2 = p1.and((Police police) -> "浙江".equals(police.getPoliceNativePlace())); 26 27 Predicate<Police> p3 = p2.or((Police police) -> "安徽".equals(police.getPoliceNativePlace())); 28 29 List<Police> result = filter(polices, p3); 30 31 result.sort(comparing(Police :: getPoliceAge).thenComparing(Police :: getPoliceNativePlace)); 32 33 System.out.println("结果: " + result); 34 } 35 36 static <T> List<T> filter(List<T> con, Predicate<T> p) { 37 List<T> result = new ArrayList<>(); 38 39 for (T e : con) { 40 if (p.test(e)) { 41 result.add(e); 42 } 43 } 44 45 return result; 46 } 47 48 } 49 50 // 以上方式需要我们自己去定义一个filter方法 或按照以下方式 51 52 package com.hz; 53 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.List; 57 import java.util.function.Function; 58 import java.util.function.Predicate; 59 60 import static java.util.Comparator.comparing; 61 62 public class PoliceMain { 63 public static void main(String[] args) { 64 List<Police> polices = Arrays.asList(new Police("P001", "余警官", 27, "浙江"), 65 new Police("P002", "李警官", 32, "安徽"), 66 new Police("P003", "程警官", 25, "安徽"), 67 new Police("P004", "杨警官", 35, "浙江"), 68 new Police("P005", "张警官", 31, "上海"), 69 new Police("P006", "王警官", 42, "浙江"), 70 new Police("P007", "赵警官", 31, "浙江"), 71 new Police("P008", "刘警官", 49, "浙江"), 72 new Police("P009", "周警官", 32, "浙江")); 73 74 //问题:找出籍贯为浙江并且年龄大于30岁的民警或者籍贯为安徽的民警,按照民警年龄排序,若年龄相同按照籍贯排序 75 Predicate<Police> p1 = (Police police) -> police.getPoliceAge() > 30; 76 77 Predicate<Police> p2 = p1.and((Police police) -> "浙江".equals(police.getPoliceNativePlace())); 78 79 Predicate<Police> p3 = p2.or((Police police) -> "安徽".equals(police.getPoliceNativePlace())); 80 81 Function<List<Police>, List<Police>> f = (List<Police> list) -> { 82 List<Police> temp = new ArrayList<>(); 83 for (Police police : list) { 84 if (p3.test(police)) { 85 temp.add(police); 86 } 87 } 88 return temp; 89 }; 90 91 List<Police> result = f.apply(polices); 92 93 result.sort(comparing(Police :: getPoliceAge).thenComparing(Police :: getPoliceNativePlace)); 94 95 System.out.println("结果: " + result); 96 } 97 98 }
以上是关于公子奇带你一步一步了解Java8中Lambda表达式的主要内容,如果未能解决你的问题,请参考以下文章